11import atexit
2+ import inspect
23import json
34import logging
45import os
56import sys
7+ import threading
68import warnings
79import weakref
810from datetime import datetime , timedelta , timezone
1113
1214from typing_extensions import Unpack
1315
16+ from posthog ._async_utils import _BackgroundEventLoopRunner
1417from posthog .args import ID_TYPES , ExceptionArg , OptionalCaptureArgs , OptionalSetArgs
1518from posthog .consumer import Consumer
1619from posthog .contexts import (
@@ -305,6 +308,10 @@ def __init__(
305308 self .flag_definition_version = 0
306309 self ._flags_etag : Optional [str ] = None
307310 self ._flag_definition_cache_provider = flag_definition_cache_provider
311+ self ._flag_definition_cache_provider_async_runner : Optional [
312+ _BackgroundEventLoopRunner
313+ ] = None
314+ self ._flag_definition_cache_provider_async_runner_lock = threading .Lock ()
308315 self .disabled = disabled or not self .api_key
309316 self .disable_geoip = disable_geoip
310317 self .historical_migration = historical_migration
@@ -1246,6 +1253,10 @@ def _reinit_after_fork(self):
12461253 else :
12471254 self .poller = None
12481255
1256+ # Async runner threads do not survive fork(); recreate lazily on next async cache call.
1257+ self ._flag_definition_cache_provider_async_runner = None
1258+ self ._flag_definition_cache_provider_async_runner_lock = threading .Lock ()
1259+
12491260 # If using Redis cache, we must reinitialize to get a fresh connection (fork-safe).
12501261 # If using Memory cache, we keep it as-is to benefit from the inherited warm cache.
12511262 if isinstance (self .flag_cache , RedisFlagCache ):
@@ -1373,11 +1384,7 @@ def join(self):
13731384 self .poller .stop ()
13741385
13751386 # Shutdown the cache provider (release locks, cleanup)
1376- if self ._flag_definition_cache_provider :
1377- try :
1378- self ._flag_definition_cache_provider .shutdown ()
1379- except Exception as e :
1380- self .log .error (f"[FEATURE FLAGS] Cache provider shutdown error: { e } " )
1387+ self ._shutdown_flag_definition_cache_provider ()
13811388
13821389 def shutdown (self ):
13831390 """
@@ -1394,6 +1401,33 @@ def shutdown(self):
13941401 if self .exception_capture :
13951402 self .exception_capture .close ()
13961403
1404+ def _resolve_flag_definition_cache_provider_result (self , result ):
1405+ if not inspect .isawaitable (result ):
1406+ return result
1407+
1408+ with self ._flag_definition_cache_provider_async_runner_lock :
1409+ if self ._flag_definition_cache_provider_async_runner is None :
1410+ self ._flag_definition_cache_provider_async_runner = (
1411+ _BackgroundEventLoopRunner ()
1412+ )
1413+ return self ._flag_definition_cache_provider_async_runner .run (result )
1414+
1415+ def _shutdown_flag_definition_cache_provider (self ):
1416+ if not self ._flag_definition_cache_provider :
1417+ return
1418+
1419+ try :
1420+ self ._resolve_flag_definition_cache_provider_result (
1421+ self ._flag_definition_cache_provider .shutdown ()
1422+ )
1423+ except Exception as e :
1424+ self .log .error (f"[FEATURE FLAGS] Cache provider shutdown error: { e } " )
1425+ finally :
1426+ with self ._flag_definition_cache_provider_async_runner_lock :
1427+ if self ._flag_definition_cache_provider_async_runner :
1428+ self ._flag_definition_cache_provider_async_runner .close ()
1429+ self ._flag_definition_cache_provider_async_runner = None
1430+
13971431 def _update_flag_state (
13981432 self , data : FlagDefinitionCacheData , old_flags_by_key : Optional [dict ] = None
13991433 ) -> None :
@@ -1416,7 +1450,7 @@ def _load_feature_flags(self):
14161450 should_fetch = True
14171451 if self ._flag_definition_cache_provider :
14181452 try :
1419- should_fetch = (
1453+ should_fetch = self . _resolve_flag_definition_cache_provider_result (
14201454 self ._flag_definition_cache_provider .should_fetch_flag_definitions ()
14211455 )
14221456 except Exception as e :
@@ -1429,7 +1463,7 @@ def _load_feature_flags(self):
14291463 # If not fetching, try to get from cache
14301464 if not should_fetch and self ._flag_definition_cache_provider :
14311465 try :
1432- cached_data = (
1466+ cached_data = self . _resolve_flag_definition_cache_provider_result (
14331467 self ._flag_definition_cache_provider .get_flag_definitions ()
14341468 )
14351469 if cached_data :
@@ -1500,12 +1534,14 @@ def _fetch_feature_flags_from_api(self):
15001534 # Store in external cache if provider is configured
15011535 if self ._flag_definition_cache_provider :
15021536 try :
1503- self ._flag_definition_cache_provider .on_flag_definitions_received (
1504- {
1505- "flags" : self .feature_flags or [],
1506- "group_type_mapping" : self .group_type_mapping or {},
1507- "cohorts" : self .cohorts or {},
1508- }
1537+ self ._resolve_flag_definition_cache_provider_result (
1538+ self ._flag_definition_cache_provider .on_flag_definitions_received (
1539+ {
1540+ "flags" : self .feature_flags or [],
1541+ "group_type_mapping" : self .group_type_mapping or {},
1542+ "cohorts" : self .cohorts or {},
1543+ }
1544+ )
15091545 )
15101546 except Exception as e :
15111547 self .log .error (f"[FEATURE FLAGS] Cache provider store error: { e } " )
0 commit comments