Update lockfile dependency

This commit is contained in:
Marius 2017-03-03 11:16:56 +01:00
parent 8a00983980
commit 7f26757a40
5 changed files with 127 additions and 64 deletions

View File

@ -2,13 +2,17 @@ lockfile
========= =========
Handle locking via pid files. Handle locking via pid files.
*Attention:* This is a fork of [Ingo Oeser's amazing work](https://github.com/nightlyone/lockfile)
whose behavior differs a bit. While the original package allows a process to
obtain the same lock twice, this fork forbids this behavior.
[![Build Status Unix][1]][2] [![Build Status Unix][1]][2]
[![Build status Windows][3]][4] [![Build status Windows][3]][4]
[1]: https://secure.travis-ci.org/nightlyone/lockfile.png [1]: https://secure.travis-ci.org/Acconut/lockfile.png
[2]: https://travis-ci.org/nightlyone/lockfile [2]: https://travis-ci.org/Acconut/lockfile
[3]: https://ci.appveyor.com/api/projects/status/7mojkmauj81uvp8u/branch/master?svg=true [3]: https://ci.appveyor.com/api/projects/status/bwy487h8cgue6up5?svg=true
[4]: https://ci.appveyor.com/project/nightlyone/lockfile/branch/master [4]: https://ci.appveyor.com/project/Acconut/lockfile/branch/master
@ -19,7 +23,7 @@ For Windows suport, Go 1.4 or newer is required.
Then run Then run
go get github.com/nightlyone/lockfile go get gopkg.in/Acconut/lockfile.v1
[5]: http://golang.org [5]: http://golang.org
[6]: http://golang.org/doc/install/source [6]: http://golang.org/doc/install/source
@ -31,12 +35,7 @@ BSD
documentation documentation
------------- -------------
[package documentation at godoc.org](http://godoc.org/github.com/nightlyone/lockfile) [package documentation at godoc.org](http://godoc.org/gopkg.in/Acconut/lockfile.v1)
install
-------------------
go get github.com/nightlyone/lockfile
contributing contributing
============ ============
@ -49,4 +48,3 @@ git commit hooks
enable commit hooks via enable commit hooks via
cd .git ; rm -rf hooks; ln -s ../git-hooks hooks ; cd .. cd .git ; rm -rf hooks; ln -s ../git-hooks hooks ; cd ..

View File

@ -1,4 +1,4 @@
clone_folder: c:\gopath\src\github.com\nightlyone\lockfile clone_folder: c:\gopath\src\github.com\Acconut\lockfile
environment: environment:
GOPATH: c:\gopath GOPATH: c:\gopath
@ -9,4 +9,5 @@ install:
- go get -v -t ./... - go get -v -t ./...
build_script: build_script:
- go test -v ./... - go test -v .
- go vet .

View File

@ -1,24 +1,44 @@
// Handle pid file based locking. // Package lockfile handles pid file based locking.
// While a sync.Mutex helps against concurrency issues within a single process,
// this package is designed to help against concurrency issues between cooperating processes
// or serializing multiple invocations of the same process.
package lockfile package lockfile
import ( import (
"errors" "errors"
"fmt" "fmt"
"io"
"io/ioutil" "io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
) )
// Lockfile is a pid file which can be locked
type Lockfile string type Lockfile string
// TemporaryError is a type of error where a retry after a random amount of sleep should help to mitigate it.
type TemporaryError string
func (t TemporaryError) Error() string { return string(t) }
// Temporary returns always true.
// It exists, so you can detect it via
// if te, ok := err.(interface{ Temporary() bool }); ok {
// fmt.Println("I am a temporay error situation, so wait and retry")
// }
func (t TemporaryError) Temporary() bool { return true }
// Various errors returned by this package
var ( var (
ErrBusy = errors.New("Locked by other process") // If you get this, retry after a short sleep might help ErrBusy = TemporaryError("Locked by other process") // If you get this, retry after a short sleep might help
ErrNeedAbsPath = errors.New("Lockfiles must be given as absolute path names") ErrNotExist = TemporaryError("Lockfile created, but doesn't exist") // If you get this, retry after a short sleep might help
ErrInvalidPid = errors.New("Lockfile contains invalid pid for system") ErrNeedAbsPath = errors.New("Lockfiles must be given as absolute path names")
ErrDeadOwner = errors.New("Lockfile contains pid of process not existent on this system anymore") ErrInvalidPid = errors.New("Lockfile contains invalid pid for system")
ErrDeadOwner = errors.New("Lockfile contains pid of process not existent on this system anymore")
ErrRogueDeletion = errors.New("Lockfile owned by me has been removed unexpectedly")
) )
// Describe a new filename located at path. It is expected to be an absolute path // New describes a new filename located at the given absolute path.
func New(path string) (Lockfile, error) { func New(path string) (Lockfile, error) {
if !filepath.IsAbs(path) { if !filepath.IsAbs(path) {
return Lockfile(""), ErrNeedAbsPath return Lockfile(""), ErrNeedAbsPath
@ -26,7 +46,7 @@ func New(path string) (Lockfile, error) {
return Lockfile(path), nil return Lockfile(path), nil
} }
// Who owns the lockfile? // GetOwner returns who owns the lockfile.
func (l Lockfile) GetOwner() (*os.Process, error) { func (l Lockfile) GetOwner() (*os.Process, error) {
name := string(l) name := string(l)
@ -36,58 +56,53 @@ func (l Lockfile) GetOwner() (*os.Process, error) {
return nil, err return nil, err
} }
var pid int // try hard for pids. If no pid, the lockfile is junk anyway and we delete it.
_, err = fmt.Sscanln(string(content), &pid) pid, err := scanPidLine(content)
if err != nil { if err != nil {
return nil, ErrInvalidPid return nil, err
}
running, err := isRunning(pid)
if err != nil {
return nil, err
} }
// try hard for pids. If no pid, the lockfile is junk anyway and we delete it. if running {
if pid > 0 { proc, err := os.FindProcess(pid)
running, err := isRunning(pid)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return proc, nil
if running {
proc, err := os.FindProcess(pid)
if err != nil {
return nil, err
}
return proc, nil
} else {
return nil, ErrDeadOwner
}
} else {
return nil, ErrInvalidPid
} }
panic("Not reached") return nil, ErrDeadOwner
} }
// Try to get Lockfile lock. Returns nil, if successful and and error describing the reason, it didn't work out. // TryLock tries to own the lock.
// Please note, that existing lockfiles containing pids of dead processes and lockfiles containing no pid at all // It Returns nil, if successful and and error describing the reason, it didn't work out.
// are deleted. // Please note, that existing lockfiles containing pids of dead processes
// and lockfiles containing no pid at all are simply deleted.
func (l Lockfile) TryLock() error { func (l Lockfile) TryLock() error {
name := string(l) name := string(l)
// This has been checked by New already. If we trigger here, // This has been checked by New already. If we trigger here,
// the caller didn't use New and re-implemented it's functionality badly. // the caller didn't use New and re-implemented it's functionality badly.
// So panic, that he might find this easily during testing. // So panic, that he might find this easily during testing.
if !filepath.IsAbs(string(name)) { if !filepath.IsAbs(name) {
panic(ErrNeedAbsPath) panic(ErrNeedAbsPath)
} }
tmplock, err := ioutil.TempFile(filepath.Dir(name), "") tmplock, err := ioutil.TempFile(filepath.Dir(name), "")
if err != nil { if err != nil {
return err return err
} else {
defer tmplock.Close()
defer os.Remove(tmplock.Name())
} }
_, err = tmplock.WriteString(fmt.Sprintf("%d\n", os.Getpid())) cleanup := func() {
if err != nil { _ = tmplock.Close()
_ = os.Remove(tmplock.Name())
}
defer cleanup()
if err := writePidLine(tmplock, os.Getpid()); err != nil {
return err return err
} }
@ -100,6 +115,10 @@ func (l Lockfile) TryLock() error {
} }
fiLock, err := os.Lstat(name) fiLock, err := os.Lstat(name)
if err != nil { if err != nil {
// tell user that a retry would be a good idea
if os.IsNotExist(err) {
return ErrNotExist
}
return err return err
} }
@ -114,6 +133,10 @@ func (l Lockfile) TryLock() error {
// Other errors -> defensively fail and let caller handle this // Other errors -> defensively fail and let caller handle this
return err return err
case nil: case nil:
// This fork differs from the upstream repository in this line. We do
// not want a process to obtain a lock if this lock is already help by
// the same process. Therefore, we always return ErrBusy if the lockfile
// contains a non-dead PID.
return ErrBusy return ErrBusy
case ErrDeadOwner, ErrInvalidPid: case ErrDeadOwner, ErrInvalidPid:
// cases we can fix below // cases we can fix below
@ -122,14 +145,57 @@ func (l Lockfile) TryLock() error {
// clean stale/invalid lockfile // clean stale/invalid lockfile
err = os.Remove(name) err = os.Remove(name)
if err != nil { if err != nil {
return err // If it doesn't exist, then it doesn't matter who removed it.
if !os.IsNotExist(err) {
return err
}
} }
// now that we cleaned up the stale lockfile, let's recurse // now that the stale lockfile is gone, let's recurse
return l.TryLock() return l.TryLock()
} }
// Release a lock again. Returns any error that happend during release of lock. // Unlock a lock again, if we owned it. Returns any error that happend during release of lock.
func (l Lockfile) Unlock() error { func (l Lockfile) Unlock() error {
return os.Remove(string(l)) proc, err := l.GetOwner()
switch err {
case ErrInvalidPid, ErrDeadOwner:
return ErrRogueDeletion
case nil:
if proc.Pid == os.Getpid() {
// we really own it, so let's remove it.
return os.Remove(string(l))
}
// Not owned by me, so don't delete it.
return ErrRogueDeletion
default:
// This is an application error or system error.
// So give a better error for logging here.
if os.IsNotExist(err) {
return ErrRogueDeletion
}
// Other errors -> defensively fail and let caller handle this
return err
}
}
func writePidLine(w io.Writer, pid int) error {
_, err := io.WriteString(w, fmt.Sprintf("%d\n", pid))
return err
}
func scanPidLine(content []byte) (int, error) {
if len(content) == 0 {
return 0, ErrInvalidPid
}
var pid int
if _, err := fmt.Sscanln(string(content), &pid); err != nil {
return 0, ErrInvalidPid
}
if pid <= 0 {
return 0, ErrInvalidPid
}
return pid, nil
} }

View File

@ -11,12 +11,10 @@ func isRunning(pid int) (bool, error) {
proc, err := os.FindProcess(pid) proc, err := os.FindProcess(pid)
if err != nil { if err != nil {
return false, err return false, err
} else {
err := proc.Signal(syscall.Signal(0))
if err == nil {
return true, nil
} else {
return false, nil
}
} }
if err := proc.Signal(syscall.Signal(0)); err != nil {
return false, nil
}
return true, nil
} }

6
vendor/vendor.json vendored
View File

@ -33,10 +33,10 @@
"revisionTime": "2016-06-15T09:26:46Z" "revisionTime": "2016-06-15T09:26:46Z"
}, },
{ {
"checksumSHA1": "0zBf3a7Gzqypt1sWefwx4NkOEIc=", "checksumSHA1": "BpFC/4q4mc/SfCvsXGzCF8s9fSE=",
"path": "gopkg.in/Acconut/lockfile.v1", "path": "gopkg.in/Acconut/lockfile.v1",
"revision": "941fc61ce4d67dd432b94ed0bd445ad5fb9391a9", "revision": "ea910fd69bd541bb592c5729c94f595c696413ab",
"revisionTime": "2016-05-20T15:30:20Z" "revisionTime": "2017-03-03T10:08:22Z"
} }
], ],
"rootPath": "github.com/tus/tusd" "rootPath": "github.com/tus/tusd"