Repository navigation
Description
Summary
On Windows CI, tests/units/reflex_base/utils/test_supervisor.py intermittently fails or hangs. The module is new in #7650 ("Give reflex run one owner for shutdown"). Unrelated PRs are hitting it:
- Merge a repeated @rx.event; restore staticmethod/classmethod above @rx.event #7807,
unit-tests (windows-latest, 3.15)(run 42994958233, job 130436693630):test_supervise_output_does_not_wait_for_lingering_descendantsfails withassert 30.13942320000001 < 10. 30.1 s is exactly the lifetime of the test'stime.sleep(30)grandchild. Sosupervise_outputreturned only after that grandchild exited, which is the very behaviour the test guards against. - Respawn failed Granian workers in production #7803,
unit-tests (windows-latest, 3.14)(run 42988477865, job 130415466314): the "Run unit tests" step was cancelled after 23 min. The last output line wastests\units\reflex_base\utils\test_supervisor.py .........ssssss....., so the step stalled inside that module.
The other Windows legs of the same runs (3.11, 3.12, 3.13, and 3.15 on #7803) passed, so the failure is intermittent. Neither PR touches reflex_base/utils/supervisor.py.
Why it matters beyond CI
The test models a real reflex run situation. A child process spawns a grandchild that inherits the output pipe and outlives the child. If supervise_output waits for the pipe to close instead of for the child to exit, then on Windows reflex run can wait on shutdown for as long as any stray descendant holds stdout/stderr. That could be up to forever, e.g. a dev server or watcher the app started.
Repro
- Run
uv run pytest tests/units/reflex_base/utils/test_supervisor.py -k lingering -p no:randomlyrepeatedly on Windows (3.14/3.15 hit it in CI). The test's_LINGERING_SCRIPTre-executes itself undersupervisor.supervise_output, startspython -c "import time; time.sleep(30)"and exits. - Expected: the outer process returns within about 1 s.
- Observed on failure: it returns after about 30 s, when the grandchild exits.
What a fix must do
- Root-cause first: find why the supervisor sometimes blocks on Windows until every handle to the pipe closes, instead of returning when the direct child exits. Likely places to look: a reader thread
join,communicate(), orReadFileon an inherited handle (Windows has noclose_fdssemantics for handles that are not explicitly marked non-inheritable). - Keep the shutdown semantics from Give reflex run one owner for shutdown #7650: a single owner for shutdown, output still forwarded until the child exits, no lost trailing output in the normal case.
- Leave the test alone: don't loosen its
elapsed < 10bound or skip it on Windows. It is correct, and the fix is insupervisor.py. - Separately, give each wait in this test module a timeout, so a regression fails within seconds instead of consuming the 360-minute job budget. That is what happened on Respawn failed Granian workers in production #7803.
Non-goals
- Changing the supervisor's behaviour on POSIX, where the test passes consistently.
- Killing descendants on exit: a lingering descendant should be left running, just not waited for.
Found during reflex 0.11.0 release fix-up CI.
Activity
linear-code commentedon Nov 11, 2026
ENG-13960
prerelease: file #7819 (Windows supervisor flake)
Don't wait in Thread.join(timeout) when draining supervised output (r…