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:

  1. Evaluation acquires reference to provider
  2. Shutdown concurrently replaces provider with NoopProvider
  3. In-flight evaluation continues with stale provider reference
  4. 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 evalMutex field 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

Test Results

All 18 new tests passing Panic detection validates no panics in normal operation Race condition tests validate graceful draining prevents nil pointer dereference Pipeline: success (276 seconds)

Impact

Production-safe — impossible to trigger panic in normal operation No breaking changes to API No performance impact — RWMutex overhead is nanosecond scale Improved code maintainability — duplication eliminated with generics

Edited by Elliot Forbes

Merge request reports

Loading
Loading