mirror of https://github.com/perkeep/perkeep.git
vendor: update go4.org/lock
To rev 9ba773eba85ab9e258ff516630f7f6474bc4535b Change-Id: I32ac9fdc825e178548aaa5efc7ad219b2ec68465
This commit is contained in:
parent
ba0126e899
commit
f4c5839f8c
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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,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
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue