Ensure only a single instance of after-lock runs at a time

This commit is contained in:
2020-08-20 15:57:17 +02:00
parent 8b89bcb3f6
commit e6458aba0b
3 changed files with 151 additions and 0 deletions

49
lockfile/lockfile.go Normal file
View File

@ -0,0 +1,49 @@
package lockfile
import (
"errors"
"os"
"syscall"
)
type Lockfile struct {
path string
fd int
}
func New(path string) *Lockfile {
return &Lockfile{path: path}
}
func (lf *Lockfile) Lock() error {
fd, err := syscall.Open(lf.path, syscall.O_CREAT | syscall.O_RDWR, 0600)
if err != nil {
return err
}
lf.fd = fd
err = syscall.Flock(lf.fd, syscall.LOCK_EX | syscall.LOCK_NB)
if err != nil {
return err
}
return nil
}
func (lf *Lockfile) Unlock() error {
if lf.fd == 0 {
panic(errors.New("Lockfile was not locked"))
}
err := syscall.Flock(lf.fd, syscall.LOCK_UN)
if err != nil {
return err
}
err = syscall.Close(lf.fd)
if err != nil {
return err
}
os.Remove(lf.path)
if err != nil {
return err
}
return nil
}

71
lockfile/lockfile_test.go Normal file
View File

@ -0,0 +1,71 @@
package lockfile_test
import (
"io/ioutil"
"os"
"testing"
"after-lock/lockfile"
)
func tmpFile() string {
tf, err := ioutil.TempFile("", "lockfile_test*")
if err != nil {
panic(err)
}
return tf.Name()
}
func TestNotLockedUnlockPanics(t *testing.T) {
defer func() {
if r := recover(); r == nil {
t.Errorf("Unlock() did not panic")
}
}()
tf := tmpFile()
defer os.Remove(tf)
err := lockfile.New(tf).Unlock()
if err != nil {
panic(err)
}
}
func TestSecondLockFails(t *testing.T) {
tf := tmpFile()
defer os.Remove(tf)
fl1 := lockfile.New(tf)
err := fl1.Lock()
if err != nil {
panic(err)
}
defer fl1.Unlock()
fl2 := lockfile.New(tf)
err = fl2.Lock()
if err == nil {
t.Errorf("Second Lock() did not fail")
}
}
func TestLockUnlock(t *testing.T) {
tf := tmpFile()
defer os.Remove(tf)
fl := lockfile.New(tf)
err := fl.Lock()
if err != nil {
panic(err)
}
err = fl.Unlock()
if err != nil {
panic(err)
}
if _, err := os.Stat(tf); !os.IsNotExist(err) {
t.Errorf("Lockfile %s still exists", tf)
}
}