diff --git a/images/Dockerfile b/images/Dockerfile index 6fa4b1dcc0d..c468b8d025d 100644 --- a/images/Dockerfile +++ b/images/Dockerfile @@ -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 diff --git a/patches/last_processed_commit.txt b/patches/last_processed_commit.txt index bf265322436..50b48bf13bb 100644 --- a/patches/last_processed_commit.txt +++ b/patches/last_processed_commit.txt @@ -1 +1 @@ -ce8ce410b0d8449a23cddb16ab38e654c4438dd7 +40dd583defe00b07f45ff976c95b564f7929bf69 diff --git a/patches/runner-main-sdk8-ppc64le.patch b/patches/runner-main-sdk8-ppc64le.patch index cb2f63cd2b6..192c69836d5 100644 --- a/patches/runner-main-sdk8-ppc64le.patch +++ b/patches/runner-main-sdk8-ppc64le.patch @@ -16,7 +16,7 @@ index 9c069b12..d26b0dc2 100644 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 @@ -284,4 +284,4 @@ index 056a312e..3f9a3679 100644 -# From upstream commit: ce8ce410b0d8449a23cddb16ab38e654c4438dd7 +# From upstream commit: 40dd583defe00b07f45ff976c95b564f7929bf69 diff --git a/patches/runner-main-sdk8-s390x.patch b/patches/runner-main-sdk8-s390x.patch index cb2f63cd2b6..192c69836d5 100644 --- a/patches/runner-main-sdk8-s390x.patch +++ b/patches/runner-main-sdk8-s390x.patch @@ -16,7 +16,7 @@ index 9c069b12..d26b0dc2 100644 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 @@ -284,4 +284,4 @@ index 056a312e..3f9a3679 100644 -# From upstream commit: ce8ce410b0d8449a23cddb16ab38e654c4438dd7 +# From upstream commit: 40dd583defe00b07f45ff976c95b564f7929bf69 diff --git a/src/Misc/externals.sh b/src/Misc/externals.sh index 8806a20fa8f..874c39394bf 100755 --- a/src/Misc/externals.sh +++ b/src/Misc/externals.sh @@ -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() { diff --git a/src/Runner.Worker/ExecutionContext.cs b/src/Runner.Worker/ExecutionContext.cs index 3a3754fa7e7..f4a020c475e 100644 --- a/src/Runner.Worker/ExecutionContext.cs +++ b/src/Runner.Worker/ExecutionContext.cs @@ -77,8 +77,7 @@ public interface IExecutionContext : IRunnerService List StepEnvironmentOverrides { get; } - ExecutionContext Root { get; } - ExecutionContext Parent { get; } + IExecutionContext Root { get; } // Initialize void InitializeJob(Pipelines.AgentJobRequestMessage message, CancellationToken token); @@ -251,7 +250,9 @@ public string ResultCode } } - public ExecutionContext Root + IExecutionContext IExecutionContext.Root => Root; + + private ExecutionContext Root { get { @@ -266,13 +267,7 @@ public ExecutionContext Root } } - public ExecutionContext Parent - { - get - { - return _parentExecutionContext; - } - } + public JobContext JobContext { diff --git a/src/Runner.Worker/PipelineTemplateEvaluatorWrapper.cs b/src/Runner.Worker/PipelineTemplateEvaluatorWrapper.cs index d560e45c845..7714b02fd06 100644 --- a/src/Runner.Worker/PipelineTemplateEvaluatorWrapper.cs +++ b/src/Runner.Worker/PipelineTemplateEvaluatorWrapper.cs @@ -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; @@ -226,8 +227,12 @@ internal TLegacy EvaluateAndCompare( Func newEvaluator, Func 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); @@ -261,7 +266,7 @@ internal TLegacy EvaluateAndCompare( } // Capture cancellation state after evaluation - var cancellationRequestedAfter = _context.CancellationToken.IsCancellationRequested; + var cancellationRequestedAfter = rootCancellationToken.IsCancellationRequested; // Compare results or exceptions bool hasMismatch = false; diff --git a/src/Test/L0/Worker/PipelineTemplateEvaluatorWrapperL0.cs b/src/Test/L0/Worker/PipelineTemplateEvaluatorWrapperL0.cs index 0a7427ced0e..72d684c1e34 100644 --- a/src/Test/L0/Worker/PipelineTemplateEvaluatorWrapperL0.cs +++ b/src/Test/L0/Worker/PipelineTemplateEvaluatorWrapperL0.cs @@ -19,6 +19,7 @@ namespace GitHub.Runner.Common.Tests.Worker public sealed class PipelineTemplateEvaluatorWrapperL0 { private CancellationTokenSource _ecTokenSource; + private CancellationTokenSource _rootTokenSource; private Mock _ec; private TestHostContext _hc; @@ -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. @@ -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)); @@ -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( + "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")] @@ -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); @@ -877,6 +917,9 @@ private void Setup([CallerMemberName] string name = "") WriteDebug = true, }); _ec.Setup(x => x.CancellationToken).Returns(_ecTokenSource.Token); + var rootEc = new Mock(); + 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(), It.IsAny())).Callback((string tag, string message) => { _hc.GetTrace().Info($"{tag}{message}"); }); @@ -885,6 +928,8 @@ private void Setup([CallerMemberName] string name = "") private void Teardown() { + _ecTokenSource?.Dispose(); + _rootTokenSource?.Dispose(); _hc?.Dispose(); } }