@@ -132,6 +132,41 @@ def _update_subs_gauge():
132132_market_data_desired_refcount : dict = {}
133133_market_data_last_warn_ts : float = 0.0
134134
135+ def _get_market_data_adapter (app_obj : FastAPI | None = None ):
136+ """Return the canonical MarketData adapter instance (app.state first, then module global)."""
137+ try :
138+ if app_obj is not None :
139+ state = getattr (app_obj , "state" , None )
140+ if state is not None :
141+ adapter = getattr (state , "market_data_adapter" , None )
142+ if adapter is not None :
143+ return adapter
144+ except Exception :
145+ pass
146+ return globals ().get ("_market_data_adapter" , None )
147+
148+
149+ async def _reconcile_subscriptions (adapter , reconcile_result : dict , state ) -> None :
150+ """Best-effort subscribe/unsubscribe executor. Safe when adapter is unavailable."""
151+ if adapter is None :
152+ logger .warning ("Reconcile: market-data adapter unavailable, skipping subscribe/unsubscribe this interval" )
153+ return
154+
155+ for tk in reconcile_result .get ("to_subscribe" , set ()):
156+ try :
157+ await adapter .subscribe (tk )
158+ logger .info ("Reconcile: requested subscribe %s" , str (tk )[:24 ])
159+ except Exception :
160+ logger .exception ("Reconcile: subscribe failed for %s" , tk )
161+
162+ for tk in reconcile_result .get ("to_unsubscribe" , set ()):
163+ try :
164+ await adapter .unsubscribe (tk )
165+ logger .info ("Reconcile: requested unsubscribe %s" , str (tk )[:24 ])
166+ state .missing_count .pop (tk , None )
167+ except Exception :
168+ logger .exception ("Reconcile: unsubscribe failed for %s" , tk )
169+
135170
136171async def market_data_event_consumer ():
137172 """
@@ -290,6 +325,12 @@ async def startup_event():
290325 # Initialize MarketDataAdapter (if enabled). Start idempotently and register event consumer task.
291326 global _market_data_adapter , _market_data_tasks
292327 try :
328+ # Always expose canonical adapter handle on app.state (can be None)
329+ try :
330+ app .state .market_data_adapter = _get_market_data_adapter (app )
331+ except Exception :
332+ logger .debug ("Could not initialize app.state.market_data_adapter" )
333+
293334 if getattr (settings , "MARKET_DATA_WS_ENABLED" , True ):
294335 if _market_data_adapter is None :
295336 if MarketDataAdapterClass is None :
@@ -309,7 +350,7 @@ async def startup_event():
309350 # bind adapter and telemetry to app.state for admin/debug handlers
310351 try :
311352 from src .market_data .telemetry import telemetry as _telemetry
312- app .state .market_data_adapter = _market_data_adapter
353+ app .state .market_data_adapter = _get_market_data_adapter ( app )
313354 app .state .market_data_telemetry = _telemetry
314355 import os
315356 app .state .market_data_pid = os .getpid ()
@@ -406,29 +447,22 @@ async def reconcile_loop(interval: int = 30):
406447 globals ()["_market_data_desired_refcount" ] = desired_refcount
407448 except Exception :
408449 logger .debug ("failed to set module-level desired_refcount" )
450+ adapter = _get_market_data_adapter (app )
451+ if adapter is None :
452+ logger .warning ("Reconcile: adapter unavailable; skipping actions for this interval" )
453+ await asyncio .sleep (interval )
454+ continue
455+
409456 # compute actions
410457 try :
411458 missing_threshold = getattr (settings , "MARKET_DATA_RECONCILE_MISSING_THRESHOLD" , 3 )
412- res = reconcile_step (_market_data_adapter , desired_refcount , state , missing_threshold = missing_threshold )
459+ res = reconcile_step (adapter , desired_refcount , state , missing_threshold = missing_threshold )
413460 except Exception :
414461 logger .exception ("Reconcile step failed" )
415462 res = {"to_subscribe" : set (), "to_unsubscribe" : set ()}
416463
417464 # perform subscribe/unsubscribe
418- for tk in res .get ("to_subscribe" , set ()):
419- try :
420- await _market_data_adapter .subscribe (tk )
421- logger .info ("Reconcile: requested subscribe %s" , str (tk )[:24 ])
422- except Exception :
423- logger .exception ("Reconcile: subscribe failed for %s" , tk )
424-
425- for tk in res .get ("to_unsubscribe" , set ()):
426- try :
427- await _market_data_adapter .unsubscribe (tk )
428- logger .info ("Reconcile: requested unsubscribe %s" , str (tk )[:24 ])
429- state .missing_count .pop (tk , None )
430- except Exception :
431- logger .exception ("Reconcile: unsubscribe failed for %s" , tk )
465+ await _reconcile_subscriptions (adapter , res , state )
432466 # update telemetry gauge after reconcile actions
433467 try :
434468 _update_subs_gauge ()
0 commit comments