Skip to content

Commit 7d73744

Browse files
lokesh755rentziassCopilot
authored
Bump to 2.335.1 (#4484)
Co-authored-by: Francesco Renzi <rentziass@github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 0d31056 commit 7d73744

4 files changed

Lines changed: 116 additions & 6 deletions

File tree

releaseVersion

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
2.335.0
1+
2.335.1

src/Runner.Worker/BackgroundStepCoordinator.cs

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,11 @@ public sealed class BackgroundStepCoordinator : RunnerService, IBackgroundStepCo
3232
// IDs of background steps that have already been completed (waited on or canceled).
3333
// Used to avoid waiting on or flushing the same step more than once.
3434
private readonly HashSet<string> _completedStepIds = new();
35+
36+
// IDs of background steps that were explicitly canceled via a `cancel` control step.
37+
// These steps are expected to be canceled, so their (Canceled) result must not be
38+
// merged into the overall job result.
39+
private readonly HashSet<string> _explicitlyCanceledStepIds = new();
3540
private SemaphoreSlim _backgroundSlotSemaphore = new SemaphoreSlim(DefaultMaxBackgroundSteps);
3641

3742
/// <summary>
@@ -41,6 +46,7 @@ public void InitializeCoordinator(int maxConcurrent)
4146
{
4247
_backgroundSteps.Clear();
4348
_completedStepIds.Clear();
49+
_explicitlyCanceledStepIds.Clear();
4450
var max = maxConcurrent > 0 ? maxConcurrent : DefaultMaxBackgroundSteps;
4551
_backgroundSlotSemaphore = new SemaphoreSlim(max);
4652
}
@@ -85,6 +91,9 @@ public void StartBackgroundStep(IStep step, CancellationToken jobCancellationTok
8591
// Safety net
8692
// -----------------------------------------------------------------
8793

94+
// Drain any background steps that weren't already waited on by an explicit wait/cancel
95+
// control step, then merge the final results of all background steps into a single result
96+
// for the caller to fold into the job result.
8897
public async Task<TaskResult> WaitForUnwaitedStepsAsync(CancellationToken cancellationToken)
8998
{
9099
var unwaitedIds = _backgroundSteps.Keys.Where(id => !_completedStepIds.Contains(id)).ToList();
@@ -95,14 +104,26 @@ public async Task<TaskResult> WaitForUnwaitedStepsAsync(CancellationToken cancel
95104
CompleteWaitedSteps(unwaitedIds);
96105
}
97106

98-
// Report the merged result of all background steps; the caller merges this into the job result.
99107
var result = TaskResult.Succeeded;
100-
foreach (var (_, (step, _, _)) in _backgroundSteps)
108+
foreach (var (stepId, (step, _, _)) in _backgroundSteps)
101109
{
102-
if (step.ExecutionContext.Result.HasValue)
110+
// A step that succeeded does not set a Result by default, so a missing
111+
// value means the step succeeded and there is nothing to merge.
112+
if (!step.ExecutionContext.Result.HasValue)
113+
{
114+
continue;
115+
}
116+
117+
// A step explicitly canceled via a `cancel` control step is expected to be canceled,
118+
// so a Canceled result must not influence the overall job result. However, if the step
119+
// failed (e.g. before the cancellation took effect), that failure should still count.
120+
if (_explicitlyCanceledStepIds.Contains(stepId) &&
121+
step.ExecutionContext.Result.Value == TaskResult.Canceled)
103122
{
104-
result = TaskResultUtil.MergeTaskResults(result, step.ExecutionContext.Result.Value);
123+
continue;
105124
}
125+
126+
result = TaskResultUtil.MergeTaskResults(result, step.ExecutionContext.Result.Value);
106127
}
107128

108129
if (result != TaskResult.Succeeded)
@@ -256,6 +277,13 @@ private async Task CancelStepsAsync(string[] cancelStepIds)
256277
return;
257278
}
258279

280+
// Mark these steps as expected-to-be-canceled so their result does not
281+
// affect the overall job result.
282+
foreach (var id in cancelStepIds)
283+
{
284+
_explicitlyCanceledStepIds.Add(id);
285+
}
286+
259287
var idsToCancel = cancelStepIds
260288
.Where(id => _backgroundSteps.ContainsKey(id) && !_backgroundSteps[id].Task.IsCompleted)
261289
.ToArray();

src/Test/L0/Worker/BackgroundStepsL0.cs

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -372,6 +372,88 @@ public async Task CancelStepPublishesCanceledBackgroundExternalId()
372372
}
373373
}
374374

375+
[Fact]
376+
[Trait("Level", "L0")]
377+
[Trait("Category", "Worker")]
378+
public async Task CanceledBackgroundStepDoesNotAffectJobResult()
379+
{
380+
using (TestHostContext hc = CreateTestContext())
381+
{
382+
// Arrange: a background step that runs until explicitly canceled. When canceled it
383+
// reports TaskResult.Canceled, but since the cancellation is expected (driven by a
384+
// cancel control step), it must not impact the overall job result.
385+
using var stepCts = new CancellationTokenSource();
386+
387+
var bgStep = CreateStep(hc, TaskResult.Succeeded, "success()", name: "server", contextName: "server", isBackground: true);
388+
var bgStepContext = Mock.Get(bgStep.Object.ExecutionContext);
389+
bgStepContext.Setup(x => x.CancellationToken).Returns(stepCts.Token);
390+
bgStepContext.Setup(x => x.CancelToken()).Callback(() => stepCts.Cancel());
391+
bgStep.Setup(x => x.RunAsync()).Returns(async () =>
392+
{
393+
await Task.Delay(TimeSpan.FromSeconds(2), stepCts.Token);
394+
});
395+
bgStep.Setup(x => x.Action).Returns(new GitHub.DistributedTask.Pipelines.ActionStep()
396+
{
397+
Name = "server",
398+
Id = Guid.NewGuid(),
399+
ContextName = "server",
400+
Background = true,
401+
});
402+
403+
var cancelStep = CreateCancelStep(hc, "server");
404+
405+
_ec.Object.Result = null;
406+
_ec.Setup(x => x.JobSteps).Returns(new Queue<IStep>(new IStep[]
407+
{
408+
bgStep.Object, cancelStep
409+
}));
410+
411+
// Act
412+
await _stepsRunner.RunAsync(jobContext: _ec.Object);
413+
414+
// Assert: the canceled background step reported Canceled, but the job result is unaffected.
415+
Assert.Equal(TaskResult.Canceled, bgStep.Object.ExecutionContext.Result);
416+
Assert.Equal(TaskResult.Succeeded, _ec.Object.Result ?? TaskResult.Succeeded);
417+
}
418+
}
419+
420+
[Fact]
421+
[Trait("Level", "L0")]
422+
[Trait("Category", "Worker")]
423+
public async Task FailedBackgroundStepTargetedByCancelStillAffectsJobResult()
424+
{
425+
using (TestHostContext hc = CreateTestContext())
426+
{
427+
// Arrange: a background step that fails (e.g. before the cancel takes effect). Even
428+
// though a cancel control step targets it, its Failed result must still propagate to
429+
// the overall job result.
430+
var bgStep = CreateStep(hc, TaskResult.Failed, "success()", name: "server", contextName: "server", isBackground: true);
431+
bgStep.Setup(x => x.RunAsync()).Returns(Task.CompletedTask);
432+
bgStep.Setup(x => x.Action).Returns(new GitHub.DistributedTask.Pipelines.ActionStep()
433+
{
434+
Name = "server",
435+
Id = Guid.NewGuid(),
436+
ContextName = "server",
437+
Background = true,
438+
});
439+
440+
var cancelStep = CreateCancelStep(hc, "server");
441+
442+
_ec.Object.Result = null;
443+
_ec.Setup(x => x.JobSteps).Returns(new Queue<IStep>(new IStep[]
444+
{
445+
bgStep.Object, cancelStep
446+
}));
447+
448+
// Act
449+
await _stepsRunner.RunAsync(jobContext: _ec.Object);
450+
451+
// Assert: the background step failed, so the job result reflects that failure.
452+
Assert.Equal(TaskResult.Failed, bgStep.Object.ExecutionContext.Result);
453+
Assert.Equal(TaskResult.Failed, _ec.Object.Result);
454+
}
455+
}
456+
375457
[Fact]
376458
[Trait("Level", "L0")]
377459
[Trait("Category", "Worker")]

src/runnerversion

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
2.335.0
1+
2.335.1

0 commit comments

Comments
 (0)