diff --git a/storage/deletion/queue_test.go b/storage/deletion/queue_test.go index 90b5ba91..c6230f5a 100644 --- a/storage/deletion/queue_test.go +++ b/storage/deletion/queue_test.go @@ -174,12 +174,11 @@ func TestQueue_BackgroundProcessing(t *testing.T) { err = queue.Add(testFile) require.NoError(t, err) - // Wait for background processing - time.Sleep(200 * time.Millisecond) - - // Verify file is deleted - _, err = os.Stat(testFile) - require.True(t, os.IsNotExist(err), "file should be deleted by background processing") + // Wait for background processing to delete the file. + require.Eventually(t, func() bool { + _, err := os.Stat(testFile) + return os.IsNotExist(err) + }, 500*time.Millisecond, 10*time.Millisecond, "file should be deleted by background processing") } func TestQueue_LockedFile(t *testing.T) { diff --git a/storage/fd/filelockmanager.go b/storage/fd/filelockmanager.go index eeda6b9b..f2b7ad30 100644 --- a/storage/fd/filelockmanager.go +++ b/storage/fd/filelockmanager.go @@ -46,7 +46,15 @@ func GetFileLockManager() *FileLockManager { // access to writers (Write/Delete), which is exactly the behaviour we need for // raw files. func (flm *FileLockManager) GetFileLock(path string) *sync.RWMutex { - lock, _ := flm.fileLocks.LoadOrStore(path, &sync.RWMutex{}) + if lock, ok := flm.fileLocks.Load(path); ok { + return lock.(*sync.RWMutex) + } + + newLock := &sync.RWMutex{} + lock, loaded := flm.fileLocks.LoadOrStore(path, newLock) + if !loaded { + return newLock + } return lock.(*sync.RWMutex) } diff --git a/storage/fd/filelockmanager_test.go b/storage/fd/filelockmanager_test.go index bb7b87c4..97b0f45c 100644 --- a/storage/fd/filelockmanager_test.go +++ b/storage/fd/filelockmanager_test.go @@ -55,6 +55,41 @@ func TestFileLockManager_GetFileLock(t *testing.T) { } } +func TestFileLockManager_ConcurrentMissUsesOneLock(t *testing.T) { + const ( + path = "/test/file.txt" + workers = 100 + ) + + manager := &FileLockManager{} + locks := make([]*sync.RWMutex, workers) + start := make(chan struct{}) + + var wg sync.WaitGroup + wg.Add(workers) + for i := range workers { + go func() { + defer wg.Done() + <-start + locks[i] = manager.GetFileLock(path) + }() + } + close(start) + wg.Wait() + + if locks[0] == nil { + t.Fatal("GetFileLock returned nil") + } + if !manager.HasLock(path) { + t.Fatal("GetFileLock did not retain the lock") + } + for i, lock := range locks { + if lock != locks[0] { + t.Fatalf("GetFileLock returned a different lock to worker %d", i) + } + } +} + func TestFileLockManager_RemoveFileLock(t *testing.T) { // Reset singleton for testing lockManagerOnce = sync.Once{}