Reimplement TimeoutScheduler with timer thread and pool dispatch - #748
Reimplement TimeoutScheduler with timer thread and pool dispatch#748rmahfoud wants to merge 2 commits into
Conversation
|
Thanks for the PR — the underlying problem is real: That said, I think this particular fix introduces two concrete regressions, and it also only addresses one of ~20 places with the same issue. Blocking issues1. Deadlock: a single shared thread runs user callbacks inline
Reproduced against current import threading
import reactivex
from reactivex import operators as ops
from reactivex.scheduler import EventLoopScheduler
sched = EventLoopScheduler() # simulates the PR's module-level default
done = threading.Event()
def on_err(e):
print("outer timeout fired, now running a nested timeout...", flush=True)
try:
reactivex.never().pipe(ops.timeout(0.5, scheduler=sched)).run()
except Exception as ex:
print("nested completed:", ex, flush=True)
done.set()
reactivex.never().pipe(ops.timeout(0.5, scheduler=sched)).subscribe(on_error=on_err)
print("nested finished in time:", done.wait(5), flush=True)The same code with 2. Unbounded queue growth on hot streams
sched = EventLoopScheduler()
s = Subject()
sub = s.pipe(ops.timeout(60.0, scheduler=sched)).subscribe(lambda v: None)
for i in range(20000):
s.on_next(i)
print("pending items in event loop queue:", len(sched._queue))
# pending items in event loop queue: 20001Every Secondary concerns
Suggested directionNote:
|
|
Thanks for the detailed review — agreed on both blocking issues and on fixing this at the scheduler layer rather than swapping I've reworked the approach locally along the lines you suggested:
I'll push an updated commit to this branch shortly (title/description will reflect the scheduler rewrite). |
Replace per-timeout threading.Timer with one timer thread and ThreadPoolExecutor dispatch so all TimeoutScheduler call sites avoid thread exhaustion without EventLoopScheduler deadlock or queue growth. Co-authored-by: Cursor <cursoragent@cursor.com>
Summary
TimeoutSchedulerno longer spawns athreading.Timer(OS thread) per pending timeout; one timer thread tracks due times and dispatches fired actions onto aThreadPoolExecutor.TimeoutScheduler.singleton()call sites (timeout,debounce,delay,timer, …) benefit without per-operator changes.Test plan
pytest tests/test_scheduler/test_timeoutscheduler.py(singleton, schedule, cancel, nested/blocking handler, queue eviction, short-vs-long, no thread-per-timer)timeout()use no longer exhausts threadstimeout/ blocking handlers on the default scheduler