Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions images/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ ARG TARGETOS
ARG TARGETARCH
ARG RUNNER_VERSION
ARG RUNNER_CONTAINER_HOOKS_VERSION=0.7.0
ARG DOCKER_VERSION=29.2.0
ARG BUILDX_VERSION=0.31.1
ARG DOCKER_VERSION=29.3.0
ARG BUILDX_VERSION=0.32.1

RUN apt update -y && apt install curl unzip -y

Expand Down
2 changes: 1 addition & 1 deletion patches/last_processed_commit.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
ce8ce410b0d8449a23cddb16ab38e654c4438dd7
40dd583defe00b07f45ff976c95b564f7929bf69
4 changes: 2 additions & 2 deletions patches/runner-main-sdk8-ppc64le.patch
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ index 9c069b12..d26b0dc2 100644
<!-- Set TRACE/DEBUG vars -->
<PropertyGroup>
diff --git a/src/Misc/externals.sh b/src/Misc/externals.sh
index 8806a20f..1ade956a 100755
index 874c3939..575a28e8 100755
--- a/src/Misc/externals.sh
+++ b/src/Misc/externals.sh
@@ -187,3 +187,13 @@ fi
Expand Down Expand Up @@ -284,4 +284,4 @@ index 056a312e..3f9a3679 100644
<Exec Command="%22$(DesktopMSBuild)%22 Runner.Service/Windows/RunnerService.csproj /p:Configuration=$(BUILDCONFIG) /p:PackageRuntime=$(PackageRuntime) /p:OutputPath=%22$(MSBuildProjectDirectory)/../_layout/bin%22" ConsoleToMSBuild="true" Condition="'$(PackageRuntime)' == 'win-x64' Or '$(PackageRuntime)' == 'win-x86' Or '$(PackageRuntime)' == 'win-arm64'" />
</Target>

# From upstream commit: ce8ce410b0d8449a23cddb16ab38e654c4438dd7
# From upstream commit: 40dd583defe00b07f45ff976c95b564f7929bf69
4 changes: 2 additions & 2 deletions patches/runner-main-sdk8-s390x.patch
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ index 9c069b12..d26b0dc2 100644
<!-- Set TRACE/DEBUG vars -->
<PropertyGroup>
diff --git a/src/Misc/externals.sh b/src/Misc/externals.sh
index 8806a20f..1ade956a 100755
index 874c3939..575a28e8 100755
--- a/src/Misc/externals.sh
+++ b/src/Misc/externals.sh
@@ -187,3 +187,13 @@ fi
Expand Down Expand Up @@ -284,4 +284,4 @@ index 056a312e..3f9a3679 100644
<Exec Command="%22$(DesktopMSBuild)%22 Runner.Service/Windows/RunnerService.csproj /p:Configuration=$(BUILDCONFIG) /p:PackageRuntime=$(PackageRuntime) /p:OutputPath=%22$(MSBuildProjectDirectory)/../_layout/bin%22" ConsoleToMSBuild="true" Condition="'$(PackageRuntime)' == 'win-x64' Or '$(PackageRuntime)' == 'win-x86' Or '$(PackageRuntime)' == 'win-arm64'" />
</Target>

# From upstream commit: ce8ce410b0d8449a23cddb16ab38e654c4438dd7
# From upstream commit: 40dd583defe00b07f45ff976c95b564f7929bf69
2 changes: 1 addition & 1 deletion src/Misc/externals.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ NODE_URL=https://nodejs.org/dist
NODE_ALPINE_URL=https://github.com/actions/alpine_nodejs/releases/download
# When you update Node versions you must also create a new release of alpine_nodejs at that updated version.
# Follow the instructions here: https://github.com/actions/alpine_nodejs?tab=readme-ov-file#getting-started
NODE20_VERSION="20.20.0"
NODE20_VERSION="20.20.1"
NODE24_VERSION="24.14.0"

get_abs_path() {
Expand Down
15 changes: 5 additions & 10 deletions src/Runner.Worker/ExecutionContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,7 @@ public interface IExecutionContext : IRunnerService

List<string> StepEnvironmentOverrides { get; }

ExecutionContext Root { get; }
ExecutionContext Parent { get; }
IExecutionContext Root { get; }

// Initialize
void InitializeJob(Pipelines.AgentJobRequestMessage message, CancellationToken token);
Expand Down Expand Up @@ -251,7 +250,9 @@ public string ResultCode
}
}

public ExecutionContext Root
IExecutionContext IExecutionContext.Root => Root;

private ExecutionContext Root
{
get
{
Expand All @@ -266,13 +267,7 @@ public ExecutionContext Root
}
}

public ExecutionContext Parent
{
get
{
return _parentExecutionContext;
}
}


public JobContext JobContext
{
Expand Down
11 changes: 8 additions & 3 deletions src/Runner.Worker/PipelineTemplateEvaluatorWrapper.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Threading;
using GitHub.Actions.WorkflowParser;
using GitHub.DistributedTask.Expressions2;
using GitHub.DistributedTask.ObjectTemplating.Tokens;
Expand Down Expand Up @@ -226,8 +227,12 @@ internal TLegacy EvaluateAndCompare<TLegacy, TNew>(
Func<TNew> newEvaluator,
Func<TLegacy, TNew, bool> resultComparer)
{
// Capture cancellation state before evaluation
var cancellationRequestedBefore = _context.CancellationToken.IsCancellationRequested;
// Use the root (job-level) cancellation token to detect cancellation race conditions.
// The step-level token only fires on step timeout, not on job cancellation.
// Job cancellation mutates JobContext.Status which expression functions read,
// so we need the root token to properly detect cancellation between evaluator runs.
var rootCancellationToken = _context.Root?.CancellationToken ?? CancellationToken.None;
var cancellationRequestedBefore = rootCancellationToken.IsCancellationRequested;

// Legacy evaluator
var legacyException = default(Exception);
Expand Down Expand Up @@ -261,7 +266,7 @@ internal TLegacy EvaluateAndCompare<TLegacy, TNew>(
}

// Capture cancellation state after evaluation
var cancellationRequestedAfter = _context.CancellationToken.IsCancellationRequested;
var cancellationRequestedAfter = rootCancellationToken.IsCancellationRequested;

// Compare results or exceptions
bool hasMismatch = false;
Expand Down
49 changes: 47 additions & 2 deletions src/Test/L0/Worker/PipelineTemplateEvaluatorWrapperL0.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ namespace GitHub.Runner.Common.Tests.Worker
public sealed class PipelineTemplateEvaluatorWrapperL0
{
private CancellationTokenSource _ecTokenSource;
private CancellationTokenSource _rootTokenSource;
private Mock<IExecutionContext> _ec;
private TestHostContext _hc;

Expand Down Expand Up @@ -65,7 +66,7 @@ public void EvaluateAndCompare_SkipsMismatchRecording_WhenCancellationOccursDuri

var wrapper = new PipelineTemplateEvaluatorWrapper(_hc, _ec.Object, allowServiceContainerCommand: false);

// Call EvaluateAndCompare directly: the new evaluator cancels the token
// Call EvaluateAndCompare directly: the new evaluator cancels the root token
// and returns a different value, forcing hasMismatch = true.
// Because cancellation flipped during the evaluation window, the
// mismatch should be skipped.
Expand All @@ -74,7 +75,7 @@ public void EvaluateAndCompare_SkipsMismatchRecording_WhenCancellationOccursDuri
() => "legacy-value",
() =>
{
_ecTokenSource.Cancel();
_rootTokenSource.Cancel();
return "different-value";
},
(legacy, @new) => string.Equals(legacy, @new, StringComparison.Ordinal));
Expand All @@ -88,6 +89,43 @@ public void EvaluateAndCompare_SkipsMismatchRecording_WhenCancellationOccursDuri
}
}

[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public void EvaluateAndCompare_SkipsMismatchRecording_WhenRootCancellationOccursBetweenEvaluators()
{
// Simulates job-level cancellation firing between legacy and new evaluator runs.
// Root is mocked with a separate CancellationTokenSource to exercise the
// _context.Root?.CancellationToken path (the job-level token).
try
{
Setup();
_ec.Object.Global.Variables.Set(Constants.Runner.Features.CompareWorkflowParser, "true");

var wrapper = new PipelineTemplateEvaluatorWrapper(_hc, _ec.Object, allowServiceContainerCommand: false);

// Legacy evaluator cancels the root token (simulating job cancel) and returns a value.
// The new evaluator returns a different value. The mismatch should be skipped.
var result = wrapper.EvaluateAndCompare<string, string>(
"TestRootCancellationSkip",
() =>
{
var legacyValue = "legacy-value";
_rootTokenSource.Cancel();
return legacyValue;
},
() => "different-value",
(legacy, @new) => string.Equals(legacy, @new, StringComparison.Ordinal));

Assert.Equal("legacy-value", result);
Assert.False(_ec.Object.Global.HasTemplateEvaluatorMismatch);
}
finally
{
Teardown();
}
}

[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
Expand Down Expand Up @@ -862,6 +900,8 @@ private void Setup([CallerMemberName] string name = "")
{
_ecTokenSource?.Dispose();
_ecTokenSource = new CancellationTokenSource();
_rootTokenSource?.Dispose();
_rootTokenSource = new CancellationTokenSource();

_hc = new TestHostContext(this, name);

Expand All @@ -877,6 +917,9 @@ private void Setup([CallerMemberName] string name = "")
WriteDebug = true,
});
_ec.Setup(x => x.CancellationToken).Returns(_ecTokenSource.Token);
var rootEc = new Mock<IExecutionContext>();
rootEc.Setup(x => x.CancellationToken).Returns(_rootTokenSource.Token);
_ec.Setup(x => x.Root).Returns(rootEc.Object);
_ec.Setup(x => x.ExpressionValues).Returns(expressionValues);
_ec.Setup(x => x.ExpressionFunctions).Returns(expressionFunctions);
_ec.Setup(x => x.Write(It.IsAny<string>(), It.IsAny<string>())).Callback((string tag, string message) => { _hc.GetTrace().Info($"{tag}{message}"); });
Expand All @@ -885,6 +928,8 @@ private void Setup([CallerMemberName] string name = "")

private void Teardown()
{
_ecTokenSource?.Dispose();
_rootTokenSource?.Dispose();
_hc?.Dispose();
}
}
Expand Down