Skip to content

fix: thread-safe cache writes and feature update handling #114

Open
vazarkevych wants to merge 1 commit into
growthbook:mainfrom
vazarkevych:fix/thread-safe-cache-writes
Open

fix: thread-safe cache writes and feature update handling #114
vazarkevych wants to merge 1 commit into
growthbook:mainfrom
vazarkevych:fix/thread-safe-cache-writes

Conversation

@vazarkevych
Copy link
Copy Markdown
Collaborator

Problem

Several race conditions existed in cache and feature update handling:

  • InMemoryFeatureCache had no locking — concurrent reads/writes could corrupt cache entries
  • FeatureRepository.load_features and load_features_async had no double-checked locking — multiple threads/coroutines could trigger redundant HTTP fetches simultaneously
  • _feature_update_callbacks list was mutated and iterated without a lock — concurrent add/remove/notify could raise RuntimeError: list changed size during iteration
  • _sticky_bucket_cache_lock was a boolean flag instead of a real lock — the spin-loop was not thread-safe and silently returned {} when the "lock" was held
  • FeatureCache.get_current_state returned a mutable reference to savedGroups instead of a copy
  • GrowthBook.load_features_async called save_in_cache with wrong cache key (client_key instead of api_host::client_key), making the cached value unreachable
  • _features_event_handler had the same incorrect cache key

Changes

  • InMemoryFeatureCache: added threading.Lock to get, set, clear
  • FeatureRepository: added _load_lock and _async_load_lock with double-checked locking pattern in load_features and load_features_async
  • _feature_update_callbacks: protected add/remove with _refresh_lock; _notify copies the list under the lock and iterates outside to prevent deadlocks from slow callbacks
  • _sticky_bucket_cache_lock: replaced boolean spin-lock with asyncio.Lock(); simplified _refresh_sticky_buckets
  • FeatureCache.get_current_state: returns dict() copy of savedGroups
  • GrowthBook.load_features_async: removed redundant save_in_cache call (already handled by FeatureRepository)
  • _features_event_handler: fixed cache key to api_host::client_key; changed return None to return

@vazarkevych vazarkevych requested a review from madhuchavva April 23, 2026 14:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant