chart/app
Chris Amow a395818581 Post cross-thread events through the loop, and measure how late it runs
P0 from docs/async_refactor.md. The mutating routes are sync `def`, so FastAPI
runs them in a threadpool, and they reach Runtime.broadcast through
rebuild_levels — writing asyncio.Queue directly from there. That queue is not
thread-safe: it wakes a consumer by resolving a Future, which only the loop
thread may do. A dropped wakeup means a drawing made in one browser does not
reach another until the next market tick.

broadcast now posts through call_soon_threadsafe when it is off the loop, and
publishes directly when it is on it, so the stream's own path pays nothing.

Worth being straight about the tests: the race is timing-dependent and did not
reproduce in twenty attempts — a foreign-thread put_nowait usually lands in the
ready queue before the loop sleeps, and a tick every second covers the rest.
Even asyncio's debug thread-affinity check stays quiet unless a consumer is
parked on the Future at that instant. So the tests assert the contract rather
than provoke the failure: a broadcast from a worker thread must go through
call_soon_threadsafe, one from the loop must deliver synchronously, and both
must arrive.

Also adds the loop-lag probe, which reports scheduling drift as loop_lag_ms on
/api/status. It found P1 on its first run: 19,441ms worst against 1.5ms in
steady state, which is seeding blocking the loop. "The chart feels laggy" is now
a number.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 15:30:45 -05:00
..
analysis Persist alert cooldowns across restarts so deploys stop re-firing every zone 2026-08-11 19:46:11 +00:00
api Post cross-thread events through the loop, and measure how late it runs 2026-08-11 15:30:45 -05:00
bars Stop a zero-price tick and a late exchange bar corrupting the store 2026-08-10 17:10:24 -05:00
market Unify chart geometry and deepen 30m history 2026-08-11 04:08:02 -05:00
notify Implement M4 confluence alerts 2026-08-09 20:51:32 -05:00
__init__.py Implement M0 Yahoo market data and replay 2026-08-09 20:36:13 -05:00
config.py Persist alert cooldowns across restarts so deploys stop re-firing every zone 2026-08-11 19:46:11 +00:00
runtime.py Post cross-thread events through the loop, and measure how late it runs 2026-08-11 15:30:45 -05:00