fix: add per-hash mutex to prevent concurrent CAS writes
StoreFile now uses a per-hash sync.Mutex to prevent race conditions when multiple workers (launcher fetcher or parallel uploads) write the same file simultaneously. Duplicate writes are idempotent — if another goroutine stored the file while we waited, return the existing hash without re-writing.
This commit is contained in:
@@ -1,8 +1,12 @@
|
||||
package cas
|
||||
|
||||
import (
|
||||
"crypto/sha1"
|
||||
"encoding/hex"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
)
|
||||
|
||||
@@ -68,6 +72,45 @@ func TestStoreFile_Duplicate(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestStoreFile_ConcurrentSameHash(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
data := []byte("concurrent write test")
|
||||
|
||||
const workers = 10
|
||||
var success int64
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(workers)
|
||||
|
||||
for i := 0; i < workers; i++ {
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
hash, err := StoreFile(dir, data)
|
||||
if err != nil {
|
||||
t.Errorf("StoreFile failed: %v", err)
|
||||
return
|
||||
}
|
||||
if len(hash) != 40 {
|
||||
t.Errorf("invalid hash length: %d", len(hash))
|
||||
return
|
||||
}
|
||||
atomic.AddInt64(&success, 1)
|
||||
}()
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
|
||||
if success != workers {
|
||||
t.Errorf("expected %d successes, got %d", workers, success)
|
||||
}
|
||||
|
||||
// All goroutines must produce the same hash for identical data.
|
||||
h := sha1.Sum(data)
|
||||
hash := hex.EncodeToString(h[:])
|
||||
if !FileExists(dir, hash) {
|
||||
t.Error("file not found after concurrent writes")
|
||||
}
|
||||
}
|
||||
|
||||
func TestFileExists(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
data := []byte("test data")
|
||||
|
||||
Reference in New Issue
Block a user