Repository navigation
Stop worker groups that Lean's watchdog leaves running at LSP close - #322
Conversation
Review at 532458aVerdict: Request changes. This makes Fixes before merge
Optional
Landing
Decision for the maintainer
This PR came from the same PR swarm that reviewed it, so this is the author's review, not an independent one. The test suite was not run locally; fix 1's proposed test was run directly against this branch's code. CI at this head: all 11 checks pass, including the real-Lean jobs. Posted by PR swarm: PR Swarm Lead |
532458a to
b0cf364
Compare
Review changes addressed at b0cf364
Still open for you: keep the process-group recording ("Part of #251"), or tag the session's processes with an environment token and sweep them, which also covers workers orphaned before close and would close #251. CI is running on this head. Posted by PR swarm: PR Swarm Lead |
Lean's watchdog starts each file worker with setsid and, as it shuts down, kills only the workers whose exit it has not seen yet. A worker that already exited on its exit notification is skipped, and its `lake setup-file` and `lean ... -o` keep running in a group the session does not own; the watchdog's fallback kill races with its own exit and often loses. Workers are also left running when the EOF grace ends while the watchdog is still stopping them, since the group signal then kills the watchdog. The session now records the process groups of its descendants outside its own group while the watchdog still parents them: before closing Lean's pipes on the abort path, and before the shutdown request on a graceful close. It scans every member of its group, so it still finds `lean --server` after `lake serve` has died. Once its own group is retired, it terminates each recorded group that still holds a recorded process, checked by group, PID and creation time, so a reused group ID is not signalled. A failed termination keeps the groups recorded for the retry. On main this is hardening: a session never holds two documents open. Fake-watchdog tests pin both close paths and a crashed wrapper, unit tests pin the reuse check and the retry, and a real-Lean test opens two files and checks that nothing outlives an abort. Closes #251.
b0cf364 to
7cf7735
Compare
Follow-up review at 7cf7735Verdict: Ready to merge. This pass checked each applied fix and the one rejected suggestion against the diff, re-read the Lean v4.32.2 watchdog code the description cites, and went over the whole change again. It merges cleanly into current main (d1ad2fb), and no commit on main since the first review touches Changed in this pass
Decision for the maintainer
This PR came from the same PR swarm that reviewed it, so this is the author's review, not an independent one. pytest was not run at this head because the machine was overloaded; the changed tests and the lifecycle tests around them were called directly and pass, and the reuse and crashed-wrapper tests fail when the identity check, the running check, or the whole-group scan is removed. The real-Lean test runs only in CI. CI at this head: all 7 checks pass, including the real-Lean jobs. Posted by PR swarm: PR Swarm Lead |
Closes #251.
Problem
Lean's watchdog (
Watchdog.lean, Lean v4.32.2) starts each file workerwith setsid. On stdin EOF or a
shutdownrequest it sends every workeran exit notification and then calls
killProcAndWaiton each in URIorder, which terminates a worker's process group only if the watchdog
has not yet seen that worker exit. A worker that exits on its exit
notification while an earlier one is being killed is skipped; its group
is then terminated only by the fallback in
forwardMessages, whichraces with the watchdog's own exit. When the fallback loses, the
worker's
lake setup-fileand itslean ... -ochild keep runningafter
close()returns. Workers are also left running when the EOFgrace (
LSP_EOF_GRACE_SECONDS) ends while the watchdog is stillstopping them: the group signal then kills the watchdog before it
reaches them.
On main the server never holds two documents open:
_hoveris the onlycaller of
_open_document, and a session that may still hold itsdocument admits no further operation. So this is hardening:
close()stops the worker groups the watchdog leaves behind, however many
documents are open.
Change
LeanLspSessionrecords the process groups of its descendants outsideits own group while the watchdog still parents them: in
_retire_oncebefore it closes Lean's pipes, and in
close()before the shutdownrequest. It scans every member of the server's group, so it still finds
lean --serverafterlake servehas died. After the server group isretired, and before its leader is reaped,
_stop_worker_groupsterminates each recorded group that still holds a recorded process
(
os.getpgid, then psutil's PID and creation-time check), so a groupwhose ID was reused is not signalled. A failed termination keeps the
groups recorded for
_abort_process's retry.Not covered: processes already orphaned when close starts. If
lean --serverdies first, its workers are reparented and there isnothing left to record.
Tests
test_close_stops_a_worker_group_the_watchdog_left_running(abortand graceful) and
test_close_stops_a_worker_group_left_running_after_the_wrapper_crashed:a fake watchdog, run directly or under a wrapper that has already
exited, leaves a worker's build running and exits; close must stop the
build. Each fails without the recording on its path.
test_abort_never_signals_a_worker_group_whose_recorded_members_are_gone(exited and left-the-group): a live group under a recorded group ID is
not signalled once its recorded process has exited or moved to another
group.
test_abort_retries_a_failed_worker_group_before_reaping_the_leader:a failed worker-group termination is retried before the leader is
reaped.
still fail if the group is signalled before EOF or if stdout stays
open.
test_real_lean_abort_stops_the_workers_of_every_open_file: realLean with two open files, the second building a slow import, then an
abort; nothing may outlive it. Whether Lean skips the second worker is
a race, so this test can pass without the change. CI's real-Lean job
selects it with
-k real_lean.ruff check autoform_cli servers testspasses, and the tests aboveother than the real-Lean one pass locally.