Extract typed Helix Job Monitor lineage model - #17223
Conversation
Represent stage attempts, logical work streams, and job incarnations explicitly, and move lineage selection and ordering out of MonitorState without changing monitor behavior.\n\nCo-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>\nCopilot-Session: baa969a4-1d79-4b6a-a3f0-4d349d768b5e
There was a problem hiding this comment.
Pull request overview
This PR extracts a typed, pure Helix Job Monitor lineage/identity model (stage-attempt, work-stream identity, and job-incarnation concepts) and moves lineage traversal, latest-incarnation selection, and deterministic ordering out of MonitorState, while preserving existing monitor behavior.
Changes:
- Introduced
JobLineagewith typedStageAttempt,WorkStreamIdentity, andJobIncarnation, and migrated callers off the previousMonitorStatelineage helpers. - Updated
MonitorState,JobMonitorRunner, andStatusReporterto consume lineage snapshots rather than owning lineage algorithms. - Added focused table-driven unit tests covering malformed, incomplete, linked/unlinked, and cyclic lineage scenarios.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| src/Microsoft.DotNet.Helix/Sdk.Tests/Microsoft.DotNet.Helix.Sdk.Tests/JobLineageTests.cs | Adds table-driven coverage for the new typed lineage/identity behavior and determinism. |
| src/Microsoft.DotNet.Helix/JobMonitor/StatusReporter.cs | Switches timeout reporting to use JobLineage to identify latest lineage leaves. |
| src/Microsoft.DotNet.Helix/JobMonitor/MonitorState.cs | Removes embedded lineage algorithms and transitions work-item outcome keys to WorkStreamIdentity. |
| src/Microsoft.DotNet.Helix/JobMonitor/JobMonitorRunner.cs | Uses JobLineage for per-stream/latest selection and ordering; introduces typed stage-attempt comparisons. |
| src/Microsoft.DotNet.Helix/JobMonitor/JobLineage.cs | New pure lineage/identity model (stage attempt parsing, work-stream identity, incarnation classification, traversal/order helpers). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Treat stage attempts as optional positive integers, reject malformed values up front, and rely on one metadata contract per build instead of preserving mixed-version fallbacks.\n\nCo-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>\nCopilot-Session: baa969a4-1d79-4b6a-a3f0-4d349d768b5e
|
Simplified in \c31f6695\ based on the invariant that a build cannot mix Job Monitor metadata contracts: stage attempts are now optional positive integers, malformed values fail at runner construction, and mixed missing/present attempt metadata is no longer treated as current. The poll loop also orders the existing leaf incarnations directly instead of constructing a second \JobLineage. |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 9 out of 9 changed files in this pull request and generated 2 comments.
Comments suppressed due to low confidence (1)
src/Microsoft.DotNet.Helix/JobMonitor/JobMonitorRunner.cs:316
- This reconciliation pass comment says jobs are processed "oldest-incarnation first", but the ordering currently starts at StageAttempt and JobName only. If the intent is to preserve the previous OrderHelixJobsOldToNew semantics (lineage depth, then stage attempt, then name), consider including LineageDepth in the ordering to match the stated behavior and keep ordering stable across refactors.
.Where(incarnation => completedJobNames.Contains(incarnation.Job.JobName))
.OrderBy(incarnation => incarnation.StageAttempt)
.ThenBy(incarnation => incarnation.Job.JobName, StringComparer.OrdinalIgnoreCase)
.ThenBy(incarnation => incarnation.Job.JobName, StringComparer.Ordinal);
| return new JobIncarnation( | ||
| job, | ||
| GetWorkStream(job), | ||
| StageAttempt.ParseOptional(job.StageAttempt), | ||
| GetLineageDepth(job)); | ||
| } |
| private bool IsStageAttemptInScope(HelixJobInfo job) | ||
| => string.IsNullOrEmpty(_options.StageAttempt) | ||
| || string.IsNullOrEmpty(job.StageAttempt) | ||
| || string.Equals(job.StageAttempt, _options.StageAttempt, StringComparison.OrdinalIgnoreCase); | ||
| { | ||
| StageAttempt? jobAttempt = StageAttempt.ParseOptional(job.StageAttempt); | ||
| return jobAttempt == _stageAttempt; | ||
| } |
Summary
MonitorStateThis is a behavior-preserving refactor and intentionally does not extract retry planning from #17175.
Closes #17174
Parent epic: #17171