Skip to content

Commit

Permalink
lockfile: add functions for non blocking lock
Browse files Browse the repository at this point in the history
extend the public API to allow a non blocking usage.

Signed-off-by: Giuseppe Scrivano <[email protected]>
  • Loading branch information
giuseppe committed May 2, 2024
1 parent f151b89 commit acdb1ae
Show file tree
Hide file tree
Showing 4 changed files with 79 additions and 6 deletions.
48 changes: 47 additions & 1 deletion pkg/lockfile/lockfile.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,20 @@ func (l *LockFile) RLock() {
l.lock(readLock)
}

// TryLock attempts to lock the lockfile as a writer. Panic if the lock is a read-only one.
func (l *LockFile) TryLock() error {
if l.ro {
panic("can't take write lock on read-only lock file")
} else {
return l.tryLock(writeLock)
}
}

// TryRLock attempts to lock the lockfile as a reader.
func (l *LockFile) TryRLock() error {
return l.tryLock(readLock)
}

// Unlock unlocks the lockfile.
func (l *LockFile) Unlock() {
l.stateMutex.Lock()
Expand Down Expand Up @@ -401,9 +415,41 @@ func (l *LockFile) lock(lType lockType) {
// Optimization: only use the (expensive) syscall when
// the counter is 0. In this case, we're either the first
// reader lock or a writer lock.
lockHandle(l.fd, lType)
lockHandle(l.fd, lType, false)
}
l.lockType = lType
l.locked = true
l.counter++
}

// lock locks the lockfile via syscall based on the specified type and
// command.
func (l *LockFile) tryLock(lType lockType) error {
if lType == readLock {
l.rwMutex.RLock()
} else {
l.rwMutex.Lock()
}
l.stateMutex.Lock()
defer l.stateMutex.Unlock()
if l.counter == 0 {
// If we're the first reference on the lock, we need to open the file again.
fd, err := openLock(l.file, l.ro)
if err != nil {
return err
}
l.fd = fd

// Optimization: only use the (expensive) syscall when
// the counter is 0. In this case, we're either the first
// reader lock or a writer lock.
if err = lockHandle(l.fd, lType, true); err != nil {
closeHandle(fd)
return err
}
}
l.lockType = lType
l.locked = true
l.counter++
return nil
}
8 changes: 6 additions & 2 deletions pkg/lockfile/lockfile_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,9 @@ func TestLockfileName(t *testing.T) {

assert.NotEmpty(t, l.name, "lockfile name should be recorded correctly")

l.Lock()
for l.TryLock() != nil {
time.Sleep(10 * time.Millisecond)
}
l.AssertLocked()
l.AssertLockedForWriting()
l.Unlock()
Expand All @@ -226,7 +228,9 @@ func TestLockfileRead(t *testing.T) {
require.Nil(t, err, "error getting temporary lock file")
defer os.Remove(l.name)

l.RLock()
for l.TryRLock() != nil {
time.Sleep(10 * time.Millisecond)
}
l.AssertLocked()
assert.Panics(t, l.AssertLockedForWriting)
l.Unlock()
Expand Down
16 changes: 14 additions & 2 deletions pkg/lockfile/lockfile_unix.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ func openHandle(path string, mode int) (fileHandle, error) {
return fileHandle(fd), err
}

func lockHandle(fd fileHandle, lType lockType) {
func lockHandle(fd fileHandle, lType lockType, nonblocking bool) error {
fType := unix.F_RDLCK
if lType != readLock {
fType = unix.F_WRLCK
Expand All @@ -85,11 +85,23 @@ func lockHandle(fd fileHandle, lType lockType) {
Start: 0,
Len: 0,
}
for unix.FcntlFlock(uintptr(fd), unix.F_SETLKW, &lk) != nil {
for {
cmd := unix.F_SETLKW
if nonblocking {
cmd = unix.F_SETLK
}
err := unix.FcntlFlock(uintptr(fd), cmd, &lk)
if err == nil || nonblocking {
return err
}
time.Sleep(10 * time.Millisecond)
}
}

func unlockAndCloseHandle(fd fileHandle) {
unix.Close(int(fd))
}

func closeHandle(fd fileHandle) {
unix.Close(int(fd))
}
13 changes: 12 additions & 1 deletion pkg/lockfile/lockfile_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,19 +81,30 @@ func openHandle(path string, mode int) (fileHandle, error) {
return fileHandle(fd), err
}

func lockHandle(fd fileHandle, lType lockType) {
func lockHandle(fd fileHandle, lType lockType, nonblocking bool) error {
flags := 0
if lType != readLock {
flags = windows.LOCKFILE_EXCLUSIVE_LOCK
}
if nonblocking {
flags |= windows.LOCKFILE_FAIL_IMMEDIATELY
}
ol := new(windows.Overlapped)
if err := windows.LockFileEx(windows.Handle(fd), uint32(flags), reserved, allBytes, allBytes, ol); err != nil {
if nonblocking {
return err
}
panic(err)
}
return nil
}

func unlockAndCloseHandle(fd fileHandle) {
ol := new(windows.Overlapped)
windows.UnlockFileEx(windows.Handle(fd), reserved, allBytes, allBytes, ol)
closeHandle(fd)
}

func closeHandle(fd fileHandle) {
windows.Close(windows.Handle(fd))
}

0 comments on commit acdb1ae

Please sign in to comment.