Use personal fork of nightlyone/filelock
This commit is contained in:
parent
5fceb7eb77
commit
9753aae065
|
@ -25,7 +25,7 @@ import (
|
||||||
"github.com/tus/tusd"
|
"github.com/tus/tusd"
|
||||||
"github.com/tus/tusd/uid"
|
"github.com/tus/tusd/uid"
|
||||||
|
|
||||||
"github.com/nightlyone/lockfile"
|
"gopkg.in/Acconut/lockfile.v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
var defaultFilePerm = os.FileMode(0664)
|
var defaultFilePerm = os.FileMode(0664)
|
||||||
|
|
|
@ -1,196 +0,0 @@
|
||||||
// Package lockfile handles pid file based locking.
|
|
||||||
package lockfile
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Lockfile is a pid file which can be locked
|
|
||||||
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 (
|
|
||||||
ErrBusy = TemporaryError("Locked by other process") // If you get this, retry after a short sleep might help
|
|
||||||
ErrNotExist = TemporaryError("Lockfile created, but doesn't exist") // If you get this, retry after a short sleep might help
|
|
||||||
ErrNeedAbsPath = errors.New("Lockfiles must be given as absolute path names")
|
|
||||||
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")
|
|
||||||
)
|
|
||||||
|
|
||||||
// New describes a new filename located at the given absolute path.
|
|
||||||
func New(path string) (Lockfile, error) {
|
|
||||||
if !filepath.IsAbs(path) {
|
|
||||||
return Lockfile(""), ErrNeedAbsPath
|
|
||||||
}
|
|
||||||
return Lockfile(path), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetOwner returns who owns the lockfile.
|
|
||||||
func (l Lockfile) GetOwner() (*os.Process, error) {
|
|
||||||
name := string(l)
|
|
||||||
|
|
||||||
// Ok, see, if we have a stale lockfile here
|
|
||||||
content, err := ioutil.ReadFile(name)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// try hard for pids. If no pid, the lockfile is junk anyway and we delete it.
|
|
||||||
pid, err := scanPidLine(content)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
running, err := isRunning(pid)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if running {
|
|
||||||
proc, err := os.FindProcess(pid)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return proc, nil
|
|
||||||
}
|
|
||||||
return nil, ErrDeadOwner
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// TryLock tries to own the lock.
|
|
||||||
// It Returns nil, if successful and and error describing the reason, it didn't work out.
|
|
||||||
// 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 {
|
|
||||||
name := string(l)
|
|
||||||
|
|
||||||
// This has been checked by New already. If we trigger here,
|
|
||||||
// the caller didn't use New and re-implemented it's functionality badly.
|
|
||||||
// So panic, that he might find this easily during testing.
|
|
||||||
if !filepath.IsAbs(name) {
|
|
||||||
panic(ErrNeedAbsPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
tmplock, err := ioutil.TempFile(filepath.Dir(name), "")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
cleanup := func() {
|
|
||||||
_ = tmplock.Close()
|
|
||||||
_ = os.Remove(tmplock.Name())
|
|
||||||
}
|
|
||||||
defer cleanup()
|
|
||||||
|
|
||||||
if err := writePidLine(tmplock, os.Getpid()); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// return value intentionally ignored, as ignoring it is part of the algorithm
|
|
||||||
_ = os.Link(tmplock.Name(), name)
|
|
||||||
|
|
||||||
fiTmp, err := os.Lstat(tmplock.Name())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
fiLock, err := os.Lstat(name)
|
|
||||||
if err != nil {
|
|
||||||
// tell user that a retry would be a good idea
|
|
||||||
if os.IsNotExist(err) {
|
|
||||||
return ErrNotExist
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Success
|
|
||||||
if os.SameFile(fiTmp, fiLock) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
proc, err := l.GetOwner()
|
|
||||||
switch err {
|
|
||||||
default:
|
|
||||||
// Other errors -> defensively fail and let caller handle this
|
|
||||||
return err
|
|
||||||
case nil:
|
|
||||||
if proc.Pid != os.Getpid() {
|
|
||||||
return ErrBusy
|
|
||||||
}
|
|
||||||
case ErrDeadOwner, ErrInvalidPid:
|
|
||||||
// cases we can fix below
|
|
||||||
}
|
|
||||||
|
|
||||||
// clean stale/invalid lockfile
|
|
||||||
err = os.Remove(name)
|
|
||||||
if err != nil {
|
|
||||||
// If it doesn't exist, then it doesn't matter who removed it.
|
|
||||||
if !os.IsNotExist(err) {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// now that the stale lockfile is gone, let's recurse
|
|
||||||
return l.TryLock()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unlock a lock again, if we owned it. Returns any error that happend during release of lock.
|
|
||||||
func (l Lockfile) Unlock() error {
|
|
||||||
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
|
|
||||||
}
|
|
|
@ -0,0 +1,135 @@
|
||||||
|
// Handle pid file based locking.
|
||||||
|
package lockfile
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Lockfile string
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrBusy = errors.New("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")
|
||||||
|
ErrInvalidPid = errors.New("Lockfile contains invalid pid for system")
|
||||||
|
ErrDeadOwner = errors.New("Lockfile contains pid of process not existent on this system anymore")
|
||||||
|
)
|
||||||
|
|
||||||
|
// Describe a new filename located at path. It is expected to be an absolute path
|
||||||
|
func New(path string) (Lockfile, error) {
|
||||||
|
if !filepath.IsAbs(path) {
|
||||||
|
return Lockfile(""), ErrNeedAbsPath
|
||||||
|
}
|
||||||
|
return Lockfile(path), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Who owns the lockfile?
|
||||||
|
func (l Lockfile) GetOwner() (*os.Process, error) {
|
||||||
|
name := string(l)
|
||||||
|
|
||||||
|
// Ok, see, if we have a stale lockfile here
|
||||||
|
content, err := ioutil.ReadFile(name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var pid int
|
||||||
|
_, err = fmt.Sscanln(string(content), &pid)
|
||||||
|
if err != nil {
|
||||||
|
return nil, ErrInvalidPid
|
||||||
|
}
|
||||||
|
|
||||||
|
// try hard for pids. If no pid, the lockfile is junk anyway and we delete it.
|
||||||
|
if pid > 0 {
|
||||||
|
running, err := isRunning(pid)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to get Lockfile lock. Returns nil, if successful and and error describing the reason, it didn't work out.
|
||||||
|
// Please note, that existing lockfiles containing pids of dead processes and lockfiles containing no pid at all
|
||||||
|
// are deleted.
|
||||||
|
func (l Lockfile) TryLock() error {
|
||||||
|
name := string(l)
|
||||||
|
|
||||||
|
// This has been checked by New already. If we trigger here,
|
||||||
|
// the caller didn't use New and re-implemented it's functionality badly.
|
||||||
|
// So panic, that he might find this easily during testing.
|
||||||
|
if !filepath.IsAbs(string(name)) {
|
||||||
|
panic(ErrNeedAbsPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
tmplock, err := ioutil.TempFile(filepath.Dir(name), "")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
} else {
|
||||||
|
defer tmplock.Close()
|
||||||
|
defer os.Remove(tmplock.Name())
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = tmplock.WriteString(fmt.Sprintf("%d\n", os.Getpid()))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// return value intentionally ignored, as ignoring it is part of the algorithm
|
||||||
|
_ = os.Link(tmplock.Name(), name)
|
||||||
|
|
||||||
|
fiTmp, err := os.Lstat(tmplock.Name())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fiLock, err := os.Lstat(name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Success
|
||||||
|
if os.SameFile(fiTmp, fiLock) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = l.GetOwner()
|
||||||
|
switch err {
|
||||||
|
default:
|
||||||
|
// Other errors -> defensively fail and let caller handle this
|
||||||
|
return err
|
||||||
|
case nil:
|
||||||
|
return ErrBusy
|
||||||
|
case ErrDeadOwner, ErrInvalidPid:
|
||||||
|
// cases we can fix below
|
||||||
|
}
|
||||||
|
|
||||||
|
// clean stale/invalid lockfile
|
||||||
|
err = os.Remove(name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// now that we cleaned up the stale lockfile, let's recurse
|
||||||
|
return l.TryLock()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Release a lock again. Returns any error that happend during release of lock.
|
||||||
|
func (l Lockfile) Unlock() error {
|
||||||
|
return os.Remove(string(l))
|
||||||
|
}
|
|
@ -11,10 +11,12 @@ 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 := proc.Signal(syscall.Signal(0)); err != nil {
|
if err == nil {
|
||||||
|
return true, nil
|
||||||
|
} else {
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
return true, nil
|
}
|
||||||
}
|
}
|
|
@ -38,12 +38,6 @@
|
||||||
"revision": "c12348ce28de40eed0136aa2b644d0ee0650e56c",
|
"revision": "c12348ce28de40eed0136aa2b644d0ee0650e56c",
|
||||||
"revisionTime": "2016-04-24T11:30:07Z"
|
"revisionTime": "2016-04-24T11:30:07Z"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"checksumSHA1": "dSCW0yEU+VThfWk5v/GkSdrjIu4=",
|
|
||||||
"path": "github.com/nightlyone/lockfile",
|
|
||||||
"revision": "3d299f51e376213fcdcfd213a76afce6b290bf9d",
|
|
||||||
"revisionTime": "2016-06-19T19:46:32Z"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"checksumSHA1": "LuFv4/jlrmFNnDb/5SCSEPAM9vU=",
|
"checksumSHA1": "LuFv4/jlrmFNnDb/5SCSEPAM9vU=",
|
||||||
"path": "github.com/pmezard/go-difflib/difflib",
|
"path": "github.com/pmezard/go-difflib/difflib",
|
||||||
|
@ -91,6 +85,12 @@
|
||||||
"path": "github.com/stretchr/testify/assert",
|
"path": "github.com/stretchr/testify/assert",
|
||||||
"revision": "d77da356e56a7428ad25149ca77381849a6a5232",
|
"revision": "d77da356e56a7428ad25149ca77381849a6a5232",
|
||||||
"revisionTime": "2016-06-15T09:26:46Z"
|
"revisionTime": "2016-06-15T09:26:46Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"checksumSHA1": "0zBf3a7Gzqypt1sWefwx4NkOEIc=",
|
||||||
|
"path": "gopkg.in/Acconut/lockfile.v1",
|
||||||
|
"revision": "941fc61ce4d67dd432b94ed0bd445ad5fb9391a9",
|
||||||
|
"revisionTime": "2016-05-20T15:30:20Z"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"rootPath": "github.com/tus/tusd"
|
"rootPath": "github.com/tus/tusd"
|
||||||
|
|
Loading…
Reference in New Issue