Skip to content

fix(cron): reap the whole process group when a script job times out - #68262

Closed
Ait0u5hi wants to merge 1 commit into
NousResearch:mainfrom
Ait0u5hi:fix/cron-process-group-reaping
Closed

fix(cron): reap the whole process group when a script job times out#68262
Ait0u5hi wants to merge 1 commit into
NousResearch:mainfrom
Ait0u5hi:fix/cron-process-group-reaping

Conversation

@Ait0u5hi

Copy link
Copy Markdown

What does this PR do?

Cron no_agent script jobs are run via subprocess.run(argv, timeout=script_timeout) in _run_job_script. On timeout, subprocess.run SIGKILLs only the direct child — any process the script backgrounded is orphaned, and a child wedged in uninterruptible D-state (heavy I/O) hangs the reap entirely. The cron heartbeat/claim-TTL recovers the claim, but not the leaked OS process, so stuck processes accumulate. Observed on a Jetson/eMMC box where scripts doing heavy I/O against a large state.db occasionally exceed script_timeout.

Related Issue

Fixes # (no existing issue — happy to open one if preferred)

Type of Change

  • 🐛 Bug fix (non-breaking change that fixes an issue)

Changes Made

  • cron/scheduler.py_run_job_script: run the script in its own session/process group (start_new_session=True on POSIX) and, on TimeoutExpired, os.killpg(os.getpgid(proc.pid), SIGKILL) the whole group instead of just the child. Windows keeps proc.kill(). Converts subprocess.runPopen + communicate(timeout=); the (bool, str) return contract and the secret-redaction path are unchanged. signal.SIGKILL via getattr(..., signal.SIGTERM) + # windows-footgun: ok.
  • tests/cron/test_scheduler_process_group_reaping.py — POSIX-guarded regression test.

How to Test

  1. python3 -m py_compile cron/scheduler.py
  2. pytest tests/cron/test_scheduler_process_group_reaping.py -q
  3. Manual repro: a script that does sleep 300 & then blocks past its timeout — after the fix, pgrep -g <pgid> is empty (grandchild reaped); before, it leaked.

Checklist

Code

  • I've read the Contributing Guide
  • My commit messages follow Conventional Commits (fix(cron):)
  • I searched for existing PRs to make sure this isn't a duplicate
  • My PR contains only changes related to this fix
  • I've run the tests and they pass
  • I've added tests for my changes
  • Ran scripts/check-windows-footguns.py cron/scheduler.py — clean

_run_job_script ran no_agent cron scripts via subprocess.run(timeout=), which
SIGKILLs only the direct child on timeout. Backgrounded grandchildren are
orphaned, and a child wedged in uninterruptible D-state hangs the reap entirely,
leaking stuck processes (the cron heartbeat recovers the claim, not the OS
process).

Run the script in its own session/process group (start_new_session=True on
POSIX) and, on TimeoutExpired, os.killpg the whole group; Windows keeps
proc.kill(). Converts subprocess.run to Popen+communicate(timeout=); the
(bool, str) return contract and secret-redaction path are unchanged.

Adds a POSIX-guarded regression test asserting the timeout path kills the group.
@alt-glitch alt-glitch added type/bug Something isn't working P2 Medium — degraded but workaround exists comp/cron Cron scheduler and job management labels Jul 20, 2026
@alt-glitch

Copy link
Copy Markdown
Collaborator

This was generated by AI during triage.

Related to #59379, which addresses the same timeout process-group leak and also corrects failure classification. This PR is the focused cleanup slice; maintainers can choose the preferred scope.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/cron Cron scheduler and job management P2 Medium — degraded but workaround exists type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants