PINK: fix reconcile 30s deadlock — async def + direct await

Root cause: _run() → pool.submit(asyncio.run, coro).result(30s) created a
new event loop in a thread-pool thread; aiohttp session is main-loop-bound
→ silent deadlock every step cycle. BingX VST is healthy (544ms gather).

Fix: async def reconcile() + await self.backend.refresh_state() in main loop.
pump_venue_events() already handles isawaitable → zero caller changes.
include_history=False (symbol=None skips history anyway).
Tests: 13/13 passing (async contract, 3 fault paths, <2s timing, gather-10).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Codex
2026-06-04 18:46:19 +02:00
parent d7e272e148
commit a9ba407ae2
2 changed files with 273 additions and 2 deletions

View File

@@ -393,8 +393,30 @@ class BingxVenueAdapter(VenueAdapter):
snapshot = self._backend_snapshot(include_history=False)
return [dict(row) for row in (snapshot.open_positions or {}).values()]
def reconcile(self) -> List[VenueEvent]:
snapshot = self._backend_snapshot(include_history=True)
async def reconcile(self) -> List[VenueEvent]: # type: ignore[override]
"""Fetch open-order state from BingX and return any pending VenueEvents.
WHY ASYNC: the old sync version called _backend_snapshot() → _call_backend()
→ _run() → pool.submit(asyncio.run, coro).result(timeout=30s). That spawned
a *new* event loop in a thread-pool thread. The BingxHttpClient (aiohttp
session) is bound to the *main* event loop — using it from a different loop
silently deadlocks. BingX VST responds in ~500ms; the deadlock made every
reconcile call block the main event loop for the full 30s timeout.
FIX: declare async, call backend.refresh_state() directly with await so it
runs in the *caller's* (main) event loop where the session lives.
pump_venue_events() already has `if inspect.isawaitable(events): await events`
— zero caller changes required.
include_history=False: all_orders/all_fills require a symbol (symbol=None
skips them anyway), so include_history=True was fetching nothing extra.
"""
try:
snapshot = await self.backend.refresh_state(None, include_history=False)
except Exception as exc:
import logging as _log
_log.getLogger(__name__).warning("reconcile: refresh_state failed: %s", exc)
return []
return self._events_from_snapshot(snapshot)
def submit(self, intent: KernelIntent) -> List[VenueEvent]: