Problem
Starting a data stream with run() / _run_forever() before registering any subscriptions causes the startup path to poll continuously. DataStream currently loops with await asyncio.sleep(0), which yields to the event loop but remains immediately runnable and can consume an entire CPU core. TradingStream has the same lifecycle pattern with a 100 ms polling interval.
Subscription handlers may be registered after the stream starts, including from another thread, so the stream needs to remain idle while still reacting promptly to registration and shutdown.
Expected behavior
- Stream startup blocks without polling until a usable subscription exists.
- Registering a subscription wakes the stream immediately.
- A shutdown request wakes the same wait path and exits without opening a websocket.
- Notifications originating outside the stream thread are delivered safely to the asyncio event loop.
Proposed fix
Use an asyncio.Event owned by the stream loop. Subscription registration and shutdown should signal it with loop.call_soon_threadsafe(event.set). Keep the existing thread-safe stop queue for shutdown state and check shutdown before subscription readiness when the waiter wakes.
Add regression tests confirming that idle startup performs no polling sleeps, shutdown wakes an unsubscribed stream, and subscriptions registered from another thread start the websocket promptly.
Impact
Applications can start their stream lifecycle before registering handlers without excessive idle CPU usage or polling-induced subscription latency.
Problem
Starting a data stream with
run()/_run_forever()before registering any subscriptions causes the startup path to poll continuously.DataStreamcurrently loops withawait asyncio.sleep(0), which yields to the event loop but remains immediately runnable and can consume an entire CPU core.TradingStreamhas the same lifecycle pattern with a 100 ms polling interval.Subscription handlers may be registered after the stream starts, including from another thread, so the stream needs to remain idle while still reacting promptly to registration and shutdown.
Expected behavior
Proposed fix
Use an
asyncio.Eventowned by the stream loop. Subscription registration and shutdown should signal it withloop.call_soon_threadsafe(event.set). Keep the existing thread-safe stop queue for shutdown state and check shutdown before subscription readiness when the waiter wakes.Add regression tests confirming that idle startup performs no polling sleeps, shutdown wakes an unsubscribed stream, and subscriptions registered from another thread start the websocket promptly.
Impact
Applications can start their stream lifecycle before registering handlers without excessive idle CPU usage or polling-induced subscription latency.