Fix featureflag race condition during concurrent evaluations and shutdown
Summary
Makes the featureflag Client panic-proof by implementing graceful draining during shutdown, comprehensive nil guards, and panic recovery around all external operations.
Problem
Production panic in featureflag client's evaluation path caused by race condition:
- Evaluation acquires reference to provider
- Shutdown concurrently replaces provider with NoopProvider
- In-flight evaluation continues with stale provider reference
- Provider's HTTP transport encounters nil pointer dereference
Stack trace: Flipt RoundTripper → Provider.BooleanEvaluation (nil) → Client.BooleanValueDetails
Solution
Three layers of defense:
1. RWMutex-based graceful draining
- Evaluations acquire read lock (RLock) — allows concurrent evaluations
- Shutdown acquires write lock (Lock) — blocks new evals, waits for in-flight ones
- Provider replacement only happens after all evaluations complete
2. Comprehensive nil guards in evaluate function
- Guard nil receiver: returns error if c == nil
- Guard nil context: returns error if ctx == nil
- Guard nil OpenFeature client: returns error if c.of == nil
3. Panic recovery around all external operations
- Deferred panic recovery in evaluate function catches panics during flag evaluation
- Separate panic recovery wrappers around tracer.Start() and span.End()
- Panic recovery around recordSpan() ensures tracing never breaks evaluation
- All panics converted to errors with descriptive messages
4. Code duplication reduction
- Generic
evaluate[T any]helper function consolidates common pattern - BooleanValueDetails and StringValueDetails delegate to evaluate helper
- Reduces ~50 lines of duplicated code while improving maintainability
Changes
client.go:
- Added
sync.RWMutex evalMutexfield to Client - Implemented generic
evaluate[T any]function with:- Mutex synchronization (RLock/RUnlock)
- Comprehensive nil guards
- Panic recovery with defer
- Tracer span creation and management
- Simplified BooleanValueDetails and StringValueDetails to use evaluate helper
- Enhanced Shutdown to acquire Lock before provider replacement
panic_test.go: (NEW)
- 13 comprehensive panic detection tests
- Tests cover: concurrent evaluations, shutdown scenarios, cache access, context cancellation, multiple clients
- Uses atomic.Int32 for thread-safe panic counting
race_condition_test.go: (NEW)
- 5 tests targeting nil pointer race condition
- Tests cover: evaluation during shutdown, rapid shutdown/eval cycles, multiple clients, stress scenarios
- Heavy concurrent load to reliably trigger race conditions