package actor import "sync" // keyedMutex provides one logical mutex per key (grain id). It is the // mechanism that enforces the core actor guarantee: a single grain only ever // processes one message (spawn / mutation / read) at a time, while distinct // grains run fully in parallel. // // Locks are reference counted and removed once no caller holds or waits on // them, so memory stays proportional to in-flight grains rather than the total // number of grains ever touched. type keyedMutex struct { mu sync.Mutex locks map[uint64]*keyedMutexEntry } type keyedMutexEntry struct { mu sync.Mutex refs int } func newKeyedMutex() *keyedMutex { return &keyedMutex{locks: make(map[uint64]*keyedMutexEntry)} } // lock acquires the mutex for id and returns an unlock function. The returned // function must be called exactly once. func (k *keyedMutex) lock(id uint64) func() { k.mu.Lock() entry, ok := k.locks[id] if !ok { entry = &keyedMutexEntry{} k.locks[id] = entry } entry.refs++ k.mu.Unlock() entry.mu.Lock() return func() { entry.mu.Unlock() k.mu.Lock() entry.refs-- if entry.refs == 0 { delete(k.locks, id) } k.mu.Unlock() } }