From b7898b6bc5d9a3dc97e2b8fb6ab28548617d6004 Mon Sep 17 00:00:00 2001 From: oliverpool Date: Thu, 2 Mar 2017 12:38:12 +0100 Subject: [PATCH 01/10] Add a prefix for the lock hard links To know to which lock the file is referring to --- lockfile.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lockfile.go b/lockfile.go index da00bec..2d956e0 100644 --- a/lockfile.go +++ b/lockfile.go @@ -93,7 +93,7 @@ func (l Lockfile) TryLock() error { panic(ErrNeedAbsPath) } - tmplock, err := ioutil.TempFile(filepath.Dir(name), "") + tmplock, err := ioutil.TempFile(filepath.Dir(name), filepath.Base(name)+".") if err != nil { return err } From 9bf21cb6d9b11403de651b95bfef56c6853ef44b Mon Sep 17 00:00:00 2001 From: Ingo Oeser Date: Fri, 7 Jul 2017 07:59:38 +0200 Subject: [PATCH 02/10] Fix license name in Readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 54ee19c..c35235c 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ Then run LICENSE ------- -BSD +MIT documentation ------------- From ffe4cfad6fcde978e5bf0f580f3c07837a295eae Mon Sep 17 00:00:00 2001 From: Ingo Oeser Date: Thu, 3 Aug 2017 00:16:54 +0200 Subject: [PATCH 03/10] fail properly on unexpected link errors also document the behavior more clearly. Updates #20 --- lockfile.go | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/lockfile.go b/lockfile.go index 2d956e0..af2d84a 100644 --- a/lockfile.go +++ b/lockfile.go @@ -108,8 +108,18 @@ func (l Lockfile) TryLock() error { return err } - // return value intentionally ignored, as ignoring it is part of the algorithm - _ = os.Link(tmplock.Name(), name) + // EEXIST and similiar error codes, caught by os.IsExist, are intentionally ignored, + // as it means that someone was faster creating this link + // and ignoring this kind of error is part of the algorithm. + // The we will probably fail the pid owner check later, if this process is still alive. + // We cannot ignore ALL errors, since failure to support hard links, disk full + // as well as many other errors can happen to a filesystem operation + // and we really want to abort on those. + if err := os.Link(tmplock.Name(), name); err != nil { + if !os.IsExist(err) { + return err + } + } fiTmp, err := os.Lstat(tmplock.Name()) if err != nil { From c849bc080a4fc7732625dcefec5b1d38c05a2a79 Mon Sep 17 00:00:00 2001 From: HeChuan Date: Thu, 7 Jun 2018 17:32:20 +0800 Subject: [PATCH 04/10] add aix system I have test on aix already --- lockfile_unix.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lockfile_unix.go b/lockfile_unix.go index 742b041..d724e70 100644 --- a/lockfile_unix.go +++ b/lockfile_unix.go @@ -1,4 +1,4 @@ -// +build darwin dragonfly freebsd linux nacl netbsd openbsd solaris +// +build darwin dragonfly freebsd linux nacl netbsd openbsd solaris aix package lockfile From edb130adc1955aace841f9a42418ba8796b5abfb Mon Sep 17 00:00:00 2001 From: Kevin Zhou Date: Fri, 24 Jan 2020 00:33:52 -0500 Subject: [PATCH 05/10] Fix typo in comment section in TryLock function --- lockfile.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lockfile.go b/lockfile.go index af2d84a..d42bad3 100644 --- a/lockfile.go +++ b/lockfile.go @@ -111,7 +111,7 @@ func (l Lockfile) TryLock() error { // EEXIST and similiar error codes, caught by os.IsExist, are intentionally ignored, // as it means that someone was faster creating this link // and ignoring this kind of error is part of the algorithm. - // The we will probably fail the pid owner check later, if this process is still alive. + // Then we will probably fail the pid owner check later, if this process is still alive. // We cannot ignore ALL errors, since failure to support hard links, disk full // as well as many other errors can happen to a filesystem operation // and we really want to abort on those. From 52f5e182df6c9e1923fdb4081c17aeed30cb4000 Mon Sep 17 00:00:00 2001 From: Ingo Oeser Date: Sun, 8 Mar 2020 13:22:58 +0100 Subject: [PATCH 06/10] Add go module --- go.mod | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 go.mod diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..5133ff7 --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module github.com/nightlyone/lockfile + +go 1.11 From 93be5571cd55e842d77eb81b440bd823b461d781 Mon Sep 17 00:00:00 2001 From: Ingo Oeser Date: Sun, 8 Mar 2020 14:22:29 +0100 Subject: [PATCH 07/10] Spring cleaning to make linter happy * Fix various speling issues in comments. * Better abstraction for pid file creation. * Improve deletion test output as it was not helpful enough. --- lockfile.go | 55 +++++++++++++++++++++++--------------- lockfile_test.go | 69 ++++++++++++++++++++---------------------------- lockfile_unix.go | 1 + 3 files changed, 63 insertions(+), 62 deletions(-) diff --git a/lockfile.go b/lockfile.go index d42bad3..76bdfbe 100644 --- a/lockfile.go +++ b/lockfile.go @@ -26,7 +26,7 @@ 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") +// fmt.Println("I am a temporary error situation, so wait and retry") // } func (t TemporaryError) Temporary() bool { return true } @@ -45,6 +45,7 @@ func New(path string) (Lockfile, error) { if !filepath.IsAbs(path) { return Lockfile(""), ErrNeedAbsPath } + return Lockfile(path), nil } @@ -63,6 +64,7 @@ func (l Lockfile) GetOwner() (*os.Process, error) { if err != nil { return nil, err } + running, err := isRunning(pid) if err != nil { return nil, err @@ -73,10 +75,11 @@ func (l Lockfile) GetOwner() (*os.Process, error) { if err != nil { return nil, err } + return proc, nil } - return nil, ErrDeadOwner + return nil, ErrDeadOwner } // TryLock tries to own the lock. @@ -93,44 +96,38 @@ func (l Lockfile) TryLock() error { panic(ErrNeedAbsPath) } - tmplock, err := ioutil.TempFile(filepath.Dir(name), filepath.Base(name)+".") + tmplock, cleanup, err := makePidFile(name, os.Getpid()) 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 - } - - // EEXIST and similiar error codes, caught by os.IsExist, are intentionally ignored, + // EEXIST and similar error codes, caught by os.IsExist, are intentionally ignored, // as it means that someone was faster creating this link // and ignoring this kind of error is part of the algorithm. // Then we will probably fail the pid owner check later, if this process is still alive. // We cannot ignore ALL errors, since failure to support hard links, disk full // as well as many other errors can happen to a filesystem operation // and we really want to abort on those. - if err := os.Link(tmplock.Name(), name); err != nil { + if err := os.Link(tmplock, name); err != nil { if !os.IsExist(err) { return err } } - fiTmp, err := os.Lstat(tmplock.Name()) + fiTmp, err := os.Lstat(tmplock) 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 } @@ -148,8 +145,7 @@ func (l Lockfile) TryLock() error { if proc.Pid != os.Getpid() { return ErrBusy } - case ErrDeadOwner, ErrInvalidPid: - // cases we can fix below + case ErrDeadOwner, ErrInvalidPid: // cases we can fix below } // clean stale/invalid lockfile @@ -165,7 +161,7 @@ func (l Lockfile) TryLock() error { return l.TryLock() } -// Unlock a lock again, if we owned it. Returns any error that happend during release of lock. +// Unlock a lock again, if we owned it. Returns any error that happened during release of lock. func (l Lockfile) Unlock() error { proc, err := l.GetOwner() switch err { @@ -189,11 +185,6 @@ func (l Lockfile) Unlock() error { } } -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 @@ -207,5 +198,25 @@ func scanPidLine(content []byte) (int, error) { if pid <= 0 { return 0, ErrInvalidPid } + return pid, nil } + +func makePidFile(name string, pid int) (tmpname string, cleanup func(), err error) { + tmplock, err := ioutil.TempFile(filepath.Dir(name), filepath.Base(name)+".") + if err != nil { + return "", nil, err + } + + cleanup = func() { + _ = tmplock.Close() + _ = os.Remove(tmplock.Name()) + } + + if _, err := io.WriteString(tmplock, fmt.Sprintf("%d\n", pid)); err != nil { + cleanup() // Do cleanup here, so call doesn't have to. + return "", nil, err + } + + return tmplock.Name(), cleanup, nil +} diff --git a/lockfile_test.go b/lockfile_test.go index be2c821..d9176b8 100644 --- a/lockfile_test.go +++ b/lockfile_test.go @@ -16,15 +16,19 @@ func ExampleLockfile() { 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 { + if err = lock.TryLock(); err != nil { fmt.Printf("Cannot lock %q, reason: %v", lock, err) panic(err) // handle properly please! } - defer lock.Unlock() + defer func() { + if err := lock.Unlock(); err != nil { + fmt.Printf("Cannot unlock %q, reason: %v", lock, err) + panic(err) // handle properly please! + } + }() fmt.Println("Do stuff under lock") // Output: Do stuff under lock @@ -61,7 +65,6 @@ func TestBasicLockUnlock(t *testing.T) { 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 @@ -71,7 +74,9 @@ func GetDeadPID() int { if seen[pid] { continue } + seen[pid] = true + running, err := isRunning(pid) if err != nil { fmt.Println("Error checking PID: ", err) @@ -164,6 +169,7 @@ func TestRogueDeletionDeadPid(t *testing.T) { t.Fatal(err) return } + defer os.Remove(path) err = lf.Unlock() @@ -172,12 +178,12 @@ func TestRogueDeletionDeadPid(t *testing.T) { 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) + if _, err := os.Stat(path); err != nil { + if os.IsNotExist(err) { + content, _ := ioutil.ReadFile(path) + t.Fatalf("lockfile %q (%q) should not be deleted by us, if we didn't create it", path, content) } + t.Fatalf("unexpected error %v", err) } } @@ -215,10 +221,12 @@ func TestInvalidPidLeadToReplacedLockfileAndSuccess(t *testing.T) { 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) @@ -250,33 +258,13 @@ func TestScanPidLine(t *testing.T) { 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, - }, + {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 @@ -284,12 +272,13 @@ func TestScanPidLine(t *testing.T) { 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 { + + if want := tc.pid; got != want { t.Errorf("%d: expected pid %d, got %d", step, want, got) } } @@ -299,9 +288,9 @@ func TestScanPidLine(t *testing.T) { if tc.xfail == nil { continue } - want := tc.xfail + _, got := scanPidLine(tc.input) - if got != want { + if want := tc.xfail; got != want { t.Errorf("%d: expected error %v, got %v", step, want, got) } } diff --git a/lockfile_unix.go b/lockfile_unix.go index d724e70..d43e9b5 100644 --- a/lockfile_unix.go +++ b/lockfile_unix.go @@ -16,5 +16,6 @@ func isRunning(pid int) (bool, error) { if err := proc.Signal(syscall.Signal(0)); err != nil { return false, nil } + return true, nil } From d487ed8593d2cb7638cf901ff9fc48f62c9a902f Mon Sep 17 00:00:00 2001 From: Ingo Oeser Date: Sun, 8 Mar 2020 14:27:51 +0100 Subject: [PATCH 08/10] CI on newer Go versions --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 76e5962..4f0af47 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,7 @@ language: go go: - - 1.4.3 - - 1.6.2 + - 1.13 + - 1.14 - tip # Only test commits to production branch and all pull requests From 368fd4d5d6ae6854cf307cf407d5ca77e5fee135 Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Wed, 14 Oct 2020 13:28:25 +0000 Subject: [PATCH 09/10] Add poweron architecture ppc64le to travis build --- .travis.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.travis.yml b/.travis.yml index 4f0af47..81a9f6b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,7 @@ language: go +arch: + - ppc64le + - amd64 go: - 1.13 - 1.14 From bf01bef5587fecae1a6519d302669c701fa41efd Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Thu, 4 Nov 2021 15:07:35 +0000 Subject: [PATCH 10/10] Add renovate.json --- renovate.json | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 renovate.json diff --git a/renovate.json b/renovate.json new file mode 100644 index 0000000..f45d8f1 --- /dev/null +++ b/renovate.json @@ -0,0 +1,5 @@ +{ + "extends": [ + "config:base" + ] +}