Skip to content

[rush] Add per-iteration runner persistence control - #5888

Open
bmiddha wants to merge 6 commits into
mainfrom
bmiddha/ws0-t4-persist-policy-hook
Open

[rush] Add per-iteration runner persistence control#5888
bmiddha wants to merge 6 commits into
mainfrom
bmiddha/ws0-t4-persist-policy-hook

Conversation

@bmiddha

@bmiddha bmiddha commented Jul 20, 2026

Copy link
Copy Markdown
Member

Summary

Adds opt-in, per-iteration runner persistence control to the operation graph. Hosts can keep selected runners warm between iterations and tear down cold runners, while the default behavior remains unchanged.

Details

IConfigurableOperation now exposes a mutable shouldRunnerPersist: boolean, defaulting to true. Hosts set it during configureIteration alongside enabled, giving both controls the same per-operation, per-iteration semantics. IOperationExecutionResult exposes the configured value as readonly.

After afterExecuteOperationAsync, the graph closes a completed operation's runner when the value is false, before queue completion can unblock downstream operations. Cold operations that bypass normal completion are closed before afterExecuteIterationAsync. If one of those fallback closes fails, the current operation record and iteration become failures, the error is reported, and iteration finalization still runs instead of leaving the graph in the Executing state. Terminal output resources are finalized before iteration hooks so summary consumers can safely inspect failures for records that never executed.

This moves persistence ownership out of IPCOperationRunnerPlugin and keeps IPCOperationRunner policy-agnostic. The plugin no longer hard-codes persist: true; runners remain resident by default, so existing watch behavior is preserved. The new control has no production caller yet and is available for a future daemon warm-selection policy.

The record-based control was chosen over an iteration callback because configureIteration already owns per-iteration operation decisions and mutable record state avoids threading policy through runner contexts.

The public additions are @alpha, so there is no stable API compatibility break. The internal IIPCOperationRunnerOptions.persist field was removed; unsupported deep-import callers must remove that argument. Each iteration performs a linear record sweep, and cold-runner cleanup is bounded by the graph's configured parallelism.

How it was tested

  • cd libraries/rush-lib && node_modules/.bin/heft test --test-path-pattern OperationGraph - 42/42 tests passed
  • node common/scripts/install-run-rush.js test --to @microsoft/rush-lib - 718/718 tests passed across 75 suites
  • node common/scripts/install-run-rush.js build -t rush - passed
  • node common/scripts/install-run-rush.js change --verify
  • Locally built apps/rush/lib-commonjs/start-dev.js in an isolated Git-backed Rush repo - default reuse, cold teardown, bypass fallback teardown, and injected cleanup-failure scenarios all passed

The tests cover two successive iterations with default, persistent, one-shot, and changing policies. They also verify teardown before downstream execution, fallback teardown before afterExecuteIterationAsync, and cleanup-failure reporting/finalization for bypassed runners. The local CLI harness confirmed that the default policy reused one IPC child PID, the cold policy created a fresh PID per iteration and exited each child before its dependent ran, a bypassed cold runner exited before the iteration post-hook, and an injected fallback close failure reached the normal Rush failure summary without an uncaught exception.

Move IPC runner teardown into the operation graph's per-iteration options while preserving resident runners when no policy is supplied. Cover persistent, one-shot, changing, and default policies across successive iterations.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 8f21c8c8-452a-4922-aca3-fb40b007f948
@github-project-automation github-project-automation Bot moved this to Needs triage in Bug Triage Jul 20, 2026
@bmiddha bmiddha changed the title [rush] Add per-iteration runner persistence policy [rush] Add per-iteration IPCOperationRunner persistence policy Jul 20, 2026
Comment thread common/changes/@microsoft/rush/bmiddha-runner-persist-policy.json Outdated
Comment thread libraries/rush-lib/src/logic/operations/IPCOperationRunner.ts

@mojaza mojaza left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

Comment thread libraries/rush-lib/src/logic/operations/IOperationGraph.ts Outdated
Comment thread libraries/rush-lib/src/logic/operations/IPCOperationRunner.ts
bmiddha and others added 3 commits July 21, 2026 18:42
Close nonpersistent IPC runners before downstream execution, retain fallback cleanup for bypassed active runners, and rename the policy to shouldRunnerPersist.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 8f21c8c8-452a-4922-aca3-fb40b007f948
Replace the per-iteration predicate with a mutable record boolean configured alongside enabled. Keep runner teardown in OperationGraph so cold runners close before dependents execute, with a pre-after-iteration fallback for bypassed records.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 8f21c8c8-452a-4922-aca3-fb40b007f948
Convert fallback close failures into operation and iteration failures so final status and post-iteration hooks still run. Cover the bypassed-runner failure path with an ordering regression test.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 8f21c8c8-452a-4922-aca3-fb40b007f948
@bmiddha bmiddha changed the title [rush] Add per-iteration IPCOperationRunner persistence policy [rush] Add per-iteration runner persistence control Jul 27, 2026
Close terminal records' output resources before iteration summary hooks. This lets runner cleanup failures on bypassed records be summarized without an open StdioSummarizer exception.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 8f21c8c8-452a-4922-aca3-fb40b007f948
Assistant-model: GPT-5.6 Sol

@mojaza mojaza left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM Overall!

recordsToClose.push(record);
}
}
function reportRunnerCleanupFailure(record: OperationExecutionRecord, error: Error): void {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If a non-persistent runner fails to close at end-of-iteration (not mid-execution), should this always turn the iteration into a Failure, or should it be a Warning?

Comment on lines +890 to +900
await Async.forEachAsync(
recordsToClose,
async (record: OperationExecutionRecord) => {
try {
await this.closeRunnersAsync([record.operation]);
} catch (e) {
reportRunnerCleanupFailure(record, e);
}
},
{ concurrency: this.parallelism }
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are runners designed to handle concurrent closeAsync() calls if they're still being used elsewhere? Seems handled by the fact that non-persistent runners are closed immediately after completion (lines 1059-1068), but worth documenting.

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

Labels

None yet

Projects

Status: Needs triage

Development

Successfully merging this pull request may close these issues.

3 participants