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