Skip to main content
Ryan Mitchell·

Caught a check-then-act race on our session cache and handed me a double-checked lock fix in Go

Diagnose race conditions, deadlocks, and concurrency bugs with systematic analysis tools, code patterns, and thread-safe fix recommendations.

Race Condition Sleuth

You are a concurrency debugging specialist. Analyze the following code for race conditions, deadlocks, and thread-safety issues. **Code Under Analysis:** ``` {{code_snippet}} ``` **Programming Language:** {{programming_language}} **Concurrency Model:** {{concurrency_model}} **Observed Symptoms:** {{observed_symptoms}} **Execution Environment:** {{execution_env}} Perform thorough concurrency analysis: 1. **Race Condition Detection**: Identify all read-write and write-write race conditions 2. **Deadlock Analysis**: Find circular wait conditions and lock ordering issues 3. **Lock Contention**: Identify overly coarse-grained locks causing bottlenecks 4. **Thread-Safety Audit**: Check which shared resources are unprotected 5. **Happens-Before Analysis**: Determine if proper synchronization guarantees ordering 6. **Visibility Issues**: Find places where changes may not be visible across threads 7. **Atomicity Violations**: Operations that should be atomic but are not 8. **Safe Fix Implementation**: Thread-safe code rewrites using: - Fine-grained locking - Lock-free data structures - Actor model isolation - Immutable data patterns - Software transactional memory 9. **Reproduction Strategy**: Stress test to reproduce the concurrency bug reliably 10. **Static Analysis Recommendations**: Tools to automatically catch similar issues Output the corrected code with detailed comments explaining each synchronization decision.

Ergebnisse

The bug is a check-then-act race on the in-memory session cache. Two goroutines read `cache[id]` as missing, both build a session, and one overwrites the other — under load this drops logins intermittently. **Race identified:** `Get` reads the map without a lock; `Set` writes under a lock. A concurrent read during a write is a data race (Go's race detector flags it as read/write on the same `map`). ```go // BEFORE — unsynchronized read, lost-update on miss type SessionCache struct { mu sync.Mutex m map[string]*Session } func (c *SessionCache) Get(id string) *Session { return c.m[id] // RACE: read without holding mu } ``` **Fixed — single-flight load under a lock so only one builder runs per key:** ```go type SessionCache struct { mu sync.RWMutex m map[string]*Session } func (c *SessionCache) GetOrCreate(id string, build func() *Session) *Session { c.mu.RLock() if s, ok := c.m[id]; ok { // fast path: shared read lock c.mu.RUnlock() return s } c.mu.RUnlock() c.mu.Lock() // slow path: exclusive defer c.mu.Unlock() if s, ok := c.m[id]; ok { // re-check after acquiring write lock return s // another goroutine won the race } s := build() c.m[id] = s return s } ``` **Why it works:** the double-check under the write lock guarantees exactly one `build()` per key even when many goroutines miss simultaneously — the happens-before edge is the `Lock`/`Unlock` pair. **Reproduce:** run the test with `go test -race -count=100` firing 200 concurrent `GetOrCreate` calls for one id; pre-fix it reports a data race, post-fix it is clean. **Alternative:** `sync.Map` or `golang.org/x/sync/singleflight` if you'd rather not own the locking.

Modell: Claude Sonnet 4

2 Likes1 SavesScore: 2