From b68e6ea65be8af72d665b57d6e5960507192ef51 Mon Sep 17 00:00:00 2001 From: rafiul bari Date: Thu, 9 Jul 2026 03:51:08 -0400 Subject: [PATCH] fix: stop realtime feed before freeing data frames on disconnect (#98) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit TradingSuite.disconnect() cleaned up instrument data contexts (freeing the Polars frames) before stopping the SignalR realtime reader. The reader keeps writing into those frames on native background threads until disconnected, so freeing them first races SignalR threads against Polars memory and corrupts the heap on Windows (STATUS_HEAP_CORRUPTION / 0xc0000374) — an intermittent, unrecoverable abort at disconnect. Reorder disconnect() to call realtime.disconnect() first, quiescing the reader before any data frame is freed. Pure reordering; no behavior change otherwise. --- src/project_x_py/trading_suite.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/project_x_py/trading_suite.py b/src/project_x_py/trading_suite.py index f1bf151..6be1f1e 100644 --- a/src/project_x_py/trading_suite.py +++ b/src/project_x_py/trading_suite.py @@ -982,6 +982,15 @@ async def disconnect(self) -> None: """ logger.info("Disconnecting TradingSuite...") + # Disconnect realtime FIRST so the SignalR background reader thread is + # quiesced before any Polars data frames it writes into are freed. The + # previous order (cleanup contexts -> disconnect realtime) freed the + # frames while the reader was still live, racing native SignalR threads + # against Polars memory and corrupting the heap on disconnect + # (Windows STATUS_HEAP_CORRUPTION / 0xc0000374). See issue #98. + if self.realtime: + await self.realtime.disconnect() + if self._instruments: # Multi-instrument mode - disconnect all contexts await self._disconnect_instrument_contexts() @@ -989,10 +998,6 @@ async def disconnect(self) -> None: # Legacy single-instrument mode await self._disconnect_legacy_single_instrument() - # Disconnect realtime - if self.realtime: - await self.realtime.disconnect() - # Clean up client context if hasattr(self, "_client_context") and self._client_context: try: