mirror of
https://github.com/lukaszraczylo/graphql-monitoring-proxy.git
synced 2026-10-16 06:07:54 +00:00
fix(memory, connection): two data-race/counter defects
1) connection_pool.go: GetConnectionStats read lastRecoveryAttempt (a multi-word time.Time) without any lock while checkAndRecover wrote it under recoveryMutex -> data race on live admin API endpoints. Snapshot it under recoveryMutex (the writer's lock). Regression test TestGetConnectionStatsRace drives the read path concurrent with the locked write under -race. 2) cache/memory memory.go: Cache.Get expired path used an unconditional Delete + atomic decrement, so concurrent Gets on the same expired entry each decremented entryCount/memoryUsage -> permanent negative drift. Use LoadAndDelete-with-guard (as evictOldest/evictToFreeMemory already do) so only the goroutine that actually removes the entry decrements. Regression test TestMemoryCacheConcurrentGetOnExpiredEntry_CountersConsistent.
This commit is contained in:
+30
@@ -1,6 +1,7 @@
|
||||
package libpack_cache_memory
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -88,3 +89,32 @@ func TestMemoryCacheCleanExpiredEntries(t *testing.T) {
|
||||
// Count should be 1 now (only the non-expired entry)
|
||||
assert.Equal(t, int64(1), cache.CountQueries(), "Expected count to be 1 after cleaning expired entries")
|
||||
}
|
||||
|
||||
func TestMemoryCacheConcurrentGetOnExpiredEntry_CountersConsistent(t *testing.T) {
|
||||
// Regression: concurrent Gets on the same expired entry must decrement
|
||||
// entryCount exactly once. Previously Get used an unconditional Delete +
|
||||
// decrement, so N concurrent Gets drove entryCount negative.
|
||||
cache := NewWithSize(DefaultTestExpiration, DefaultMaxMemorySize, DefaultMaxCacheSize)
|
||||
|
||||
cache.Set("k", []byte("v"), 10*time.Millisecond)
|
||||
time.Sleep(50 * time.Millisecond) // let it expire
|
||||
|
||||
var wg sync.WaitGroup
|
||||
for range 32 {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
if _, ok := cache.Get("k"); ok {
|
||||
t.Error("expired entry should not be returned")
|
||||
}
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
if got := cache.CountQueries(); got != 0 {
|
||||
t.Fatalf("entryCount = %d after concurrent expired gets, want 0", got)
|
||||
}
|
||||
if got := cache.GetMemoryUsage(); got != 0 {
|
||||
t.Fatalf("memoryUsage = %d after concurrent expired gets, want 0", got)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user