From cb671d50c426f1395fba9bc099cf7a0d3c7bf919 Mon Sep 17 00:00:00 2001 From: Nathan Ollerenshaw Date: Sun, 3 Sep 2017 10:26:17 -0700 Subject: [PATCH] Fix for file locking on filesystems that don't support hard linking. #947 #22 #913 --- Gopkg.lock | 14 +- internal/gps/source_manager.go | 88 ++--- .../nightlyone/lockfile/.gitmodules | 3 - .../nightlyone/lockfile/.travis.yml | 14 - vendor/github.com/nightlyone/lockfile/LICENSE | 19 -- .../github.com/nightlyone/lockfile/README.md | 52 --- .../nightlyone/lockfile/appveyor.yml | 12 - .../nightlyone/lockfile/lockfile.go | 201 ------------ .../nightlyone/lockfile/lockfile_test.go | 308 ------------------ .../nightlyone/lockfile/lockfile_unix.go | 20 -- .../nightlyone/lockfile/lockfile_windows.go | 30 -- .../lockfile => theckman/go-flock}/.gitignore | 7 +- .../github.com/theckman/go-flock/.travis.yml | 9 + vendor/github.com/theckman/go-flock/LICENSE | 27 ++ vendor/github.com/theckman/go-flock/README.md | 36 ++ vendor/github.com/theckman/go-flock/flock.go | 61 ++++ .../theckman/go-flock/flock_example_test.go | 47 +++ .../theckman/go-flock/flock_test.go | 151 +++++++++ .../theckman/go-flock/flock_unix.go | 104 ++++++ .../theckman/go-flock/flock_winapi.go | 92 ++++++ .../theckman/go-flock/flock_windows.go | 100 ++++++ 21 files changed, 670 insertions(+), 725 deletions(-) delete mode 100644 vendor/github.com/nightlyone/lockfile/.gitmodules delete mode 100644 vendor/github.com/nightlyone/lockfile/.travis.yml delete mode 100644 vendor/github.com/nightlyone/lockfile/LICENSE delete mode 100644 vendor/github.com/nightlyone/lockfile/README.md delete mode 100644 vendor/github.com/nightlyone/lockfile/appveyor.yml delete mode 100644 vendor/github.com/nightlyone/lockfile/lockfile.go delete mode 100644 vendor/github.com/nightlyone/lockfile/lockfile_test.go delete mode 100644 vendor/github.com/nightlyone/lockfile/lockfile_unix.go delete mode 100644 vendor/github.com/nightlyone/lockfile/lockfile_windows.go rename vendor/github.com/{nightlyone/lockfile => theckman/go-flock}/.gitignore (86%) create mode 100644 vendor/github.com/theckman/go-flock/.travis.yml create mode 100644 vendor/github.com/theckman/go-flock/LICENSE create mode 100644 vendor/github.com/theckman/go-flock/README.md create mode 100644 vendor/github.com/theckman/go-flock/flock.go create mode 100644 vendor/github.com/theckman/go-flock/flock_example_test.go create mode 100644 vendor/github.com/theckman/go-flock/flock_test.go create mode 100644 vendor/github.com/theckman/go-flock/flock_unix.go create mode 100644 vendor/github.com/theckman/go-flock/flock_winapi.go create mode 100644 vendor/github.com/theckman/go-flock/flock_windows.go diff --git a/Gopkg.lock b/Gopkg.lock index 5278bfd612..aec71c8734 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -26,12 +26,6 @@ packages = ["."] revision = "cd8b52f8269e0feb286dfeef29f8fe4d5b397e0b" -[[projects]] - branch = "master" - name = "github.com/nightlyone/lockfile" - packages = ["."] - revision = "e83dc5e7bba095e8d32fb2124714bf41f2a30cb5" - [[projects]] name = "github.com/pelletier/go-buffruneio" packages = ["."] @@ -56,9 +50,15 @@ packages = ["."] revision = "836a144573533ea4da4e6929c235fd348aed1c80" +[[projects]] + branch = "master" + name = "github.com/theckman/go-flock" + packages = ["."] + revision = "6de226b0d5f040ed85b88c82c381709b98277f3d" + [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "7c692c08851064cf6167056c23082edcf393d3af6bc0768c3111e0dde7444960" + inputs-digest = "bb3f72fff849c3fe5cf827c32437acd4e9a91127653ec097c6062574f20cc90a" solver-name = "gps-cdcl" solver-version = 1 diff --git a/internal/gps/source_manager.go b/internal/gps/source_manager.go index e5110b042e..bc01eaa6a5 100644 --- a/internal/gps/source_manager.go +++ b/internal/gps/source_manager.go @@ -19,9 +19,9 @@ import ( "time" "github.com/golang/dep/internal/gps/pkgtree" - "github.com/nightlyone/lockfile" "github.com/pkg/errors" "github.com/sdboyer/constext" + flock "github.com/theckman/go-flock" ) // Used to compute a friendly filepath from a URL-shaped input. @@ -115,7 +115,7 @@ func (p ProjectAnalyzerInfo) String() string { // tools; control via dependency injection is intended to be sufficient. type SourceMgr struct { cachedir string // path to root of cache dir - lf *lockfile.Lockfile // handle for the sm lock file on disk + lf *flock.Flock // handle for the sm lock file on disk suprvsr *supervisor // subsystem that supervises running calls/io cancelAll context.CancelFunc // cancel func to kill all running work deduceCoord *deductionCoordinator // subsystem that manages import path deduction @@ -152,78 +152,58 @@ type SourceManagerConfig struct { // bug!). It should be safe to reuse across concurrent solving runs, even on // unrelated projects. func NewSourceManager(c SourceManagerConfig) (*SourceMgr, error) { + var locked bool + var err error + var lasttime time.Time + if c.Logger == nil { c.Logger = log.New(ioutil.Discard, "", 0) } - err := os.MkdirAll(filepath.Join(c.Cachedir, "sources"), 0777) + err = os.MkdirAll(filepath.Join(c.Cachedir, "sources"), 0777) if err != nil { return nil, err } - // Fix for #820 + // Lockfile semantics using go-flock. The reason for switching to this is + // that github.com/nightlyone/lockfile uses hard links for it's locking + // strategy which does not work on filesystems that don't support hard links. // - // Consult https://godoc.org/github.com/nightlyone/lockfile for the lockfile - // behaviour. It's magic. It deals with stale processes, and if there is - // a process keeping the lock busy, it will pass back a temporary error that - // we can spin on. + // go-flock uses the OS native file locking function, so has a better chance + // of working. + // + // go-flock uses a mutex so is safe if multiple goroutines/threads try to call + // gps.NewSourceManager() glpath := filepath.Join(c.Cachedir, "sm.lock") - lockfile, err := lockfile.New(glpath) - if err != nil { - return nil, CouldNotCreateLockError{ - Path: glpath, - Err: errors.Wrapf(err, "unable to create lock %s", glpath), - } - } + lockfile := flock.NewFlock(glpath) - process, err := lockfile.GetOwner() - if err == nil { - // If we didn't get an error, then the lockfile exists already. We should - // check to see if it's us already: - if process.Pid == os.Getpid() { + for !locked { + locked, err = lockfile.TryLock() + + if err != nil { return nil, CouldNotCreateLockError{ Path: glpath, - Err: fmt.Errorf("lockfile %s already locked by this process", glpath), + Err: errors.Wrapf(err, "unable to create lock %s", glpath), } } - // There is a lockfile, but it's owned by someone else. We'll try to lock - // it anyway. - } - - // If it's a TemporaryError, we retry every second. Otherwise, we fail - // permanently. - // - // TODO: #534 needs to be implemented to provide a better way to log warnings, - // but until then we will just use stderr. + if !locked { + nowtime := time.Now() + duration := nowtime.Sub(lasttime) - // Implicit Time of 0. - var lasttime time.Time - err = lockfile.TryLock() - for err != nil { - nowtime := time.Now() - duration := nowtime.Sub(lasttime) - - // The first time this is evaluated, duration will be very large as lasttime is 0. - // Unless time travel is invented and someone travels back to the year 1, we should - // be ok. - if duration > 15*time.Second { - fmt.Fprintf(os.Stderr, "waiting for lockfile %s: %s\n", glpath, err.Error()) - lasttime = nowtime - } + // When locking fails we retry every second. There does not appear to be + // + // TODO: #534 needs to be implemented to provide a better way to log warnings, + // but until then we will use stderr. - if _, ok := err.(interface { - Temporary() bool - }); ok { - time.Sleep(time.Second * 1) - } else { - return nil, CouldNotCreateLockError{ - Path: glpath, - Err: errors.Wrapf(err, "unable to lock %s", glpath), + if duration > 15*time.Second { + fmt.Fprintf(os.Stderr, "waiting for lockfile %s\n", glpath) + lasttime = nowtime } + + time.Sleep(1 * time.Second) } - err = lockfile.TryLock() } ctx, cf := context.WithCancel(context.TODO()) @@ -232,7 +212,7 @@ func NewSourceManager(c SourceManagerConfig) (*SourceMgr, error) { sm := &SourceMgr{ cachedir: c.Cachedir, - lf: &lockfile, + lf: lockfile, suprvsr: superv, cancelAll: cf, deduceCoord: deducer, diff --git a/vendor/github.com/nightlyone/lockfile/.gitmodules b/vendor/github.com/nightlyone/lockfile/.gitmodules deleted file mode 100644 index 6faa9e3469..0000000000 --- a/vendor/github.com/nightlyone/lockfile/.gitmodules +++ /dev/null @@ -1,3 +0,0 @@ -[submodule "git-hooks"] - path = git-hooks - url = https://github.com/nightlyone/git-hooks diff --git a/vendor/github.com/nightlyone/lockfile/.travis.yml b/vendor/github.com/nightlyone/lockfile/.travis.yml deleted file mode 100644 index 76e5962bf1..0000000000 --- a/vendor/github.com/nightlyone/lockfile/.travis.yml +++ /dev/null @@ -1,14 +0,0 @@ -language: go -go: - - 1.4.3 - - 1.6.2 - - tip - -# Only test commits to production branch and all pull requests -branches: - only: - - master - -matrix: - allow_failures: - - go: tip diff --git a/vendor/github.com/nightlyone/lockfile/LICENSE b/vendor/github.com/nightlyone/lockfile/LICENSE deleted file mode 100644 index eb5b804685..0000000000 --- a/vendor/github.com/nightlyone/lockfile/LICENSE +++ /dev/null @@ -1,19 +0,0 @@ -Copyright (c) 2012 Ingo Oeser - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. diff --git a/vendor/github.com/nightlyone/lockfile/README.md b/vendor/github.com/nightlyone/lockfile/README.md deleted file mode 100644 index c35235cdae..0000000000 --- a/vendor/github.com/nightlyone/lockfile/README.md +++ /dev/null @@ -1,52 +0,0 @@ -lockfile -========= -Handle locking via pid files. - -[![Build Status Unix][1]][2] -[![Build status Windows][3]][4] - -[1]: https://secure.travis-ci.org/nightlyone/lockfile.png -[2]: https://travis-ci.org/nightlyone/lockfile -[3]: https://ci.appveyor.com/api/projects/status/7mojkmauj81uvp8u/branch/master?svg=true -[4]: https://ci.appveyor.com/project/nightlyone/lockfile/branch/master - - - -install -------- -Install [Go 1][5], either [from source][6] or [with a prepackaged binary][7]. -For Windows suport, Go 1.4 or newer is required. - -Then run - - go get github.com/nightlyone/lockfile - -[5]: http://golang.org -[6]: http://golang.org/doc/install/source -[7]: http://golang.org/doc/install - -LICENSE -------- -MIT - -documentation -------------- -[package documentation at godoc.org](http://godoc.org/github.com/nightlyone/lockfile) - -install -------------------- - go get github.com/nightlyone/lockfile - - -contributing -============ - -Contributions are welcome. Please open an issue or send me a pull request for a dedicated branch. -Make sure the git commit hooks show it works. - -git commit hooks ------------------------ -enable commit hooks via - - cd .git ; rm -rf hooks; ln -s ../git-hooks hooks ; cd .. - diff --git a/vendor/github.com/nightlyone/lockfile/appveyor.yml b/vendor/github.com/nightlyone/lockfile/appveyor.yml deleted file mode 100644 index cf72a58b13..0000000000 --- a/vendor/github.com/nightlyone/lockfile/appveyor.yml +++ /dev/null @@ -1,12 +0,0 @@ -clone_folder: c:\gopath\src\github.com\nightlyone\lockfile - -environment: - GOPATH: c:\gopath - -install: - - go version - - go env - - go get -v -t ./... - -build_script: - - go test -v ./... diff --git a/vendor/github.com/nightlyone/lockfile/lockfile.go b/vendor/github.com/nightlyone/lockfile/lockfile.go deleted file mode 100644 index 2d956e07e3..0000000000 --- a/vendor/github.com/nightlyone/lockfile/lockfile.go +++ /dev/null @@ -1,201 +0,0 @@ -// 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. You can also combine sync.Mutex -// with Lockfile in order to serialize an action between different goroutines in a single program -// and also multiple invocations of this program. -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), filepath.Base(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 -} diff --git a/vendor/github.com/nightlyone/lockfile/lockfile_test.go b/vendor/github.com/nightlyone/lockfile/lockfile_test.go deleted file mode 100644 index be2c821e21..0000000000 --- a/vendor/github.com/nightlyone/lockfile/lockfile_test.go +++ /dev/null @@ -1,308 +0,0 @@ -package lockfile - -import ( - "fmt" - "io/ioutil" - "math/rand" - "os" - "path/filepath" - "strconv" - "testing" -) - -func ExampleLockfile() { - lock, err := New(filepath.Join(os.TempDir(), "lock.me.now.lck")) - if err != nil { - fmt.Printf("Cannot init lock. reason: %v", err) - panic(err) // handle properly please! - } - err = lock.TryLock() - - // Error handling is essential, as we only try to get the lock. - if err != nil { - fmt.Printf("Cannot lock %q, reason: %v", lock, err) - panic(err) // handle properly please! - } - - defer lock.Unlock() - - fmt.Println("Do stuff under lock") - // Output: Do stuff under lock -} - -func TestBasicLockUnlock(t *testing.T) { - path, err := filepath.Abs("test_lockfile.pid") - if err != nil { - panic(err) - } - - lf, err := New(path) - if err != nil { - t.Fail() - fmt.Println("Error making lockfile: ", err) - return - } - - err = lf.TryLock() - if err != nil { - t.Fail() - fmt.Println("Error locking lockfile: ", err) - return - } - - err = lf.Unlock() - if err != nil { - t.Fail() - fmt.Println("Error unlocking lockfile: ", err) - return - } -} - -func GetDeadPID() int { - // I have no idea how windows handles large PIDs, or if they even exist. - // So limit it to be less or equal to 4096 to be safe. - - const maxPid = 4095 - - // limited iteration, so we finish one day - seen := map[int]bool{} - for len(seen) < maxPid { - pid := rand.Intn(maxPid + 1) // see https://godoc.org/math/rand#Intn why - if seen[pid] { - continue - } - seen[pid] = true - running, err := isRunning(pid) - if err != nil { - fmt.Println("Error checking PID: ", err) - continue - } - - if !running { - return pid - } - } - panic(fmt.Sprintf("all pids lower %d are used, cannot test this", maxPid)) -} - -func TestBusy(t *testing.T) { - path, err := filepath.Abs("test_lockfile.pid") - if err != nil { - t.Fatal(err) - return - } - - pid := os.Getppid() - - if err := ioutil.WriteFile(path, []byte(strconv.Itoa(pid)+"\n"), 0666); err != nil { - t.Fatal(err) - return - } - defer os.Remove(path) - - lf, err := New(path) - if err != nil { - t.Fatal(err) - return - } - - got := lf.TryLock() - if got != ErrBusy { - t.Fatalf("expected error %q, got %v", ErrBusy, got) - return - } -} - -func TestRogueDeletion(t *testing.T) { - path, err := filepath.Abs("test_lockfile.pid") - if err != nil { - t.Fatal(err) - return - } - lf, err := New(path) - if err != nil { - t.Fatal(err) - return - } - err = lf.TryLock() - if err != nil { - t.Fatal(err) - return - } - err = os.Remove(path) - if err != nil { - t.Fatal(err) - return - } - - got := lf.Unlock() - if got != ErrRogueDeletion { - t.Fatalf("unexpected error: %v", got) - return - } -} - -func TestRogueDeletionDeadPid(t *testing.T) { - path, err := filepath.Abs("test_lockfile.pid") - if err != nil { - t.Fatal(err) - return - } - lf, err := New(path) - if err != nil { - t.Fatal(err) - return - } - err = lf.TryLock() - if err != nil { - t.Fatal(err) - return - } - - pid := GetDeadPID() - if err := ioutil.WriteFile(path, []byte(strconv.Itoa(pid)+"\n"), 0666); err != nil { - t.Fatal(err) - return - } - defer os.Remove(path) - - err = lf.Unlock() - if err != ErrRogueDeletion { - t.Fatalf("unexpected error: %v", err) - return - } - - if _, err := os.Stat(path); os.IsNotExist(err) { - t.Fatal("lockfile should not be deleted by us, if we didn't create it") - } else { - if err != nil { - t.Fatalf("unexpected error %v", err) - } - } -} - -func TestRemovesStaleLockOnDeadOwner(t *testing.T) { - path, err := filepath.Abs("test_lockfile.pid") - if err != nil { - t.Fatal(err) - return - } - lf, err := New(path) - if err != nil { - t.Fatal(err) - return - } - pid := GetDeadPID() - if err := ioutil.WriteFile(path, []byte(strconv.Itoa(pid)+"\n"), 0666); err != nil { - t.Fatal(err) - return - } - err = lf.TryLock() - if err != nil { - t.Fatal(err) - return - } - - if err := lf.Unlock(); err != nil { - t.Fatal(err) - return - } -} - -func TestInvalidPidLeadToReplacedLockfileAndSuccess(t *testing.T) { - path, err := filepath.Abs("test_lockfile.pid") - if err != nil { - t.Fatal(err) - return - } - if err := ioutil.WriteFile(path, []byte("\n"), 0666); err != nil { - t.Fatal(err) - return - } - defer os.Remove(path) - - lf, err := New(path) - if err != nil { - t.Fatal(err) - return - } - - if err := lf.TryLock(); err != nil { - t.Fatalf("unexpected error: %v", err) - return - } - - // now check if file exists and contains the correct content - got, err := ioutil.ReadFile(path) - if err != nil { - t.Fatalf("unexpected error %v", err) - return - } - want := fmt.Sprintf("%d\n", os.Getpid()) - if string(got) != want { - t.Fatalf("got %q, want %q", got, want) - } -} - -func TestScanPidLine(t *testing.T) { - tests := [...]struct { - input []byte - pid int - xfail error - }{ - { - xfail: ErrInvalidPid, - }, - { - input: []byte(""), - xfail: ErrInvalidPid, - }, - { - input: []byte("\n"), - xfail: ErrInvalidPid, - }, - { - input: []byte("-1\n"), - xfail: ErrInvalidPid, - }, - { - input: []byte("0\n"), - xfail: ErrInvalidPid, - }, - { - input: []byte("a\n"), - xfail: ErrInvalidPid, - }, - { - input: []byte("1\n"), - pid: 1, - }, - } - - // test positive cases first - for step, tc := range tests { - if tc.xfail != nil { - continue - } - want := tc.pid - got, err := scanPidLine(tc.input) - if err != nil { - t.Fatalf("%d: unexpected error %v", step, err) - } - if got != want { - t.Errorf("%d: expected pid %d, got %d", step, want, got) - } - } - - // test negative cases now - for step, tc := range tests { - if tc.xfail == nil { - continue - } - want := tc.xfail - _, got := scanPidLine(tc.input) - if got != want { - t.Errorf("%d: expected error %v, got %v", step, want, got) - } - } -} diff --git a/vendor/github.com/nightlyone/lockfile/lockfile_unix.go b/vendor/github.com/nightlyone/lockfile/lockfile_unix.go deleted file mode 100644 index 742b041fb6..0000000000 --- a/vendor/github.com/nightlyone/lockfile/lockfile_unix.go +++ /dev/null @@ -1,20 +0,0 @@ -// +build darwin dragonfly freebsd linux nacl netbsd openbsd solaris - -package lockfile - -import ( - "os" - "syscall" -) - -func isRunning(pid int) (bool, error) { - proc, err := os.FindProcess(pid) - if err != nil { - return false, err - } - - if err := proc.Signal(syscall.Signal(0)); err != nil { - return false, nil - } - return true, nil -} diff --git a/vendor/github.com/nightlyone/lockfile/lockfile_windows.go b/vendor/github.com/nightlyone/lockfile/lockfile_windows.go deleted file mode 100644 index 482bd91d7b..0000000000 --- a/vendor/github.com/nightlyone/lockfile/lockfile_windows.go +++ /dev/null @@ -1,30 +0,0 @@ -package lockfile - -import ( - "syscall" -) - -//For some reason these consts don't exist in syscall. -const ( - error_invalid_parameter = 87 - code_still_active = 259 -) - -func isRunning(pid int) (bool, error) { - procHnd, err := syscall.OpenProcess(syscall.PROCESS_QUERY_INFORMATION, true, uint32(pid)) - if err != nil { - if scerr, ok := err.(syscall.Errno); ok { - if uintptr(scerr) == error_invalid_parameter { - return false, nil - } - } - } - - var code uint32 - err = syscall.GetExitCodeProcess(procHnd, &code) - if err != nil { - return false, err - } - - return code == code_still_active, nil -} diff --git a/vendor/github.com/nightlyone/lockfile/.gitignore b/vendor/github.com/theckman/go-flock/.gitignore similarity index 86% rename from vendor/github.com/nightlyone/lockfile/.gitignore rename to vendor/github.com/theckman/go-flock/.gitignore index 5a05665dee..daf913b1b3 100644 --- a/vendor/github.com/nightlyone/lockfile/.gitignore +++ b/vendor/github.com/theckman/go-flock/.gitignore @@ -7,11 +7,6 @@ _obj _test -# popular temporaries -.err -.out -.diff - # Architecture specific extensions/prefixes *.[568vq] [568vq].out @@ -25,3 +20,5 @@ _cgo_export.* _testmain.go *.exe +*.test +*.prof diff --git a/vendor/github.com/theckman/go-flock/.travis.yml b/vendor/github.com/theckman/go-flock/.travis.yml new file mode 100644 index 0000000000..687875bbf4 --- /dev/null +++ b/vendor/github.com/theckman/go-flock/.travis.yml @@ -0,0 +1,9 @@ +language: go +go: + - 1.5 +script: go test -v ./... -check.vv +sudo: false +notifications: + email: + on_success: never + on_failure: always diff --git a/vendor/github.com/theckman/go-flock/LICENSE b/vendor/github.com/theckman/go-flock/LICENSE new file mode 100644 index 0000000000..aff7d358e2 --- /dev/null +++ b/vendor/github.com/theckman/go-flock/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2015, Tim Heckman +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of linode-netint nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/theckman/go-flock/README.md b/vendor/github.com/theckman/go-flock/README.md new file mode 100644 index 0000000000..8206997177 --- /dev/null +++ b/vendor/github.com/theckman/go-flock/README.md @@ -0,0 +1,36 @@ +# go-flock +[![TravisCI Build Status](https://img.shields.io/travis/theckman/go-flock/master.svg?style=flat)](https://travis-ci.org/theckman/go-flock) +[![GoDoc](https://img.shields.io/badge/godoc-go--flock-blue.svg?style=flat)](https://godoc.org/github.com/theckman/go-flock) +[![License](https://img.shields.io/badge/license-BSD_3--Clause-brightgreen.svg?style=flat)](https://github.com/theckman/go-flock/blob/master/LICENSE) + +`flock` implements a thread-safe sync.Locker interface for file locking. It also +includes a non-blocking TryLock() function to allow locking without blocking execution. + +## License +`flock` is released under the BSD 3-Clause License. See the `LICENSE` file for more details. + +## Intsallation +``` +go get -u github.com/theckman/go-flock +``` + +## Usage +```Go +import "github.com/theckman/go-flock" + +fileLock := flock.NewFlock("/var/lock/go-lock.lock") + +locked, err := fileLock.TryLock() + +if err != nil { + // handle locking error +} + +if locked { + // do work + fileLock.Unlock() +} +``` + +For more detailed usage information take a look at the package API docs on +[GoDoc](https://godoc.org/github.com/theckman/go-flock). diff --git a/vendor/github.com/theckman/go-flock/flock.go b/vendor/github.com/theckman/go-flock/flock.go new file mode 100644 index 0000000000..2582077ffc --- /dev/null +++ b/vendor/github.com/theckman/go-flock/flock.go @@ -0,0 +1,61 @@ +// Copyright 2015 Tim Heckman. All rights reserved. +// Use of this source code is governed by the BSD 3-Clause +// license that can be found in the LICENSE file. + +// Package flock implements a thread-safe sync.Locker interface for file locking. +// It also includes a non-blocking TryLock() function to allow locking +// without blocking execution. +// +// Package flock is released under the BSD 3-Clause License. See the LICENSE file +// for more details. +package flock + +import ( + "os" + "sync" +) + +// Flock is the struct type to handle file locking. All fields are unexported, +// with access to some of the fields provided by getter methods (Path() and Locked()). +type Flock struct { + path string + m sync.RWMutex + fh *os.File + l bool +} + +// NewFlock is a function to return a new instance of *Flock. The only parameter +// it takes is the path to the desired lockfile. +func NewFlock(path string) *Flock { + return &Flock{path: path} +} + +// Path is a function to return the path as provided in NewFlock(). +func (f *Flock) Path() string { + return f.path +} + +// Locked is a function to return the current lock state (locked: true, unlocked: false). +func (f *Flock) Locked() bool { + f.m.RLock() + defer f.m.RUnlock() + return f.l +} + +func (f *Flock) String() string { + return f.path +} + +func (f *Flock) setFh() error { + // open a new os.File instance + // create it if it doesn't exist, truncate it if it does exist, open the file read-write + fh, err := os.OpenFile(f.path, os.O_CREATE|os.O_TRUNC|os.O_RDWR, os.FileMode(0600)) + + if err != nil { + return err + } + + // set the filehandle on the struct + f.fh = fh + return nil +} diff --git a/vendor/github.com/theckman/go-flock/flock_example_test.go b/vendor/github.com/theckman/go-flock/flock_example_test.go new file mode 100644 index 0000000000..4a9681c7ed --- /dev/null +++ b/vendor/github.com/theckman/go-flock/flock_example_test.go @@ -0,0 +1,47 @@ +// Copyright 2015 Tim Heckman. All rights reserved. +// Use of this source code is governed by the BSD 3-Clause +// license that can be found in the LICENSE file. + +package flock_test + +import ( + "fmt" + + "github.com/theckman/go-flock" +) + +func ExampleFlock_Locked() { + f := flock.NewFlock("/tmp/go-lock.lock") + f.TryLock() // unchecked errors here + + fmt.Printf("locked: %v\n", f.Locked()) + + f.Unlock() + + fmt.Printf("locked: %v\n", f.Locked()) + // Output: locked: true + // locked: false +} + +func ExampleFlock_TryLock() { + // should probably put these in /var/lock + fileLock := flock.NewFlock("/tmp/go-lock.lock") + + locked, err := fileLock.TryLock() + + if err != nil { + // handle locking error + } + + if locked { + fmt.Printf("path: %s; locked: %v\n", fileLock.Path(), fileLock.Locked()) + + if err := fileLock.Unlock(); err != nil { + // handle unlock error + } + } + + fmt.Printf("path: %s; locked: %v\n", fileLock.Path(), fileLock.Locked()) + // Output: path: /tmp/go-lock.lock; locked: true + // path: /tmp/go-lock.lock; locked: false +} diff --git a/vendor/github.com/theckman/go-flock/flock_test.go b/vendor/github.com/theckman/go-flock/flock_test.go new file mode 100644 index 0000000000..4fb336f179 --- /dev/null +++ b/vendor/github.com/theckman/go-flock/flock_test.go @@ -0,0 +1,151 @@ +// Copyright 2015 Tim Heckman. All rights reserved. +// Use of this source code is governed by the BSD 3-Clause +// license that can be found in the LICENSE file. + +package flock_test + +import ( + "io/ioutil" + "os" + "testing" + + "github.com/theckman/go-flock" + + . "gopkg.in/check.v1" +) + +type TestSuite struct { + path string + flock *flock.Flock +} + +var _ = Suite(&TestSuite{}) + +func Test(t *testing.T) { TestingT(t) } + +func (t *TestSuite) SetUpTest(c *C) { + tmpFile, err := ioutil.TempFile(os.TempDir(), "go-flock-") + c.Assert(err, IsNil) + c.Assert(tmpFile, Not(IsNil)) + + t.path = tmpFile.Name() + + defer os.Remove(t.path) + tmpFile.Close() + + t.flock = flock.NewFlock(t.path) +} + +func (t *TestSuite) TearDownTest(c *C) { + t.flock.Unlock() + os.Remove(t.path) +} + +func (t *TestSuite) TestNewFlock(c *C) { + var f *flock.Flock + + f = flock.NewFlock(t.path) + c.Assert(f, Not(IsNil)) + c.Check(f.Path(), Equals, t.path) + c.Check(f.Locked(), Equals, false) +} + +func (t *TestSuite) TestFlock_Path(c *C) { + var path string + path = t.flock.Path() + c.Check(path, Equals, t.path) +} + +func (t *TestSuite) TestFlock_Locked(c *C) { + var locked bool + locked = t.flock.Locked() + c.Check(locked, Equals, false) +} + +func (t *TestSuite) TestFlock_String(c *C) { + var str string + str = t.flock.String() + c.Assert(str, Equals, t.path) +} + +func (t *TestSuite) TestFlock_TryLock(c *C) { + c.Assert(t.flock.Locked(), Equals, false) + + var locked bool + var err error + + locked, err = t.flock.TryLock() + c.Assert(err, IsNil) + c.Check(locked, Equals, true) + c.Check(t.flock.Locked(), Equals, true) + + locked, err = t.flock.TryLock() + c.Assert(err, IsNil) + c.Check(locked, Equals, true) + + // make sure we just return false with no error in cases + // where we would have been blocked + locked, err = flock.NewFlock(t.path).TryLock() + c.Assert(err, IsNil) + c.Check(locked, Equals, false) +} + +func (t *TestSuite) TestFlock_Unlock(c *C) { + var err error + + err = t.flock.Unlock() + c.Assert(err, IsNil) + + // get a lock for us to unlock + locked, err := t.flock.TryLock() + c.Assert(err, IsNil) + c.Assert(locked, Equals, true) + c.Assert(t.flock.Locked(), Equals, true) + + _, err = os.Stat(t.path) + c.Assert(os.IsNotExist(err), Equals, false) + + err = t.flock.Unlock() + c.Assert(err, IsNil) + c.Check(t.flock.Locked(), Equals, false) +} + +func (t *TestSuite) TestFlock_Lock(c *C) { + c.Assert(t.flock.Locked(), Equals, false) + + var err error + + err = t.flock.Lock() + c.Assert(err, IsNil) + c.Check(t.flock.Locked(), Equals, true) + + // test that the short-circuit works + err = t.flock.Lock() + c.Assert(err, IsNil) + + // + // Test that Lock() is a blocking call + // + ch := make(chan error, 2) + gf := flock.NewFlock(t.path) + defer gf.Unlock() + + go func(ch chan<- error) { + ch <- nil + ch <- gf.Lock() + close(ch) + }(ch) + + errCh, ok := <-ch + c.Assert(ok, Equals, true) + c.Assert(errCh, IsNil) + + err = t.flock.Unlock() + c.Assert(err, IsNil) + + errCh, ok = <-ch + c.Assert(ok, Equals, true) + c.Assert(errCh, IsNil) + c.Check(t.flock.Locked(), Equals, false) + c.Check(gf.Locked(), Equals, true) +} diff --git a/vendor/github.com/theckman/go-flock/flock_unix.go b/vendor/github.com/theckman/go-flock/flock_unix.go new file mode 100644 index 0000000000..8c9a64f073 --- /dev/null +++ b/vendor/github.com/theckman/go-flock/flock_unix.go @@ -0,0 +1,104 @@ +// Copyright 2015 Tim Heckman. All rights reserved. +// Use of this source code is governed by the BSD 3-Clause +// license that can be found in the LICENSE file. + +// +build !windows + +package flock + +import ( + "syscall" +) + +// Lock is a blocking call to try and take the file lock. It will wait until it +// is able to obtain the exclusive file lock. It's recommended that TryLock() be +// used over this function. This function may block the ability to query the +// current Locked() status due to a RW-mutex lock. +// +// If we are already locked, this function short-circuits and returns immediately +// assuming it can take the mutex lock. +func (f *Flock) Lock() error { + f.m.Lock() + defer f.m.Unlock() + + if f.l { + return nil + } + + if f.fh == nil { + if err := f.setFh(); err != nil { + return err + } + } + + if err := syscall.Flock(int(f.fh.Fd()), syscall.LOCK_EX); err != nil { + return err + } + + f.l = true + return nil +} + +// Unlock is a function to unlock the file. This file takes a RW-mutex lock, so +// while it is running the Locked() function will be blocked. +// +// This function short-circuits if we are unlocked already. If not, it calls +// syscall.LOCK_UN on the file and closes the file descriptor It does not remove +// the file from disk. It's up to your application to do. +func (f *Flock) Unlock() error { + f.m.Lock() + defer f.m.Unlock() + + // if we aren't locked or if the lockfile instance is nil + // just return a nil error because we are unlocked + if !f.l || f.fh == nil { + return nil + } + + // mark the file as unlocked + if err := syscall.Flock(int(f.fh.Fd()), syscall.LOCK_UN); err != nil { + return err + } + + f.fh.Close() + + f.l = false + f.fh = nil + + return nil +} + +// TryLock is the preferred function for taking a file lock. This function does +// take a RW-mutex lock before it tries to lock the file, so there is the +// possibility that this function may block for a short time if another goroutine +// is trying to take any action. +// +// The actual file lock is non-blocking. If we are unable to get the exclusive +// file lock, the function will return false instead of waiting for the lock. +// If we get the lock, we also set the *Flock instance as being locked. +func (f *Flock) TryLock() (bool, error) { + f.m.Lock() + defer f.m.Unlock() + + if f.l { + return true, nil + } + + if f.fh == nil { + if err := f.setFh(); err != nil { + return false, err + } + } + + err := syscall.Flock(int(f.fh.Fd()), syscall.LOCK_EX|syscall.LOCK_NB) + + switch err { + case syscall.EWOULDBLOCK: + return false, nil + case nil: + f.l = true + return true, nil + } + + return false, err +} diff --git a/vendor/github.com/theckman/go-flock/flock_winapi.go b/vendor/github.com/theckman/go-flock/flock_winapi.go new file mode 100644 index 0000000000..e4a25769a0 --- /dev/null +++ b/vendor/github.com/theckman/go-flock/flock_winapi.go @@ -0,0 +1,92 @@ +// Copyright 2015 Tim Heckman. All rights reserved. +// Use of this source code is governed by the BSD 3-Clause +// license that can be found in the LICENSE file. + +// +build windows + +package flock + +import ( + "syscall" + "unsafe" +) + +var ( + kernel32, _ = syscall.LoadLibrary("kernel32.dll") + procLockFileEx, _ = syscall.GetProcAddress(kernel32, "LockFileEx") + procUnlockFileEx, _ = syscall.GetProcAddress(kernel32, "UnlockFileEx") +) + +const ( + LOCKFILE_FAIL_IMMEDIATELY = 0x00000001 + LOCKFILE_EXCLUSIVE_LOCK = 0x00000002 +) + +// Do the interface allocations only once for common +// Errno values. +const ( + errnoERROR_IO_PENDING = 997 +) + +var ( + errERROR_IO_PENDING error = syscall.Errno(errnoERROR_IO_PENDING) +) + +// errnoErr returns common boxed Errno values, to prevent +// allocations at runtime. +func errnoErr(e syscall.Errno) error { + switch e { + case 0: + return nil + case errnoERROR_IO_PENDING: + return errERROR_IO_PENDING + } + // TODO: add more here, after collecting data on the common + // error values see on Windows. (perhaps when running + // all.bat?) + return e +} + +func lockFileEx(handle syscall.Handle, flags uint32, reserved uint32, numberOfBytesToLockLow uint32, numberOfBytesToLockHigh uint32, offset *syscall.Overlapped) (success bool, err error) { + r1, _, e1 := syscall.Syscall6( + uintptr(procLockFileEx), + 6, + uintptr(handle), + uintptr(flags), + uintptr(reserved), + uintptr(numberOfBytesToLockLow), + uintptr(numberOfBytesToLockHigh), + uintptr(unsafe.Pointer(offset))) + + success = r1 == 1 + if !success { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func unlockFileEx(handle syscall.Handle, reserved uint32, numberOfBytesToLockLow uint32, numberOfBytesToLockHigh uint32, offset *syscall.Overlapped) (success bool, err error) { + r1, _, e1 := syscall.Syscall6( + uintptr(procUnlockFileEx), + 5, + uintptr(handle), + uintptr(reserved), + uintptr(numberOfBytesToLockLow), + uintptr(numberOfBytesToLockHigh), + uintptr(unsafe.Pointer(offset)), + 0) + + success = r1 == 1 + if !success { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} diff --git a/vendor/github.com/theckman/go-flock/flock_windows.go b/vendor/github.com/theckman/go-flock/flock_windows.go new file mode 100644 index 0000000000..c3a04e9614 --- /dev/null +++ b/vendor/github.com/theckman/go-flock/flock_windows.go @@ -0,0 +1,100 @@ +// Copyright 2015 Tim Heckman. All rights reserved. +// Use of this source code is governed by the BSD 3-Clause +// license that can be found in the LICENSE file. + +package flock + +import ( + "syscall" +) + +// Lock is a blocking call to try and take the file lock. It will wait until it +// is able to obtain the exclusive file lock. It's recommended that TryLock() be +// used over this function. This function may block the ability to query the +// current Locked() status due to a RW-mutex lock. +// +// If we are already locked, this function short-circuits and returns immediately +// assuming it can take the mutex lock. +func (f *Flock) Lock() error { + f.m.Lock() + defer f.m.Unlock() + + if f.l { + return nil + } + + if f.fh == nil { + if err := f.setFh(); err != nil { + return err + } + } + + if _, err := lockFileEx(syscall.Handle(f.fh.Fd()), LOCKFILE_EXCLUSIVE_LOCK, 0, 1, 0, &syscall.Overlapped{}); err != nil { + return err + } + + f.l = true + return nil +} + +// Unlock is a function to unlock the file. This file takes a RW-mutex lock, so +// while it is running the Locked() function will be blocked. +// +// This function short-circuits if we are unlocked already. If not, it calls +// syscall.LOCK_UN on the file and closes the file descriptor It does not remove +// the file from disk. It's up to your application to do. +func (f *Flock) Unlock() error { + f.m.Lock() + defer f.m.Unlock() + + // if we aren't locked or if the lockfile instance is nil + // just return a nil error because we are unlocked + if !f.l || f.fh == nil { + return nil + } + + // mark the file as unlocked + if _, err := unlockFileEx(syscall.Handle(f.fh.Fd()), 0, 1, 0, &syscall.Overlapped{}); err != nil { + return err + } + + f.fh.Close() + + f.l = false + f.fh = nil + + return nil +} + +// TryLock is the preferred function for taking a file lock. This function does +// take a RW-mutex lock before it tries to lock the file, so there is the +// possibility that this function may block for a short time if another goroutine +// is trying to take any action. +// +// The actual file lock is non-blocking. If we are unable to get the exclusive +// file lock, the function will return false instead of waiting for the lock. +// If we get the lock, we also set the *Flock instance as being locked. +func (f *Flock) TryLock() (bool, error) { + f.m.Lock() + defer f.m.Unlock() + + if f.l { + return true, nil + } + + if f.fh == nil { + if err := f.setFh(); err != nil { + return false, err + } + } + + _, err := lockFileEx(syscall.Handle(f.fh.Fd()), LOCKFILE_EXCLUSIVE_LOCK|LOCKFILE_FAIL_IMMEDIATELY, 0, 1, 0, &syscall.Overlapped{}) + + switch err { + case nil: + f.l = true + return true, nil + } + + return false, err +}