fix(evolution): use CompareAndSwap for atomic lockStoreFile repair

Replace the Delete+LoadOrStore pattern with CompareAndSwap to
atomically swap in a fresh mutex when a corrupted entry is detected.

Changes:
- Use a retry loop with CAS instead of Delete+LoadOrStore, avoiding
  the race where two goroutines could create different mutexes
- Add nil check: a nil *sync.Mutex passes the type assertion but
  panics on Lock() — now handled by the CAS recovery path
- Use continue-loop retry instead of unchecked second assertion

CompareAndSwap guarantees that when multiple goroutines detect a
corrupted entry, they converge on the same replacement mutex.
This commit is contained in:
程智超0668000959 2026-06-12 11:25:14 +08:00
parent f5add27d39
commit bbdf746bcb

View file

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