fix: bound safe_terminate waits and gevent stdio exit#419
Merged
RonnyPfannschmidt merged 1 commit intoJul 22, 2026
Conversation
Harden safe_terminate so a stuck kill cannot hang Group.terminate forever (supersedes pytest-dev#221 / residual of pytest-dev#43), and switch gevent fdopen to FileObject so init_popen_io children can exit cleanly. Co-authored-by: Cursor AI <ai@cursor.sh> Co-authored-by: Cursor Grok 4.5 <grok@x.ai>
There was a problem hiding this comment.
Pull request overview
This PR hardens execnet teardown behavior to avoid indefinite hangs during gateway group termination and fixes a gevent stdio wrapper choice that could keep the interpreter from exiting cleanly.
Changes:
- Bound
safe_terminate()waits (timeout * 2), continue past timed-out termination/kill pairs, and still attemptwaitall. - Added a regression test covering the “kill blocks / terminate must not hang forever” scenario.
- Switched gevent
fdopen()fromFileObjectThreadtoFileObjectto avoid keeping a native threadpool alive on exit.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
testing/test_multi.py |
Adds a regression test ensuring safe_terminate() doesn’t hang when a kill function blocks. |
src/execnet/multi.py |
Updates safe_terminate() typing and logic to bound waits and avoid indefinite blocking. |
src/execnet/gateway_base.py |
Adjusts gevent fdopen() implementation to prevent shutdown hangs caused by FileObjectThread. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+294
to
+318
| release_kill = execmodel.Event() | ||
| other_killed: list[int] = [] | ||
|
|
||
| def term_slow() -> None: | ||
| execmodel.sleep(10) | ||
|
|
||
| def kill_hang() -> None: | ||
| kill_started.set() | ||
| release_kill.wait() | ||
|
|
||
| def kill_ok() -> None: | ||
| other_killed.append(1) | ||
|
|
||
| safe_terminate( | ||
| execmodel, | ||
| 0.2, | ||
| [ | ||
| (term_slow, kill_hang), | ||
| (term_slow, kill_ok), | ||
| ], | ||
| ) | ||
|
|
||
| assert kill_started.is_set() | ||
| assert other_killed == [1] | ||
| release_kill.set() |
Comment on lines
+363
to
+367
| try: | ||
| reply.waitfinish(timeout=wait_timeout) | ||
| except OSError: | ||
| # termkill still running (typically stuck in killfunc). | ||
| continue |
Zac-HD
approved these changes
Jul 22, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
safe_terminateso a stuckkillfunccannot hangGroup.terminateforever: wait with atimeout * 2bound, continue past timed-out replies, and still runwaitall. Adds a regression test for the hang seen via pytest-xdist teardown.fdopento useFileObject(Posix on Unix) instead ofFileObjectThread, whose native threadpool prevented interpreter shutdown afterinit_popen_ioand timed outtest_popen_io[gevent].Supersedes #221 (and the residual hang from #43 after
waitallgained a timeout).Test plan
pytest testing/test_multi.py::test_safe_terminate_does_not_hang_when_kill_blockspytest testing/test_basics.py::test_popen_io[gevent-sys.executable]pytest -k gevent(related suite)Made with Cursor