Skip to content

fix: stop realtime feed before freeing data frames on disconnect (heap corruption, #98)#99

Open
blurxy wants to merge 1 commit into
TexasCoding:mainfrom
blurxy:fix/disconnect-reorder-heap-corruption
Open

fix: stop realtime feed before freeing data frames on disconnect (heap corruption, #98)#99
blurxy wants to merge 1 commit into
TexasCoding:mainfrom
blurxy:fix/disconnect-reorder-heap-corruption

Conversation

@blurxy

@blurxy blurxy commented Jul 9, 2026

Copy link
Copy Markdown

Summary

Fixes a Windows heap-corruption crash (STATUS_HEAP_CORRUPTION / 0xc0000374) that can occur when a multi-instrument TradingSuite disconnects while realtime data is flowing. The fix reorders TradingSuite.disconnect() to stop the realtime feed before cleaning up the instrument data contexts.

Tracks #98.

Root cause

TradingSuite.disconnect() currently runs cleanup in this order:

  1. _disconnect_instrument_contexts()context.data.cleanup() — frees the Polars DataFrames that hold each timeframe's bars.
  2. self.realtime.disconnect() — stops the SignalR background reader.

The SignalR reader runs on native background threads and keeps writing incoming quotes/trades into the RealtimeDataManager's Polars frames until it is stopped. Freeing those frames in step 1 while the reader is still live in step 2 races native SignalR threads against Polars-owned memory. On Windows this manifests as STATUS_HEAP_CORRUPTION (exit code 0xc0000374 / 3221225477) — an immediate, unrecoverable process abort at disconnect. It is timing-dependent (worst around active-session disconnects / restarts), which is why it presents as an intermittent crash rather than a deterministic one.

Fix

Move the self.realtime.disconnect() call to the top of disconnect(), before _disconnect_instrument_contexts() / _disconnect_legacy_single_instrument(). Quiescing the reader first guarantees no native thread is touching a Polars frame while it is being freed. The change is a pure reordering — no behavior change beyond the ordering, no new dependencies.

         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.
+        if self.realtime:
+            await self.realtime.disconnect()
+
         if self._instruments:
             await self._disconnect_instrument_contexts()
         else:
             await self._disconnect_legacy_single_instrument()
-
-        # Disconnect realtime
-        if self.realtime:
-            await self.realtime.disconnect()

Validation

We run the equivalent reordering (quiesce realtime before the per-context cleanups) at the broker layer in a live Windows trading bot on TopstepX/ProjectX with a 4-instrument suite (NQ/ES/MNQ/MES). Before the reorder we saw recurring 0xc0000374 aborts at disconnect; after it, disconnects are clean across many session restarts and the daily 17:00 ET maintenance rollover. This PR moves that same fix into the SDK so the default disconnect() path is safe for all users.

…asCoding#98)

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.
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