Skip to content

Windows: supervise_output intermittently waits for a lingering descendant that holds the output pipe (test_supervisor flake/hang) #7819

Closed

Description

@masenf

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_descendants fails with assert 30.13942320000001 < 10. 30.1 s is exactly the lifetime of the test's time.sleep(30) grandchild. So supervise_output returned 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 was tests\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:randomly repeatedly on Windows (3.14/3.15 hit it in CI). The test's _LINGERING_SCRIPT re-executes itself under supervisor.supervise_output, starts python -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(), or ReadFile on an inherited handle (Windows has no close_fds semantics 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 < 10 bound or skip it on Windows. It is correct, and the fix is in supervisor.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

  1. linear-code commented on Nov 11, 2026

    @linear-code
  2. added a commit that references this issue on Nov 11, 2026
  3. added
    release-trainIssues found during agentic /prerelease-testing fanout, not necessarily validated bugs.
    on Nov 11, 2026
  4. added a commit that references this issue on Nov 12, 2026
    9c44f07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    release-trainIssues found during agentic /prerelease-testing fanout, not necessarily validated bugs.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions