fix(cron): terminal-worker reaper lives in scheduler_detached_worker, not the scheduler facade

cron/scheduler.py is already past the facade line-count gate; the reap helper is a detached-worker teardown concern with an existing topical home. The waiter late-imports it so behaviour and the sync run_one_job contract are unchanged.
This commit is contained in:
teknium1
2026-09-18 03:58:30 -07:00
committed by Teknium
parent 21d7283f3d
commit d6d3ee8584
2 changed files with 22 additions and 18 deletions

View File

@@ -3096,23 +3096,6 @@ def _run_one_job_body(
reset_terminal_scope(_terminal_scope_token)
def _reap_terminal_worker_in_background(process: subprocess.Popen) -> None:
"""Keep the reap contract when the waiter returns before the worker exits.
The ledger turning terminal lets the waiter return while the worker is
still in final teardown. The gateway remains the worker's parent, so if
nobody calls ``wait()`` afterwards the worker lingers as a zombie (STAT=Z)
under the gateway until it is restarted (#114509). A short-lived daemon
thread holds that single responsibility and ends with the process exit
it waits for.
"""
threading.Thread(
target=process.wait,
name=f"cron-worker-reap-{getattr(process, 'pid', '?')}",
daemon=True,
).start()
def _wait_for_external_cron_worker_body(
process: subprocess.Popen,
*,
@@ -3139,7 +3122,9 @@ def _wait_for_external_cron_worker_body(
returncode = process.wait(timeout=1.0)
except subprocess.TimeoutExpired:
if _is_terminal():
_reap_terminal_worker_in_background(process)
from cron.scheduler_detached_worker import reap_terminal_worker_in_background
reap_terminal_worker_in_background(process)
return True
continue
# The worker can commit its terminal row and exit between the first

View File

@@ -9,6 +9,8 @@ overlap behind #102827. The worker's Future owns the teardown instead.
from __future__ import annotations
import concurrent.futures
import subprocess
import threading
from typing import Optional
@@ -32,3 +34,20 @@ def defer_teardown_to_running_worker(
# Runs inline if the worker finished between done() and here — still exactly once.
future.add_done_callback(_finish)
return True
def reap_terminal_worker_in_background(process: subprocess.Popen) -> None:
"""Keep the reap contract when the waiter returns before the worker exits.
The ledger turning terminal lets ``_wait_for_external_cron_worker_body``
return while the worker is still in final teardown. The gateway remains the
worker's parent, so if nobody calls ``wait()`` afterwards the worker lingers
as a zombie (STAT=Z) under the gateway until it is restarted (#114509). A
short-lived daemon thread holds that single responsibility and ends with
the process exit it waits for.
"""
threading.Thread(
target=process.wait,
name=f"cron-worker-reap-{getattr(process, 'pid', '?')}",
daemon=True,
).start()