diff --git a/pkg/evolution/store.go b/pkg/evolution/store.go index d8411076..3ab7af4d 100644 --- a/pkg/evolution/store.go +++ b/pkg/evolution/store.go @@ -554,19 +554,20 @@ func isInvalidJSON(err error) bool { } func lockStoreFile(path string) func() { - actual, _ := storeFileLocks.LoadOrStore(path, &sync.Mutex{}) - mu, ok := actual.(*sync.Mutex) - if !ok { - // Delete the corrupted entry so that the next LoadOrStore - // atomically creates a fresh mutex. Multiple goroutines may - // race to Delete, but LoadOrStore ensures they all converge - // on the same mutex before entering the critical section. - storeFileLocks.Delete(path) - actual, _ = storeFileLocks.LoadOrStore(path, &sync.Mutex{}) - mu = actual.(*sync.Mutex) + for { + actual, _ := storeFileLocks.LoadOrStore(path, &sync.Mutex{}) + mu, ok := actual.(*sync.Mutex) + if !ok || mu == nil { + // Corrupted entry (wrong type or nil *sync.Mutex). + // Atomically swap in a fresh mutex via CompareAndSwap. + // If CAS fails, another goroutine already replaced it — + // just retry the loop to pick up the valid entry. + storeFileLocks.CompareAndSwap(path, actual, &sync.Mutex{}) + continue + } + mu.Lock() + return mu.Unlock } - mu.Lock() - return mu.Unlock } func (s *Store) profilePath(workspaceID, skillName string) (string, error) {