Skip to content

go.race_condition

Correctness High Common in Incidents

Detects potential race conditions from concurrent access to shared state without synchronization.

Race conditions cause some of the hardest bugs to find:

  • Intermittent failures - May only happen under specific timing
  • Data corruption - Concurrent writes produce garbage
  • Security vulnerabilities - TOCTOU attacks exploit race windows
  • Impossible to reproduce - Works in testing, fails in production

Go’s race detector catches some at runtime, but static analysis catches them earlier.

// ❌ Before
var counter int
func incrementUnsafe() {
go func() {
counter++ // Race: concurrent read-modify-write
}()
}

Two goroutines incrementing simultaneously might both read the same value and write the same result.

// ✅ After (mutex)
var (
counter int
mu sync.Mutex
)
func incrementSafe() {
go func() {
mu.Lock()
counter++
mu.Unlock()
}()
}
// ✅ After (atomic)
var counter int64
func incrementAtomic() {
go func() {
atomic.AddInt64(&counter, 1)
}()
}
  • Shared variables accessed in goroutines without mutex
  • Map access from multiple goroutines
  • Struct field access without synchronization
  • Closure capturing variables modified concurrently

Unfault can add mutex protection around shared variable access when the pattern allows safe transformation.

// Atomic for simple counters
var count int64
atomic.AddInt64(&count, 1)
val := atomic.LoadInt64(&count)
// Mutex for complex state
type SafeCache struct {
mu sync.RWMutex
data map[string]string
}
func (c *SafeCache) Get(key string) string {
c.mu.RLock()
defer c.mu.RUnlock()
return c.data[key]
}
func (c *SafeCache) Set(key, value string) {
c.mu.Lock()
defer c.mu.Unlock()
c.data[key] = value
}
// sync.Map for simple concurrent maps
var cache sync.Map
cache.Store("key", "value")
val, ok := cache.Load("key")
Terminal window
# Run tests with race detector
go test -race ./...
# Build with race detector
go build -race