51 lines
1.1 KiB
Go
51 lines
1.1 KiB
Go
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()
|
|
}
|
|
}
|