From f4c5839f8c7831d14d625f9dc0af7a153fd49280 Mon Sep 17 00:00:00 2001 From: mpl Date: Wed, 27 Jan 2016 15:40:06 +0100 Subject: [PATCH] vendor: update go4.org/lock To rev 9ba773eba85ab9e258ff516630f7f6474bc4535b Change-Id: I32ac9fdc825e178548aaa5efc7ad219b2ec68465 --- vendor/go4.org/lock/lock.go | 141 ++++++++++++--------- vendor/go4.org/lock/lock_darwin_amd64.go | 17 +-- vendor/go4.org/lock/lock_freebsd.go | 15 +-- vendor/go4.org/lock/lock_linux_amd64.go | 15 +-- vendor/go4.org/lock/lock_linux_arm.go | 15 +-- vendor/go4.org/lock/lock_plan9.go | 20 +-- vendor/go4.org/lock/lock_test.go | 155 ++++++++++++++++++----- 7 files changed, 215 insertions(+), 163 deletions(-) diff --git a/vendor/go4.org/lock/lock.go b/vendor/go4.org/lock/lock.go index b012566a2..a9fb802de 100644 --- a/vendor/go4.org/lock/lock.go +++ b/vendor/go4.org/lock/lock.go @@ -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() } diff --git a/vendor/go4.org/lock/lock_darwin_amd64.go b/vendor/go4.org/lock/lock_darwin_amd64.go index 9fea51fe8..35f5787ba 100644 --- a/vendor/go4.org/lock/lock_darwin_amd64.go +++ b/vendor/go4.org/lock/lock_darwin_amd64.go @@ -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 } diff --git a/vendor/go4.org/lock/lock_freebsd.go b/vendor/go4.org/lock/lock_freebsd.go index d3835d624..ee2767a0a 100644 --- a/vendor/go4.org/lock/lock_freebsd.go +++ b/vendor/go4.org/lock/lock_freebsd.go @@ -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 } diff --git a/vendor/go4.org/lock/lock_linux_amd64.go b/vendor/go4.org/lock/lock_linux_amd64.go index 3a7eb00a6..08b3aae92 100644 --- a/vendor/go4.org/lock/lock_linux_amd64.go +++ b/vendor/go4.org/lock/lock_linux_amd64.go @@ -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 } diff --git a/vendor/go4.org/lock/lock_linux_arm.go b/vendor/go4.org/lock/lock_linux_arm.go index c2a0a102e..ebf87bd3e 100644 --- a/vendor/go4.org/lock/lock_linux_arm.go +++ b/vendor/go4.org/lock/lock_linux_arm.go @@ -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 } diff --git a/vendor/go4.org/lock/lock_plan9.go b/vendor/go4.org/lock/lock_plan9.go index bdf4e2292..d841c27d7 100644 --- a/vendor/go4.org/lock/lock_plan9.go +++ b/vendor/go4.org/lock/lock_plan9.go @@ -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 } diff --git a/vendor/go4.org/lock/lock_test.go b/vendor/go4.org/lock/lock_test.go index 518d2f025..de9c8f872 100644 --- a/vendor/go4.org/lock/lock_test.go +++ b/vendor/go4.org/lock/lock_test.go @@ -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, + } +}