Skip to content

Interpreter hangs at exit after the test suite completes (wait_for_thread_shutdown) #41

Description

@eldonm

pytest -q finishes the run — every test executes, the summary is written — and then the process never exits. It sits in interpreter shutdown indefinitely.

Measured on one such run: 5h27m wall clock, 53 seconds of CPU. The suite itself took a few minutes; the rest was the hang.

Evidence

sample <pid> during the hang:

1712 Py_RunMain  (in Python) + 272
  1712 Py_FinalizeEx  (in Python) + 64
    1712 wait_for_thread_shutdown  (in Python) + 92

wait_for_thread_shutdown joins non-daemon threads. Something started one and never joined it.

Likely culprit

jvspatial/api/integrations/scheduler/scheduler.py:69 creates a ThreadPoolExecutor. Since Python 3.9 its worker threads are non-daemon, and concurrent.futures.thread._python_exit joins them at interpreter exit — which is precisely the frame above.

Note the SchedulerThread itself (scheduler.py:124-126) is daemon=True, so it looks handled at a glance; the executor it owns is the part that isn't. If the executor is never .shutdown(), or a worker is parked on a blocking call, exit blocks forever.

Stated as the strongest hypothesis, not a confirmed diagnosis — I have not instrumented which thread is actually outstanding.

Why it is worth fixing

  • CI hides it. The runner tears the container down, so this never shows up in a workflow — only locally, where it costs a maintainer real time.
  • It hides results. Anyone piping the suite (pytest ... | tail) gets no output at all, because the pipe never closes. That is how it burned five hours here: the run had completed and looked like it was still going.
  • It reproduces on unmodified main.

Repro

pytest -q            # completes, then hangs; ^C or kill required
pytest -q | tail     # worse: no output ever appears

Suggested direction

Shut the executor down on scheduler stop (and register an atexit fallback), or construct it so the workers cannot outlive the process. A pytest session-finish assertion that no non-daemon threads remain would keep it from coming back.

Found while running the full suite during #37 / #38.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions