vendor: update go4.org/lock

To rev 9ba773eba85ab9e258ff516630f7f6474bc4535b

Change-Id: I32ac9fdc825e178548aaa5efc7ad219b2ec68465
This commit is contained in:
mpl 2016-01-27 15:40:06 +01:00
parent ba0126e899
commit f4c5839f8c
7 changed files with 215 additions and 163 deletions

141
vendor/go4.org/lock/lock.go generated vendored
View File

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
// package lock is a file locking library.
// Package lock is a file locking library.
package lock
import (
@ -45,115 +45,142 @@ import (
// On other operating systems, lock will fallback to using the presence and
// content of a file named name + '.lock' to implement locking behavior.
func Lock(name string) (io.Closer, error) {
return lockFn(name)
abs, err := filepath.Abs(name)
if err != nil {
return nil, err
}
lockmu.Lock()
defer lockmu.Unlock()
if locked[abs] {
return nil, fmt.Errorf("file %q already locked", abs)
}
c, err := lockFn(abs)
if err != nil {
return nil, fmt.Errorf("cannot acquire lock: %v", err)
}
locked[abs] = true
return c, nil
}
var lockFn = lockPortable
// Portable version not using fcntl. Doesn't handle crashes as gracefully,
// lockPortable is a portable version not using fcntl. Doesn't handle crashes as gracefully,
// since it can leave stale lock files.
// TODO: write pid of owner to lock file and on race see if pid is
// still alive?
func lockPortable(name string) (io.Closer, error) {
absName, err := filepath.Abs(name)
if err != nil {
return nil, fmt.Errorf("can't Lock file %q: can't find abs path: %v", name, err)
}
fi, err := os.Stat(absName)
fi, err := os.Stat(name)
if err == nil && fi.Size() > 0 {
if isStaleLock(absName) {
os.Remove(absName)
} else {
return nil, fmt.Errorf("can't Lock file %q: has non-zero size", name)
st := portableLockStatus(name)
switch st {
case statusLocked:
return nil, fmt.Errorf("file %q already locked", name)
case statusStale:
os.Remove(name)
case statusInvalid:
return nil, fmt.Errorf("can't Lock file %q: has invalid contents", name)
}
}
f, err := os.OpenFile(absName, os.O_RDWR|os.O_CREATE|os.O_TRUNC|os.O_EXCL, 0666)
f, err := os.OpenFile(name, os.O_RDWR|os.O_CREATE|os.O_TRUNC|os.O_EXCL, 0666)
if err != nil {
return nil, fmt.Errorf("failed to create lock file %s %v", absName, err)
return nil, fmt.Errorf("failed to create lock file %s %v", name, err)
}
if err := json.NewEncoder(f).Encode(&pidLockMeta{OwnerPID: os.Getpid()}); err != nil {
return nil, err
return nil, fmt.Errorf("cannot write owner pid: %v", err)
}
return &lockCloser{f: f, abs: absName}, nil
return &unlocker{
f: f,
abs: name,
portable: true,
}, nil
}
type lockStatus int
const (
statusInvalid lockStatus = iota
statusLocked
statusUnlocked
statusStale
)
type pidLockMeta struct {
OwnerPID int
}
func isStaleLock(path string) bool {
func portableLockStatus(path string) lockStatus {
f, err := os.Open(path)
if err != nil {
return false
return statusUnlocked
}
defer f.Close()
var meta pidLockMeta
if json.NewDecoder(f).Decode(&meta) != nil {
return false
return statusInvalid
}
if meta.OwnerPID == 0 {
return false
return statusInvalid
}
p, err := os.FindProcess(meta.OwnerPID)
if err != nil {
// e.g. on Windows
return true
return statusStale
}
// On unix, os.FindProcess always is true, so we have to send
// it a signal to see if it's alive.
if signalZero != nil {
if p.Signal(signalZero) != nil {
return true
return statusStale
}
}
return false
return statusLocked
}
var signalZero os.Signal // nil or set by lock_sigzero.go
type lockCloser struct {
f *os.File
abs string
once sync.Once
err error
}
func (lc *lockCloser) Close() error {
lc.once.Do(lc.close)
return lc.err
}
func (lc *lockCloser) close() {
if err := lc.f.Close(); err != nil {
lc.err = err
}
if err := os.Remove(lc.abs); err != nil {
lc.err = err
}
}
var (
lockmu sync.Mutex
locked = map[string]bool{} // abs path -> true
)
// unlocker is used by the darwin and linux implementations with fcntl
// advisory locks.
type unlocker struct {
f *os.File
abs string
portable bool
f *os.File
abs string
// once guards the close method call.
once sync.Once
// err holds the error returned by Close.
err error
}
func (u *unlocker) Close() error {
u.once.Do(u.close)
return u.err
}
func (u *unlocker) close() {
lockmu.Lock()
// Remove is not necessary but it's nice for us to clean up.
defer lockmu.Unlock()
delete(locked, u.abs)
if u.portable {
// In the portable lock implementation, it's
// important to close before removing because
// Windows won't allow us to remove an open
// file.
if err := u.f.Close(); err != nil {
u.err = err
}
if err := os.Remove(u.abs); err != nil {
// Note that if both Close and Remove fail,
// we care more about the latter than the former
// so we'll return that error.
u.err = err
}
return
}
// In other implementatioons, it's nice for us to clean up.
// If we do do this, though, it needs to be before the
// u.f.Close below.
os.Remove(u.abs)
if err := u.f.Close(); err != nil {
return err
}
delete(locked, u.abs)
lockmu.Unlock()
return nil
u.err = u.f.Close()
}

View File

@ -23,7 +23,6 @@ import (
"fmt"
"io"
"os"
"path/filepath"
"syscall"
"unsafe"
)
@ -33,18 +32,6 @@ func init() {
}
func lockFcntl(name string) (io.Closer, error) {
abs, err := filepath.Abs(name)
if err != nil {
return nil, err
}
lockmu.Lock()
if locked[abs] {
lockmu.Unlock()
return nil, fmt.Errorf("file %q already locked", abs)
}
locked[abs] = true
lockmu.Unlock()
fi, err := os.Stat(name)
if err == nil && fi.Size() > 0 {
return nil, fmt.Errorf("can't Lock file %q: has non-zero size", name)
@ -52,7 +39,7 @@ func lockFcntl(name string) (io.Closer, error) {
f, err := os.Create(name)
if err != nil {
return nil, fmt.Errorf("Lock Create of %s (abs: %s) failed: %v", name, abs, err)
return nil, fmt.Errorf("Lock Create of %s failed: %v", name, err)
}
// This type matches C's "struct flock" defined in /usr/include/sys/fcntl.h.
@ -76,5 +63,5 @@ func lockFcntl(name string) (io.Closer, error) {
f.Close()
return nil, errno
}
return &unlocker{f, abs}, nil
return &unlocker{f: f, abs: name}, nil
}

15
vendor/go4.org/lock/lock_freebsd.go generated vendored
View File

@ -20,7 +20,6 @@ import (
"fmt"
"io"
"os"
"path/filepath"
"syscall"
"unsafe"
)
@ -30,18 +29,6 @@ func init() {
}
func lockFcntl(name string) (io.Closer, error) {
abs, err := filepath.Abs(name)
if err != nil {
return nil, err
}
lockmu.Lock()
if locked[abs] {
lockmu.Unlock()
return nil, fmt.Errorf("file %q already locked", abs)
}
locked[abs] = true
lockmu.Unlock()
fi, err := os.Stat(name)
if err == nil && fi.Size() > 0 {
return nil, fmt.Errorf("can't Lock file %q: has non-zero size", name)
@ -75,5 +62,5 @@ func lockFcntl(name string) (io.Closer, error) {
f.Close()
return nil, errno
}
return &unlocker{f, abs}, nil
return &unlocker{f: f, abs: name}, nil
}

View File

@ -23,7 +23,6 @@ import (
"fmt"
"io"
"os"
"path/filepath"
"syscall"
"unsafe"
)
@ -33,18 +32,6 @@ func init() {
}
func lockFcntl(name string) (io.Closer, error) {
abs, err := filepath.Abs(name)
if err != nil {
return nil, err
}
lockmu.Lock()
if locked[abs] {
lockmu.Unlock()
return nil, fmt.Errorf("file %q already locked", abs)
}
locked[abs] = true
lockmu.Unlock()
fi, err := os.Stat(name)
if err == nil && fi.Size() > 0 {
return nil, fmt.Errorf("can't Lock file %q: has non-zero size", name)
@ -76,5 +63,5 @@ func lockFcntl(name string) (io.Closer, error) {
f.Close()
return nil, errno
}
return &unlocker{f, abs}, nil
return &unlocker{f: f, abs: name}, nil
}

View File

@ -23,7 +23,6 @@ import (
"fmt"
"io"
"os"
"path/filepath"
"syscall"
"unsafe"
)
@ -33,18 +32,6 @@ func init() {
}
func lockFcntl(name string) (io.Closer, error) {
abs, err := filepath.Abs(name)
if err != nil {
return nil, err
}
lockmu.Lock()
if locked[abs] {
lockmu.Unlock()
return nil, fmt.Errorf("file %q already locked", abs)
}
locked[abs] = true
lockmu.Unlock()
fi, err := os.Stat(name)
if err == nil && fi.Size() > 0 {
return nil, fmt.Errorf("can't Lock file %q: has non-zero size", name)
@ -77,5 +64,5 @@ func lockFcntl(name string) (io.Closer, error) {
f.Close()
return nil, errno
}
return &unlocker{f, abs}, nil
return &unlocker{f: f, abs: name}, nil
}

20
vendor/go4.org/lock/lock_plan9.go generated vendored
View File

@ -20,7 +20,6 @@ import (
"fmt"
"io"
"os"
"path/filepath"
)
func init() {
@ -28,28 +27,15 @@ func init() {
}
func lockPlan9(name string) (io.Closer, error) {
var f *os.File
abs, err := filepath.Abs(name)
if err != nil {
return nil, err
}
lockmu.Lock()
if locked[abs] {
lockmu.Unlock()
return nil, fmt.Errorf("file %q already locked", abs)
}
locked[abs] = true
lockmu.Unlock()
fi, err := os.Stat(name)
if err == nil && fi.Size() > 0 {
return nil, fmt.Errorf("can't Lock file %q: has non-zero size", name)
}
f, err = os.OpenFile(name, os.O_RDWR|os.O_CREATE, os.ModeExclusive|0644)
f, err := os.OpenFile(name, os.O_RDWR|os.O_CREATE, os.ModeExclusive|0644)
if err != nil {
return nil, fmt.Errorf("Lock Create of %s (abs: %s) failed: %v", name, abs, err)
return nil, fmt.Errorf("Lock Create of %s failed: %v", name, err)
}
return &unlocker{f, abs}, nil
return &unlocker{f: f, abs: name}, nil
}

155
vendor/go4.org/lock/lock_test.go generated vendored
View File

@ -17,9 +17,11 @@ limitations under the License.
package lock
import (
"bufio"
"errors"
"fmt"
"io"
"io/ioutil"
"log"
"os"
"os/exec"
"path/filepath"
@ -46,18 +48,27 @@ func TestLockInChild(t *testing.T) {
lock = lockPortable
}
lk, err := lock(f)
if err != nil {
log.Fatalf("Lock failed: %v", err)
var lk io.Closer
for scan := bufio.NewScanner(os.Stdin); scan.Scan(); {
var err error
switch scan.Text() {
case "lock":
lk, err = lock(f)
case "unlock":
err = lk.Close()
lk = nil
case "exit":
// Simulate a crash, or at least not unlocking the lock.
os.Exit(0)
default:
err = fmt.Errorf("unexpected child command %q", scan.Text())
}
if err != nil {
fmt.Println(err)
} else {
fmt.Println("")
}
}
if v, _ := strconv.ParseBool(os.Getenv("TEST_LOCK_CRASH")); v {
// Simulate a crash, or at least not unlocking the
// lock. We still exit 0 just to simplify the parent
// process exec code.
os.Exit(0)
}
lk.Close()
}
func testLock(t *testing.T, portable bool) {
@ -65,6 +76,7 @@ func testLock(t *testing.T, portable bool) {
if portable {
lock = lockPortable
}
t.Logf("test lock, portable %v", portable)
td, err := ioutil.TempDir("", "")
if err != nil {
@ -74,30 +86,27 @@ func testLock(t *testing.T, portable bool) {
path := filepath.Join(td, "foo.lock")
childLock := func(crash bool) error {
cmd := exec.Command(os.Args[0], "-test.run=LockInChild$")
cmd.Env = []string{"TEST_LOCK_FILE=" + path}
if portable {
cmd.Env = append(cmd.Env, "TEST_LOCK_PORTABLE=1")
}
if crash {
cmd.Env = append(cmd.Env, "TEST_LOCK_CRASH=1")
}
out, err := cmd.CombinedOutput()
t.Logf("Child output: %q (err %v)", out, err)
if err != nil {
return fmt.Errorf("Child Process lock of %s failed: %v %s", path, err, out)
}
return nil
}
proc := newChildProc(t, path, portable)
defer proc.kill()
t.Logf("Locking in crashing child...")
if err := childLock(true); err != nil {
t.Logf("First lock in child")
if err := proc.do("lock"); err != nil {
t.Fatalf("first lock in child process: %v", err)
}
t.Logf("Crash child")
if err := proc.do("exit"); err != nil {
t.Fatalf("crash in child process: %v", err)
}
proc = newChildProc(t, path, portable)
defer proc.kill()
t.Logf("Locking+unlocking in child...")
if err := childLock(false); err != nil {
if err := proc.do("lock"); err != nil {
t.Fatalf("lock in child process after crashing child: %v", err)
}
if err := proc.do("unlock"); err != nil {
t.Fatalf("lock in child process after crashing child: %v", err)
}
@ -114,7 +123,7 @@ func testLock(t *testing.T, portable bool) {
}
t.Logf("Locking in child...")
if childLock(false) == nil {
if err := proc.do("lock"); err == nil {
t.Fatalf("expected lock in child process to fail")
}
@ -123,9 +132,91 @@ func testLock(t *testing.T, portable bool) {
t.Fatal(err)
}
t.Logf("Trying lock again in child...")
if err := proc.do("lock"); err != nil {
t.Fatal(err)
}
if err := proc.do("unlock"); err != nil {
t.Fatal(err)
}
lk3, err := lock(path)
if err != nil {
t.Fatal(err)
}
lk3.Close()
}
type childLockCmd struct {
op string
reply chan<- error
}
type childProc struct {
proc *os.Process
c chan childLockCmd
}
func (c *childProc) kill() {
c.proc.Kill()
}
func (c *childProc) do(op string) error {
reply := make(chan error)
c.c <- childLockCmd{
op: op,
reply: reply,
}
return <-reply
}
func newChildProc(t *testing.T, path string, portable bool) *childProc {
cmd := exec.Command(os.Args[0], "-test.run=LockInChild$")
cmd.Env = []string{"TEST_LOCK_FILE=" + path}
toChild, err := cmd.StdinPipe()
if err != nil {
t.Fatalf("cannot make pipe: %v", err)
}
fromChild, err := cmd.StdoutPipe()
if err != nil {
t.Fatalf("cannot make pipe: %v", err)
}
cmd.Stderr = os.Stderr
if portable {
cmd.Env = append(cmd.Env, "TEST_LOCK_PORTABLE=1")
}
if err := cmd.Start(); err != nil {
t.Fatalf("cannot start child: %v", err)
}
cmdChan := make(chan childLockCmd)
go func() {
defer fromChild.Close()
defer toChild.Close()
inScan := bufio.NewScanner(fromChild)
for c := range cmdChan {
fmt.Fprintln(toChild, c.op)
ok := inScan.Scan()
if c.op == "exit" {
if ok {
c.reply <- errors.New("child did not exit")
} else {
cmd.Wait()
c.reply <- nil
}
break
}
if !ok {
panic("child exited early")
}
if errText := inScan.Text(); errText != "" {
c.reply <- errors.New(errText)
} else {
c.reply <- nil
}
}
}()
return &childProc{
c: cmdChan,
proc: cmd.Process,
}
}