Files
miti99bot/internal/keylock/keylock_test.go
T
tiennm99 676c481def refactor(modules): extract per-subject lock to internal/keylock
Extracted per-subject RWMutex primitive into dedicated internal/keylock
package to allow reuse across game modules. Wordle previously held local
subjectLocks; loldle module (Phase 5c) would duplicate this type without
the extraction. Placing keylock as a peer to storage/telegram (not nested
under modules/) matches its cross-module scope.
2026-05-09 09:56:47 +07:00

74 lines
1.9 KiB
Go

package keylock
import (
"sync"
"sync/atomic"
"testing"
"time"
)
// TestMap_DistinctKeysDoNotBlock asserts the per-key fan-out: two acquires on
// different keys must run in parallel even while one is held. We arrange a
// goroutine that holds key "a" for a beat, then a second goroutine acquires
// "b" — if Acquire blocked across keys, the second goroutine couldn't finish
// before the first releases. We give it a generous timeout to avoid flakes
// on slow CI hardware while still failing if locks are global.
func TestMap_DistinctKeysDoNotBlock(t *testing.T) {
var m Map
holdA := make(chan struct{})
releasedA := make(chan struct{})
bDone := make(chan struct{})
go func() {
release := m.Acquire("a")
close(holdA)
<-time.After(50 * time.Millisecond)
release()
close(releasedA)
}()
go func() {
<-holdA
release := m.Acquire("b")
release()
close(bDone)
}()
select {
case <-bDone:
// Good: b acquired while a was held.
case <-time.After(40 * time.Millisecond):
t.Fatal("Acquire(\"b\") blocked while Acquire(\"a\") was held — keys are not independent")
}
<-releasedA
}
// TestMap_SameKeySerialises asserts mutual exclusion on a shared key under
// concurrent contention. If acquires interleaved, the counter would race.
func TestMap_SameKeySerialises(t *testing.T) {
var m Map
var counter int64
const goroutines = 32
const itersEach = 100
var wg sync.WaitGroup
wg.Add(goroutines)
for i := 0; i < goroutines; i++ {
go func() {
defer wg.Done()
for j := 0; j < itersEach; j++ {
release := m.Acquire("shared")
// Read-modify-write that would race without serialisation.
v := atomic.LoadInt64(&counter)
atomic.StoreInt64(&counter, v+1)
release()
}
}()
}
wg.Wait()
if got := atomic.LoadInt64(&counter); got != int64(goroutines*itersEach) {
t.Errorf("counter = %d, want %d (lost updates → mutex didn't serialise)", got, goroutines*itersEach)
}
}