Skip to content

Add a per-job time limit to the dispatcher - #32

Merged
HalRiveria merged 3 commits into
mainfrom
feat/dispatcher-timeout
Nov 3, 2026
Merged

HalRiveria merged 3 commits into
mainfrom
feat/dispatcher-timeout

Conversation

@HalRiveria-Bot

@HalRiveria-Bot HalRiveria-Bot commented Nov 2, 2026 •

Copy link
Copy Markdown
Collaborator

What was wrong: Invoke-PkProcessDispatch had no time limit, so a hung program held its slot until the run was aborted. The README's -JobPollCompleted workaround broke with a custom starter that set no Stopwatch (under StrictMode it threw and ended the run; otherwise the limit silently never fired), and a killed job looked like any other non-zero exit.

What changed:

  • The dispatcher takes -TimeoutSeconds (default 0, no limit). A job's TimeoutSeconds key overrides it, and 0 opts the job out.
  • An overrunning program is killed with everything it started. Its job goes through -JobFinalize with $job['TimedOut'] = $true, and other jobs carry on.
  • The limit stays in force while an abort waits for running jobs. A retry gets a fresh clock. A starter that sets no Stopwatch is still timed. A job whose .Proc isn't a process isn't timed.
  • A program that can't be stopped gets one try and one warning, not a taskkill on every pass.
  • A bad key is a failed start, before anything runs.
  • Invoke-PkProcessBatch uses the same code instead of its own poll closure, and gains a per-command TimeoutSeconds key. A bad value gives that command a StartError, so every command still gets a result.
  • -TimeoutSeconds is now a [double] on both, so 0.4 no longer rounds to 0 and silently means no limit.
  • The README's workaround is gone, replaced by the new parameter.

Behavior users can notice:

  • A job field already named TimeoutSeconds now sets the kit's limit, and a value that isn't a number fails that job's start.
  • TimeLimit and TimedOut are new reserved job keys.

Tested: the harness passes on 5.1 and 7 (322 assertions, none skipped), with a new section AC. 16 of its assertions failed on main before the feature. The fractional tests fail on an [int] parameter, and the warning test fails without the one-try rule (12 warnings instead of 1). A plain callback that reads the caller's own $TimeoutSeconds still sees the caller's value, on the main thread and in a worker.

Not covered: if taskkill reports failure on a single program but the program did die (a child it couldn't kill, say), the job may show TimedOut = $false. That's existing behavior in Stop-PkProcessTree, and reproducing it needs an elevated child process.

🤖 Generated with Claude Code

HalRiveria and others added 3 commits November 2, 2026 18:48
Invoke-PkProcessDispatch had no time limit, so a hung program held its slot until
the run was aborted. The README's -JobPollCompleted workaround broke with a custom
starter that set no Stopwatch, and gave no way to tell a timed-out job from a
failed one.

The dispatcher now takes -TimeoutSeconds, and a job's TimeoutSeconds key overrides
it (0 opts out). An overrunning program is killed with everything it started, and
its job goes through -JobFinalize with TimedOut set. Invoke-PkProcessBatch uses the
same code in place of its own poll closure and gains a per-command key; a bad value
there gives that command a StartError.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… run

-TimeoutSeconds was an int, so 0.4 bound as 0 and meant no limit. A process the
time check couldn't read threw and ended the whole dispatch; it's now just not
timed. The last check before an abort's force-stop no longer kills jobs one at a
time, since whatever is still running is force-stopped together right after.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PowerShell returns nothing for HasExited on a Process it can't read rather than
throwing, so the time check needs no catch of its own. A never-started Process
reaches the "couldn't be stopped" path, which makes it testable: one warning, not
a taskkill on every pass.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@HalRiveria
HalRiveria merged commit bf45b0d into main Nov 3, 2026
2 checks passed
@HalRiveria
HalRiveria deleted the feat/dispatcher-timeout branch November 3, 2026 01:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants