From 8d6b8696a6cf19d210987032572456626792fe23 Mon Sep 17 00:00:00 2001 From: Konstantin Khlebnikov Date: Wed, 19 Nov 2025 19:27:11 +0100 Subject: [PATCH] Do not block try lock on state mutex TryLock should not block when file lock is held by other process and state lock is held by other coroutine which waits for file lock. Signed-off-by: Konstantin Khlebnikov --- pkg/lockfile/lockfile.go | 6 +++-- pkg/lockfile/lockfile_test.go | 49 +++++++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 2 deletions(-) diff --git a/pkg/lockfile/lockfile.go b/pkg/lockfile/lockfile.go index dfe81c2458..3238defa0b 100644 --- a/pkg/lockfile/lockfile.go +++ b/pkg/lockfile/lockfile.go @@ -417,10 +417,12 @@ func (l *LockFile) tryLock(lType rawfilelock.LockType) error { success = l.rwMutex.TryLock() rwMutexUnlocker = l.rwMutex.Unlock } - if !success { + if !success || !l.stateMutex.TryLock() { + if success { + rwMutexUnlocker() + } return fmt.Errorf("resource temporarily unavailable") } - 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. diff --git a/pkg/lockfile/lockfile_test.go b/pkg/lockfile/lockfile_test.go index 32dd5eb281..ecba214870 100644 --- a/pkg/lockfile/lockfile_test.go +++ b/pkg/lockfile/lockfile_test.go @@ -277,6 +277,55 @@ func TestTryReadLockFile(t *testing.T) { assert.Nil(t, <-errChan) } +func TestTryLockState(t *testing.T) { + l, err := getTempLockfile() + require.NoError(t, err, "creating lock") + defer os.Remove(l.name) + + // Take a write lock somewhere. + cmd, wc, rc, err := subLock(l) + require.NoError(t, err) + _, err = io.Copy(io.Discard, rc) + require.NoError(t, err) + + err = l.TryRLock() + assert.NotNil(t, err) + + // Lock and hold state mutex. + locked := make(chan bool) + go func() { + locked <- false + l.RLock() + locked <- true + l.Unlock() + locked <- false + }() + + assert.False(t, <-locked) + + // Wait state mutex is locked. + for !l.stateMutex.TryLock() { + l.stateMutex.Unlock() + time.Sleep(100 * time.Millisecond) + } + + // Try locks should fail without blocking. + errChan := make(chan error) + go func() { + errChan <- l.TryRLock() + errChan <- l.TryLock() + }() + assert.NotNil(t, <-errChan) + assert.NotNil(t, <-errChan) + + wc.Close() + err = cmd.Wait() + require.NoError(t, err) + + assert.True(t, <-locked) + assert.False(t, <-locked) +} + func TestLockfileRead(t *testing.T) { l, err := getTempLockfile() require.Nil(t, err, "error getting temporary lock file")