From a4f3762c7cad515b061088ca85ead4fa258811e0 Mon Sep 17 00:00:00 2001 From: Gregorius Soedharmo Date: Sat, 13 Dec 2025 03:17:34 +0700 Subject: [PATCH 1/9] Fix .NET 10 CLR shutdown hook breaking change --- Directory.Build.props | 1 + global.json | 11 +- .../Actor/TerminationSignalHandlerSpec.cs | 295 ++++++++++++++++++ src/core/Akka.Tests/Akka.Tests.csproj | 13 +- src/core/Akka/Actor/CoordinatedShutdown.cs | 75 +++-- .../Akka/Actor/TerminationSignalHandler.cs | 117 +++++++ 6 files changed, 472 insertions(+), 40 deletions(-) create mode 100644 src/core/Akka.Tests/Actor/TerminationSignalHandlerSpec.cs create mode 100644 src/core/Akka/Actor/TerminationSignalHandler.cs diff --git a/Directory.Build.props b/Directory.Build.props index 01f9fdd9fd6..b8fb710f57a 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -30,6 +30,7 @@ 2.60.0 0.15.4 net8.0 + net10.0 6.0.5 net48 netstandard2.0 diff --git a/global.json b/global.json index 6316ee7bf7a..42b44a1eda6 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,7 @@ { - "sdk": { - "version": "8.0.403", - "rollForward": "major" - } -} \ No newline at end of file + "sdk": { + "version": "10.0.0", + "rollForward": "major", + "allowPrerelease": true + } +} \ No newline at end of file diff --git a/src/core/Akka.Tests/Actor/TerminationSignalHandlerSpec.cs b/src/core/Akka.Tests/Actor/TerminationSignalHandlerSpec.cs new file mode 100644 index 00000000000..e7616bac00a --- /dev/null +++ b/src/core/Akka.Tests/Actor/TerminationSignalHandlerSpec.cs @@ -0,0 +1,295 @@ +//----------------------------------------------------------------------- +// +// Copyright (C) 2009-2022 Lightbend Inc. +// Copyright (C) 2013-2025 .NET Foundation +// +//----------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Threading.Tasks; +using Akka.Actor; +using Akka.Configuration; +using Akka.TestKit; +using Akka.TestKit.Extensions; +using Akka.Util.Internal; +using FluentAssertions; +using Xunit; +using Xunit.Abstractions; +using static Akka.Actor.CoordinatedShutdown; + +namespace Akka.Tests.Actor; + +/// +/// Tests for the CLR termination signal handling in . +/// +public class TerminationSignalHandlerSpec : AkkaSpec +{ + public TerminationSignalHandlerSpec(ITestOutputHelper output) : base(output) + { + } + + public ExtendedActorSystem ExtSys => Sys.AsInstanceOf(); + + private static readonly Phase EmptyPhase = new(ImmutableHashSet.Empty, TimeSpan.FromSeconds(10), true); + + /// + /// Test double for that allows simulating termination signals. + /// + private class TestTerminationSignalHandler : ITerminationSignalHandler + { + public Action RegisteredCallback { get; private set; } + public bool IsDisposed { get; private set; } + public int RegisterCallCount { get; private set; } + + public void Register(Action onTerminationSignal) + { + RegisterCallCount++; + RegisteredCallback = onTerminationSignal; + } + + public void SimulateTerminationSignal() + { + RegisteredCallback?.Invoke(); + } + + public void Dispose() + { + IsDisposed = true; + } + } + + [Fact(DisplayName = "CoordinatedShutdown should register handler when run-by-clr-shutdown-hook is enabled")] + public void CoordinatedShutdown_should_register_handler_when_enabled() + { + // Arrange + var phases = new Dictionary { { "a", EmptyPhase } }; + var coord = new CoordinatedShutdown(ExtSys, phases); + var testHandler = new TestTerminationSignalHandler(); + var conf = ConfigurationFactory.ParseString("run-by-clr-shutdown-hook = on"); + + // Act + CoordinatedShutdown.InitClrHook(Sys, conf, coord, testHandler); + + // Assert + testHandler.RegisterCallCount.Should().Be(1); + testHandler.RegisteredCallback.Should().NotBeNull(); + } + + [Fact(DisplayName = "CoordinatedShutdown should not register handler when run-by-clr-shutdown-hook is disabled")] + public void CoordinatedShutdown_should_not_register_handler_when_disabled() + { + // Arrange + var phases = new Dictionary { { "a", EmptyPhase } }; + var coord = new CoordinatedShutdown(ExtSys, phases); + var testHandler = new TestTerminationSignalHandler(); + var conf = ConfigurationFactory.ParseString("run-by-clr-shutdown-hook = off"); + + // Act + CoordinatedShutdown.InitClrHook(Sys, conf, coord, testHandler); + + // Assert + testHandler.RegisterCallCount.Should().Be(0); + testHandler.RegisteredCallback.Should().BeNull(); + } + + [Fact(DisplayName = "CoordinatedShutdown should run shutdown tasks when termination signal is received")] + public async Task CoordinatedShutdown_should_run_when_termination_signal_received() + { + // Arrange + var sys = ActorSystem.Create( + "TerminationSignalTest", + ConfigurationFactory.ParseString(@" + akka.coordinated-shutdown.terminate-actor-system = on + akka.coordinated-shutdown.run-by-clr-shutdown-hook = on + akka.coordinated-shutdown.run-by-actor-system-terminate = off")); + + try + { + var testHandler = new TestTerminationSignalHandler(); + var coord = CoordinatedShutdown.Get(sys); + + var taskExecuted = new TaskCompletionSource(); + coord.AddTask(PhaseBeforeServiceUnbind, "test-task", () => + { + taskExecuted.SetResult(true); + return Task.FromResult(Done.Instance); + }); + + // Re-initialize with test handler + var conf = sys.Settings.Config.GetConfig("akka.coordinated-shutdown"); + CoordinatedShutdown.InitClrHook(sys, conf, coord, testHandler); + + // Act - simulate termination signal + testHandler.SimulateTerminationSignal(); + + // Assert + var result = await taskExecuted.Task.AwaitWithTimeout(TimeSpan.FromSeconds(10)); + result.Should().BeTrue(); + coord.ShutdownReason.Should().Be(ClrExitReason.Instance); + } + finally + { + await sys.Terminate(); + } + } + + [Fact(DisplayName = "CoordinatedShutdown should set _runningClrHook flag during CLR shutdown")] + public async Task CoordinatedShutdown_should_set_running_flag_during_clr_shutdown() + { + // Arrange + var sys = ActorSystem.Create( + "RunningFlagTest", + ConfigurationFactory.ParseString(@" + akka.coordinated-shutdown.terminate-actor-system = on + akka.coordinated-shutdown.run-by-clr-shutdown-hook = on + akka.coordinated-shutdown.run-by-actor-system-terminate = off")); + + try + { + var testHandler = new TestTerminationSignalHandler(); + var coord = CoordinatedShutdown.Get(sys); + + var flagObserved = new TaskCompletionSource(); + coord.AddTask(PhaseBeforeServiceUnbind, "flag-check-task", () => + { + // The _runningClrHook flag should be set by now + // We can't directly access the private field, but we can verify + // the shutdown is running with ClrExitReason + flagObserved.SetResult(coord.ShutdownReason == ClrExitReason.Instance); + return Task.FromResult(Done.Instance); + }); + + var conf = sys.Settings.Config.GetConfig("akka.coordinated-shutdown"); + CoordinatedShutdown.InitClrHook(sys, conf, coord, testHandler); + + // Act + testHandler.SimulateTerminationSignal(); + + // Assert + var result = await flagObserved.Task.AwaitWithTimeout(TimeSpan.FromSeconds(10)); + result.Should().BeTrue(); + } + finally + { + await sys.Terminate(); + } + } + + [Fact(DisplayName = "CoordinatedShutdown should dispose handler when ActorSystem terminates normally")] + public async Task CoordinatedShutdown_should_dispose_handler_on_normal_termination() + { + // Arrange + var sys = ActorSystem.Create( + "DisposeTest", + ConfigurationFactory.ParseString(@" + akka.coordinated-shutdown.terminate-actor-system = on + akka.coordinated-shutdown.run-by-clr-shutdown-hook = on + akka.coordinated-shutdown.run-by-actor-system-terminate = on")); + + var testHandler = new TestTerminationSignalHandler(); + var coord = CoordinatedShutdown.Get(sys); + var conf = sys.Settings.Config.GetConfig("akka.coordinated-shutdown"); + CoordinatedShutdown.InitClrHook(sys, conf, coord, testHandler); + + // Act - terminate system normally (not via signal) + await sys.Terminate(); + + // Give continuation time to run + await Task.Delay(100); + + // Assert + testHandler.IsDisposed.Should().BeTrue(); + } + + [Fact(DisplayName = "CoordinatedShutdown CLR hooks should only execute once even if signal fires multiple times")] + public async Task CoordinatedShutdown_clr_hooks_should_only_execute_once() + { + // Arrange + var sys = ActorSystem.Create( + "IdempotencyTest", + ConfigurationFactory.ParseString(@" + akka.coordinated-shutdown.terminate-actor-system = on + akka.coordinated-shutdown.run-by-clr-shutdown-hook = on + akka.coordinated-shutdown.run-by-actor-system-terminate = off")); + + try + { + var testHandler = new TestTerminationSignalHandler(); + var coord = CoordinatedShutdown.Get(sys); + + var executionCount = 0; + coord.AddTask(PhaseBeforeServiceUnbind, "count-task", () => + { + executionCount++; + return Task.FromResult(Done.Instance); + }); + + var conf = sys.Settings.Config.GetConfig("akka.coordinated-shutdown"); + CoordinatedShutdown.InitClrHook(sys, conf, coord, testHandler); + + // Act - simulate multiple termination signals + testHandler.SimulateTerminationSignal(); + testHandler.SimulateTerminationSignal(); + testHandler.SimulateTerminationSignal(); + + // Wait for shutdown to complete + await sys.WhenTerminated.AwaitWithTimeout(TimeSpan.FromSeconds(10)); + + // Assert - task should only have executed once + executionCount.Should().Be(1); + } + finally + { + if (!sys.WhenTerminated.IsCompleted) + await sys.Terminate(); + } + } + + [Fact(DisplayName = "CoordinatedShutdown should handle exceptions in shutdown tasks gracefully")] + public async Task CoordinatedShutdown_should_handle_task_exceptions_gracefully() + { + // Arrange + var sys = ActorSystem.Create( + "ExceptionTest", + ConfigurationFactory.ParseString(@" + akka.coordinated-shutdown.terminate-actor-system = on + akka.coordinated-shutdown.run-by-clr-shutdown-hook = on + akka.coordinated-shutdown.run-by-actor-system-terminate = off")); + + try + { + var testHandler = new TestTerminationSignalHandler(); + var coord = CoordinatedShutdown.Get(sys); + + var secondTaskExecuted = new TaskCompletionSource(); + + coord.AddTask(PhaseBeforeServiceUnbind, "failing-task", () => + { + throw new Exception("Test exception"); + }); + + coord.AddTask(PhaseServiceUnbind, "second-task", () => + { + secondTaskExecuted.SetResult(true); + return Task.FromResult(Done.Instance); + }); + + var conf = sys.Settings.Config.GetConfig("akka.coordinated-shutdown"); + CoordinatedShutdown.InitClrHook(sys, conf, coord, testHandler); + + // Act + testHandler.SimulateTerminationSignal(); + + // Assert - second task should still execute despite first task throwing + var result = await secondTaskExecuted.Task.AwaitWithTimeout(TimeSpan.FromSeconds(10)); + result.Should().BeTrue(); + } + finally + { + if (!sys.WhenTerminated.IsCompleted) + await sys.Terminate(); + } + } +} \ No newline at end of file diff --git a/src/core/Akka.Tests/Akka.Tests.csproj b/src/core/Akka.Tests/Akka.Tests.csproj index 5d5455f948e..70e11729d08 100644 --- a/src/core/Akka.Tests/Akka.Tests.csproj +++ b/src/core/Akka.Tests/Akka.Tests.csproj @@ -2,8 +2,8 @@ - $(NetFrameworkTestVersion);$(NetTestVersion) - $(NetTestVersion) + $(NetFrameworkTestVersion);$(NetTestVersion);$(NetTenTestVersion) + $(NetTestVersion);$(NetTenTestVersion) @@ -25,8 +25,17 @@ + + + + + $(DefineConstants);CORECLR + + $(DefineConstants);CORECLR + + diff --git a/src/core/Akka/Actor/CoordinatedShutdown.cs b/src/core/Akka/Actor/CoordinatedShutdown.cs index f0008de8981..5476dd152d1 100644 --- a/src/core/Akka/Actor/CoordinatedShutdown.cs +++ b/src/core/Akka/Actor/CoordinatedShutdown.cs @@ -706,56 +706,65 @@ internal static void InitPhaseActorSystemTerminate(ActorSystem system, Config co } } - // TODO: do we need to check for null or empty config here? /// - /// Initializes the CLR hook + /// Initializes the CLR hook for handling process termination signals. /// /// The actor system for this extension. /// The HOCON configuration. /// The plugin instance. - internal static void InitClrHook(ActorSystem system, Config conf, CoordinatedShutdown coord) + /// Optional signal handler for testing. If null, creates platform-appropriate handler. + internal static void InitClrHook(ActorSystem system, Config conf, CoordinatedShutdown coord, ITerminationSignalHandler signalHandler = null) { var runByClrShutdownHook = conf.GetBoolean("run-by-clr-shutdown-hook", false); - if (runByClrShutdownHook) + if (!runByClrShutdownHook) + return; + + // Use injected handler or create platform-appropriate one + signalHandler ??= CreateDefaultTerminationHandler(); + + // Register the signal handler to run CLR hooks when termination signal is received + signalHandler.Register(() => { - var exitTask = TerminateOnClrExit(coord); - // run all hooks during termination sequence - AppDomain.CurrentDomain.ProcessExit += exitTask; - system.WhenTerminated.ContinueWith(_ => - { - AppDomain.CurrentDomain.ProcessExit -= exitTask; - }); + // Must block - if this returns, process exits + coord.RunClrHooks().Wait(coord.TotalTimeout); + }); - coord.AddClrShutdownHook(() => + // Add the actual shutdown hook that performs coordinated shutdown + coord.AddClrShutdownHook(() => + { + coord._runningClrHook = true; + return Task.Run(() => { - coord._runningClrHook = true; - return Task.Run(() => + if (!system.WhenTerminated.IsCompleted) { - if (!system.WhenTerminated.IsCompleted) + coord.Log.Info("Starting coordinated shutdown from CLR termination hook."); + try { - coord.Log.Info("Starting coordinated shutdown from CLR termination hook."); - try - { - coord.Run(ClrExitReason.Instance).Wait(coord.TotalTimeout); - } - catch (Exception ex) - { - coord.Log.Warning("CoordinatedShutdown from CLR shutdown failed: {0}", ex.Message); - } + coord.Run(ClrExitReason.Instance).Wait(coord.TotalTimeout); + } + catch (Exception ex) + { + coord.Log.Warning("CoordinatedShutdown from CLR shutdown failed: {0}", ex.Message); } - return Done.Instance; - }); + } + return Done.Instance; }); - } + }); + + // Cleanup handler when system terminates normally + system.WhenTerminated.ContinueWith(_ => signalHandler.Dispose()); } - private static EventHandler TerminateOnClrExit(CoordinatedShutdown coord) + /// + /// Creates the appropriate termination signal handler for the current platform. + /// + private static ITerminationSignalHandler CreateDefaultTerminationHandler() { - return (_, _) => - { - // have to block, because if this method exits the process exits. - coord.RunClrHooks().Wait(coord.TotalTimeout); - }; +#if NET6_0_OR_GREATER + return new PosixTerminationSignalHandler(); +#else + return new LegacyTerminationSignalHandler(); +#endif } } } diff --git a/src/core/Akka/Actor/TerminationSignalHandler.cs b/src/core/Akka/Actor/TerminationSignalHandler.cs new file mode 100644 index 00000000000..9500cd42e2e --- /dev/null +++ b/src/core/Akka/Actor/TerminationSignalHandler.cs @@ -0,0 +1,117 @@ +//----------------------------------------------------------------------- +// +// Copyright (C) 2009-2022 Lightbend Inc. +// Copyright (C) 2013-2025 .NET Foundation +// +//----------------------------------------------------------------------- + +using System; +using System.Threading; +#if NET6_0_OR_GREATER +using System.Runtime.InteropServices; +#endif + +namespace Akka.Actor +{ + /// + /// Abstraction for handling process termination signals (SIGTERM, SIGHUP, ProcessExit). + /// Required for .NET 10 compatibility where ProcessExit no longer fires on SIGTERM. + /// + internal interface ITerminationSignalHandler : IDisposable + { + /// + /// Registers a callback to be invoked when a termination signal is received. + /// The callback will only be invoked once, even if multiple signals are received. + /// + /// The callback to invoke on termination signal. + void Register(Action onTerminationSignal); + } + +#if NET6_0_OR_GREATER + /// + /// .NET 6+ implementation using PosixSignalRegistration for proper signal handling. + /// Handles SIGTERM and SIGHUP signals, plus ProcessExit as fallback. + /// This is required for .NET 10 compatibility where ProcessExit no longer fires on SIGTERM. + /// + internal sealed class PosixTerminationSignalHandler : ITerminationSignalHandler + { + private PosixSignalRegistration? _sigtermRegistration; + private PosixSignalRegistration? _sighupRegistration; + private EventHandler? _processExitHandler; + private Action? _callback; + private int _invoked; + + /// + public void Register(Action onTerminationSignal) + { + _callback = onTerminationSignal; + + // Register POSIX signals (works on Unix/macOS/Windows in .NET 6+) + _sigtermRegistration = PosixSignalRegistration.Create( + PosixSignal.SIGTERM, OnSignalReceived); + + _sighupRegistration = PosixSignalRegistration.Create( + PosixSignal.SIGHUP, OnSignalReceived); + + // Keep ProcessExit as fallback for non-signal termination scenarios + _processExitHandler = (_, _) => InvokeCallback(); + AppDomain.CurrentDomain.ProcessExit += _processExitHandler; + } + + private void OnSignalReceived(PosixSignalContext context) + { + // Cancel default termination to allow graceful shutdown + context.Cancel = true; + InvokeCallback(); + } + + private void InvokeCallback() + { + // Ensure callback only runs once + if (Interlocked.CompareExchange(ref _invoked, 1, 0) == 0) + { + _callback?.Invoke(); + } + } + + /// + public void Dispose() + { + _sigtermRegistration?.Dispose(); + _sighupRegistration?.Dispose(); + if (_processExitHandler != null) + AppDomain.CurrentDomain.ProcessExit -= _processExitHandler; + } + } +#else + /// + /// Legacy implementation for .NET Standard 2.0 / .NET Framework. + /// Uses ProcessExit only (no POSIX signal support available). + /// + internal sealed class LegacyTerminationSignalHandler : ITerminationSignalHandler + { + private EventHandler? _processExitHandler; + private int _invoked; + + /// + public void Register(Action onTerminationSignal) + { + _processExitHandler = (_, _) => + { + if (Interlocked.CompareExchange(ref _invoked, 1, 0) == 0) + { + onTerminationSignal(); + } + }; + AppDomain.CurrentDomain.ProcessExit += _processExitHandler; + } + + /// + public void Dispose() + { + if (_processExitHandler != null) + AppDomain.CurrentDomain.ProcessExit -= _processExitHandler; + } + } +#endif +} From f8f329e35aafe6eff008f91ed62047ea92956553 Mon Sep 17 00:00:00 2001 From: Gregorius Soedharmo Date: Sat, 13 Dec 2025 03:27:33 +0700 Subject: [PATCH 2/9] fix global.json --- global.json | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/global.json b/global.json index 42b44a1eda6..53058434126 100644 --- a/global.json +++ b/global.json @@ -1,7 +1,6 @@ { "sdk": { - "version": "10.0.0", - "rollForward": "major", - "allowPrerelease": true + "version": "10.0.101", + "rollForward": "major" } } \ No newline at end of file From b6f3fc757e8e91cb3c240a19255d9b25e72f34dd Mon Sep 17 00:00:00 2001 From: Gregorius Soedharmo Date: Mon, 15 Dec 2025 22:24:10 +0700 Subject: [PATCH 3/9] Fix warning as errors --- src/core/Akka.Tests/Akka.Tests.csproj | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/core/Akka.Tests/Akka.Tests.csproj b/src/core/Akka.Tests/Akka.Tests.csproj index 70e11729d08..69c1e297646 100644 --- a/src/core/Akka.Tests/Akka.Tests.csproj +++ b/src/core/Akka.Tests/Akka.Tests.csproj @@ -20,8 +20,6 @@ - - From 914cc91dec82ebf090a5b585dddad25239485741 Mon Sep 17 00:00:00 2001 From: Gregorius Soedharmo Date: Wed, 17 Dec 2025 15:27:21 +0700 Subject: [PATCH 4/9] Install .NET SDK 8.0 --- build-system/azure-pipeline.mntr-template.yaml | 8 +++++++- build-system/azure-pipeline.template.yaml | 8 +++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/build-system/azure-pipeline.mntr-template.yaml b/build-system/azure-pipeline.mntr-template.yaml index f1c237867bc..5365ca079d6 100644 --- a/build-system/azure-pipeline.mntr-template.yaml +++ b/build-system/azure-pipeline.mntr-template.yaml @@ -16,11 +16,17 @@ jobs: vmImage: ${{ parameters.vmImage }} steps: - task: UseDotNet@2 - displayName: 'Use .NET' + displayName: 'Use .NET SDK from global.json' inputs: packageType: 'sdk' useGlobalJson: true + - task: UseDotNet@2 + displayName: 'Install .NET 8.0 Runtime' + inputs: + packageType: 'runtime' + version: '8.0.x' + # Set the Incrementalist base branch based on PR target branch - pwsh: | if ('$(Build.Reason)' -eq 'PullRequest') { diff --git a/build-system/azure-pipeline.template.yaml b/build-system/azure-pipeline.template.yaml index 09d1ee5d667..4298afbc842 100644 --- a/build-system/azure-pipeline.template.yaml +++ b/build-system/azure-pipeline.template.yaml @@ -23,11 +23,17 @@ jobs: value: true steps: - task: UseDotNet@2 - displayName: 'Use .NET' + displayName: 'Use .NET SDK from global.json' inputs: packageType: 'sdk' useGlobalJson: true + - task: UseDotNet@2 + displayName: 'Install .NET 8.0 Runtime' + inputs: + packageType: 'runtime' + version: '8.0.x' + # Set the Incrementalist base branch based on PR target branch - pwsh: | if ('$(Build.Reason)' -eq 'PullRequest') { From dc548209e2afdc3cb766c91889dae456cba079da Mon Sep 17 00:00:00 2001 From: Gregorius Soedharmo Date: Wed, 17 Dec 2025 15:27:51 +0700 Subject: [PATCH 5/9] Add very targetted .NET 10 CI/CD run --- build-system/pr-validation.yaml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/build-system/pr-validation.yaml b/build-system/pr-validation.yaml index 1a49c58fd0c..fe83f2541ff 100644 --- a/build-system/pr-validation.yaml +++ b/build-system/pr-validation.yaml @@ -93,6 +93,24 @@ jobs: outputDirectory: "TestResults" artifactName: "net_tests_linux-$(Build.BuildId)" + - template: azure-pipeline.template.yaml + parameters: + name: "net10_tests_windows" + displayName: ".NET 10 CLR Hook Tests (Windows)" + vmImage: "windows-latest" + command: "dotnet incrementalist run --config .incrementalist/testsOnly.json --branch $(IncrementalistBaseBranch) -- test src/core/Akka.Tests/Akka.Tests.csproj -c Release --no-build --framework net10.0 --filter FullyQualifiedName~TerminationSignalHandlerSpec --logger:trx --results-directory TestResults" + outputDirectory: "TestResults" + artifactName: "net10_tests_windows-$(Build.BuildId)" + + - template: azure-pipeline.template.yaml + parameters: + name: "net10_tests_linux" + displayName: ".NET 10 CLR Hook Tests (Linux)" + vmImage: "ubuntu-latest" + command: "dotnet incrementalist run --config .incrementalist/testsOnly.json --branch $(IncrementalistBaseBranch) -- test src/core/Akka.Tests/Akka.Tests.csproj -c Release --no-build --framework net10.0 --filter FullyQualifiedName~TerminationSignalHandlerSpec --logger:trx --results-directory TestResults" + outputDirectory: "TestResults" + artifactName: "net10_tests_linux-$(Build.BuildId)" + - template: azure-pipeline.mntr-template.yaml parameters: name: "net_mntr_windows" From a27acf16c21074efe26d27ebe1ebd52510534b62 Mon Sep 17 00:00:00 2001 From: Gregorius Soedharmo Date: Wed, 17 Dec 2025 15:41:29 +0700 Subject: [PATCH 6/9] Fix CI/CD script --- .incrementalist/net10-Clr-testsOnly.json | 16 ++++++++++++++++ build-system/pr-validation.yaml | 4 ++-- 2 files changed, 18 insertions(+), 2 deletions(-) create mode 100644 .incrementalist/net10-Clr-testsOnly.json diff --git a/.incrementalist/net10-Clr-testsOnly.json b/.incrementalist/net10-Clr-testsOnly.json new file mode 100644 index 00000000000..ab5a4a99347 --- /dev/null +++ b/.incrementalist/net10-Clr-testsOnly.json @@ -0,0 +1,16 @@ +{ + "outputFile": "bin/output/incrementalist.txt", + "gitBranch": "dev", + "verbose": false, + "timeoutMinutes": 20, + "continueOnError": true, + "runInParallel": false, + "failOnNoProjects": false, + "skip": [ + "**/.Tests.MultiNode.csproj", + "src/examples/**" + ], + "target": [ + "**/Akka.Tests.csproj" + ] +} \ No newline at end of file diff --git a/build-system/pr-validation.yaml b/build-system/pr-validation.yaml index fe83f2541ff..b0477b50a28 100644 --- a/build-system/pr-validation.yaml +++ b/build-system/pr-validation.yaml @@ -98,7 +98,7 @@ jobs: name: "net10_tests_windows" displayName: ".NET 10 CLR Hook Tests (Windows)" vmImage: "windows-latest" - command: "dotnet incrementalist run --config .incrementalist/testsOnly.json --branch $(IncrementalistBaseBranch) -- test src/core/Akka.Tests/Akka.Tests.csproj -c Release --no-build --framework net10.0 --filter FullyQualifiedName~TerminationSignalHandlerSpec --logger:trx --results-directory TestResults" + command: "dotnet incrementalist run --config .incrementalist/net10-Clr-testsOnly.json --branch $(IncrementalistBaseBranch) -- test -c Release --no-build --framework net10.0 --filter FullyQualifiedName~TerminationSignalHandlerSpec --logger:trx --results-directory TestResults" outputDirectory: "TestResults" artifactName: "net10_tests_windows-$(Build.BuildId)" @@ -107,7 +107,7 @@ jobs: name: "net10_tests_linux" displayName: ".NET 10 CLR Hook Tests (Linux)" vmImage: "ubuntu-latest" - command: "dotnet incrementalist run --config .incrementalist/testsOnly.json --branch $(IncrementalistBaseBranch) -- test src/core/Akka.Tests/Akka.Tests.csproj -c Release --no-build --framework net10.0 --filter FullyQualifiedName~TerminationSignalHandlerSpec --logger:trx --results-directory TestResults" + command: "dotnet incrementalist run --config .incrementalist/net10-Clr-testsOnly.json --branch $(IncrementalistBaseBranch) -- test -c Release --no-build --framework net10.0 --filter FullyQualifiedName~TerminationSignalHandlerSpec --logger:trx --results-directory TestResults" outputDirectory: "TestResults" artifactName: "net10_tests_linux-$(Build.BuildId)" From 0125baf4cf3826e42e53e41f22adaadd777deef7 Mon Sep 17 00:00:00 2001 From: Gregorius Soedharmo Date: Thu, 18 Dec 2025 08:18:56 +0700 Subject: [PATCH 7/9] Code fix --- src/core/Akka/Actor/TerminationSignalHandler.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/Akka/Actor/TerminationSignalHandler.cs b/src/core/Akka/Actor/TerminationSignalHandler.cs index 9500cd42e2e..7083e4ccd1a 100644 --- a/src/core/Akka/Actor/TerminationSignalHandler.cs +++ b/src/core/Akka/Actor/TerminationSignalHandler.cs @@ -51,7 +51,7 @@ public void Register(Action onTerminationSignal) PosixSignal.SIGTERM, OnSignalReceived); _sighupRegistration = PosixSignalRegistration.Create( - PosixSignal.SIGHUP, OnSignalReceived); + PosixSignal.SIGINT, OnSignalReceived); // Keep ProcessExit as fallback for non-signal termination scenarios _processExitHandler = (_, _) => InvokeCallback(); From 5bf21b740333ba8dde6a854cb6a883320e31c2e1 Mon Sep 17 00:00:00 2001 From: Gregorius Soedharmo Date: Thu, 18 Dec 2025 08:19:27 +0700 Subject: [PATCH 8/9] Revert .NET 10 CI/CD integration (will be moved to a new PR) --- .incrementalist/net10-Clr-testsOnly.json | 16 ---------------- Directory.Build.props | 1 - build-system/azure-pipeline.mntr-template.yaml | 8 +------- build-system/azure-pipeline.template.yaml | 8 +------- build-system/pr-validation.yaml | 18 ------------------ global.json | 8 ++++---- src/core/Akka.Tests/Akka.Tests.csproj | 4 ++-- 7 files changed, 8 insertions(+), 55 deletions(-) delete mode 100644 .incrementalist/net10-Clr-testsOnly.json diff --git a/.incrementalist/net10-Clr-testsOnly.json b/.incrementalist/net10-Clr-testsOnly.json deleted file mode 100644 index ab5a4a99347..00000000000 --- a/.incrementalist/net10-Clr-testsOnly.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "outputFile": "bin/output/incrementalist.txt", - "gitBranch": "dev", - "verbose": false, - "timeoutMinutes": 20, - "continueOnError": true, - "runInParallel": false, - "failOnNoProjects": false, - "skip": [ - "**/.Tests.MultiNode.csproj", - "src/examples/**" - ], - "target": [ - "**/Akka.Tests.csproj" - ] -} \ No newline at end of file diff --git a/Directory.Build.props b/Directory.Build.props index b8fb710f57a..01f9fdd9fd6 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -30,7 +30,6 @@ 2.60.0 0.15.4 net8.0 - net10.0 6.0.5 net48 netstandard2.0 diff --git a/build-system/azure-pipeline.mntr-template.yaml b/build-system/azure-pipeline.mntr-template.yaml index 5365ca079d6..2875e5f8700 100644 --- a/build-system/azure-pipeline.mntr-template.yaml +++ b/build-system/azure-pipeline.mntr-template.yaml @@ -16,16 +16,10 @@ jobs: vmImage: ${{ parameters.vmImage }} steps: - task: UseDotNet@2 - displayName: 'Use .NET SDK from global.json' + displayName: 'Use .NET' inputs: packageType: 'sdk' useGlobalJson: true - - - task: UseDotNet@2 - displayName: 'Install .NET 8.0 Runtime' - inputs: - packageType: 'runtime' - version: '8.0.x' # Set the Incrementalist base branch based on PR target branch - pwsh: | diff --git a/build-system/azure-pipeline.template.yaml b/build-system/azure-pipeline.template.yaml index 4298afbc842..746b297ca10 100644 --- a/build-system/azure-pipeline.template.yaml +++ b/build-system/azure-pipeline.template.yaml @@ -23,16 +23,10 @@ jobs: value: true steps: - task: UseDotNet@2 - displayName: 'Use .NET SDK from global.json' + displayName: 'Use .NET' inputs: packageType: 'sdk' useGlobalJson: true - - - task: UseDotNet@2 - displayName: 'Install .NET 8.0 Runtime' - inputs: - packageType: 'runtime' - version: '8.0.x' # Set the Incrementalist base branch based on PR target branch - pwsh: | diff --git a/build-system/pr-validation.yaml b/build-system/pr-validation.yaml index b0477b50a28..1a49c58fd0c 100644 --- a/build-system/pr-validation.yaml +++ b/build-system/pr-validation.yaml @@ -93,24 +93,6 @@ jobs: outputDirectory: "TestResults" artifactName: "net_tests_linux-$(Build.BuildId)" - - template: azure-pipeline.template.yaml - parameters: - name: "net10_tests_windows" - displayName: ".NET 10 CLR Hook Tests (Windows)" - vmImage: "windows-latest" - command: "dotnet incrementalist run --config .incrementalist/net10-Clr-testsOnly.json --branch $(IncrementalistBaseBranch) -- test -c Release --no-build --framework net10.0 --filter FullyQualifiedName~TerminationSignalHandlerSpec --logger:trx --results-directory TestResults" - outputDirectory: "TestResults" - artifactName: "net10_tests_windows-$(Build.BuildId)" - - - template: azure-pipeline.template.yaml - parameters: - name: "net10_tests_linux" - displayName: ".NET 10 CLR Hook Tests (Linux)" - vmImage: "ubuntu-latest" - command: "dotnet incrementalist run --config .incrementalist/net10-Clr-testsOnly.json --branch $(IncrementalistBaseBranch) -- test -c Release --no-build --framework net10.0 --filter FullyQualifiedName~TerminationSignalHandlerSpec --logger:trx --results-directory TestResults" - outputDirectory: "TestResults" - artifactName: "net10_tests_linux-$(Build.BuildId)" - - template: azure-pipeline.mntr-template.yaml parameters: name: "net_mntr_windows" diff --git a/global.json b/global.json index 53058434126..d9adffea2fc 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { - "sdk": { - "version": "10.0.101", - "rollForward": "major" - } + "sdk": { + "version": "8.0.403", + "rollForward": "major" + } } \ No newline at end of file diff --git a/src/core/Akka.Tests/Akka.Tests.csproj b/src/core/Akka.Tests/Akka.Tests.csproj index 69c1e297646..b04bed82bb9 100644 --- a/src/core/Akka.Tests/Akka.Tests.csproj +++ b/src/core/Akka.Tests/Akka.Tests.csproj @@ -2,8 +2,8 @@ - $(NetFrameworkTestVersion);$(NetTestVersion);$(NetTenTestVersion) - $(NetTestVersion);$(NetTenTestVersion) + $(NetFrameworkTestVersion);$(NetTestVersion) + $(NetTestVersion) From c07799b69641542923e02bdfa0d6ea439c92aa0b Mon Sep 17 00:00:00 2001 From: Gregorius Soedharmo Date: Thu, 18 Dec 2025 08:20:58 +0700 Subject: [PATCH 9/9] whitespace cleanup --- build-system/azure-pipeline.mntr-template.yaml | 2 +- build-system/azure-pipeline.template.yaml | 2 +- global.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/build-system/azure-pipeline.mntr-template.yaml b/build-system/azure-pipeline.mntr-template.yaml index 2875e5f8700..f1c237867bc 100644 --- a/build-system/azure-pipeline.mntr-template.yaml +++ b/build-system/azure-pipeline.mntr-template.yaml @@ -20,7 +20,7 @@ jobs: inputs: packageType: 'sdk' useGlobalJson: true - + # Set the Incrementalist base branch based on PR target branch - pwsh: | if ('$(Build.Reason)' -eq 'PullRequest') { diff --git a/build-system/azure-pipeline.template.yaml b/build-system/azure-pipeline.template.yaml index 746b297ca10..09d1ee5d667 100644 --- a/build-system/azure-pipeline.template.yaml +++ b/build-system/azure-pipeline.template.yaml @@ -27,7 +27,7 @@ jobs: inputs: packageType: 'sdk' useGlobalJson: true - + # Set the Incrementalist base branch based on PR target branch - pwsh: | if ('$(Build.Reason)' -eq 'PullRequest') { diff --git a/global.json b/global.json index d9adffea2fc..6316ee7bf7a 100644 --- a/global.json +++ b/global.json @@ -3,4 +3,4 @@ "version": "8.0.403", "rollForward": "major" } -} \ No newline at end of file +} \ No newline at end of file