From 5cabc8ec424c9e341965bc4c07d283fa412a682e Mon Sep 17 00:00:00 2001 From: Aaron Stannard Date: Fri, 2 Oct 2026 20:03:10 +0000 Subject: [PATCH] Serialization: V2 switch is HOCON-only (drop SerializationV2Setup) The switch is a boolean read once at startup. HOCON covers every user and Akka.Hosting writes the key, so the Setup added a public type and a precedence rule for nothing. Follow-up to #8724. Part of #8713. --- .../SerializationV2Specs.cs | 50 +++++++++++------ .../Akka.Hosting/AkkaHostingExtensions.cs | 9 ++-- ...oreAPISpec.ApproveCore.DotNet.verified.txt | 5 -- .../SerializationV2SwitchSpec.cs | 22 +------- src/core/Akka/Configuration/akka.conf | 1 - .../Akka/Serialization/ModuleSerializers.cs | 2 +- src/core/Akka/Serialization/Serialization.cs | 5 +- .../Serialization/SerializationV2Setup.cs | 54 ------------------- 8 files changed, 42 insertions(+), 106 deletions(-) delete mode 100644 src/core/Akka/Serialization/SerializationV2Setup.cs diff --git a/src/contrib/hosting/Akka.Hosting.TestKit.Tests/SerializationV2Specs.cs b/src/contrib/hosting/Akka.Hosting.TestKit.Tests/SerializationV2Specs.cs index 099f3d4c21b..26fab59e388 100644 --- a/src/contrib/hosting/Akka.Hosting.TestKit.Tests/SerializationV2Specs.cs +++ b/src/contrib/hosting/Akka.Hosting.TestKit.Tests/SerializationV2Specs.cs @@ -6,7 +6,7 @@ #nullable enable using System; -using Akka.Serialization; +using System.Threading.Tasks; using FluentAssertions; using Xunit; using LogLevel = Microsoft.Extensions.Logging.LogLevel; @@ -25,35 +25,53 @@ protected override void ConfigureAkka(AkkaConfigurationBuilder builder, IService builder.WithSerializationV2(); } - [Fact(DisplayName = "Should_add_an_enabled_SerializationV2Setup_When_WithSerializationV2_is_called")] - public void Should_add_an_enabled_SerializationV2Setup_When_WithSerializationV2_is_called() + [Fact(DisplayName = "Should_set_the_serialization_v2_HOCON_key_to_true_When_WithSerializationV2_is_called")] + public async Task Should_set_the_serialization_v2_HOCON_key_to_true_When_WithSerializationV2_is_called() { - var setup = Sys.Settings.Setup.Get(); - setup.HasValue.Should().BeTrue(); - setup.Value.Enabled.Should().BeTrue(); + await Task.Yield(); + Sys.Settings.Config.GetBoolean("akka.actor.serialization-v2", false).Should().BeTrue(); } } -public sealed class SerializationV2SpecsOverridden : TestKit +public sealed class SerializationV2SpecsDisabled : TestKit { - public SerializationV2SpecsOverridden(XunitTestOutputHelper output) - : base(nameof(SerializationV2SpecsOverridden), output, logLevel: LogLevel.Information) + public SerializationV2SpecsDisabled(XunitTestOutputHelper output) + : base(nameof(SerializationV2SpecsDisabled), output, logLevel: LogLevel.Information) { } protected override void ConfigureAkka(AkkaConfigurationBuilder builder, IServiceProvider provider) { - // HOCON says on, and the last builder call says off: the setup wins over HOCON, and the last call wins + // HOCON added earlier says on; the extension wins over it builder.AddHocon("akka.actor.serialization-v2 = on", HoconAddMode.Prepend); - builder.WithSerializationV2(); builder.WithSerializationV2(false); } - [Fact(DisplayName = "Should_keep_only_the_last_SerializationV2Setup_When_WithSerializationV2_is_called_twice")] - public void Should_keep_only_the_last_SerializationV2Setup_When_WithSerializationV2_is_called_twice() + [Fact(DisplayName = "Should_set_the_serialization_v2_HOCON_key_to_false_When_WithSerializationV2_is_called_with_false")] + public async Task Should_set_the_serialization_v2_HOCON_key_to_false_When_WithSerializationV2_is_called_with_false() + { + await Task.Yield(); + Sys.Settings.Config.GetBoolean("akka.actor.serialization-v2", true).Should().BeFalse(); + } +} + +public sealed class SerializationV2SpecsLastCallWins : TestKit +{ + public SerializationV2SpecsLastCallWins(XunitTestOutputHelper output) + : base(nameof(SerializationV2SpecsLastCallWins), output, logLevel: LogLevel.Information) + { + } + + protected override void ConfigureAkka(AkkaConfigurationBuilder builder, IServiceProvider provider) + { + builder.WithSerializationV2(false); + builder.WithSerializationV2(); + } + + [Fact(DisplayName = "Should_use_the_last_value_When_WithSerializationV2_is_called_twice")] + public async Task Should_use_the_last_value_When_WithSerializationV2_is_called_twice() { - var setup = Sys.Settings.Setup.Get(); - setup.HasValue.Should().BeTrue(); - setup.Value.Enabled.Should().BeFalse(); + await Task.Yield(); + Sys.Settings.Config.GetBoolean("akka.actor.serialization-v2", false).Should().BeTrue(); } } diff --git a/src/contrib/hosting/Akka.Hosting/AkkaHostingExtensions.cs b/src/contrib/hosting/Akka.Hosting/AkkaHostingExtensions.cs index 436889782ed..7ca9b58a2fd 100644 --- a/src/contrib/hosting/Akka.Hosting/AkkaHostingExtensions.cs +++ b/src/contrib/hosting/Akka.Hosting/AkkaHostingExtensions.cs @@ -221,8 +221,7 @@ public static AkkaConfigurationBuilder WithStrictSerialization(this AkkaConfigur } /// - /// Turns the Serialization V2 switch on or off by adding a , which wins over - /// akka.actor.serialization-v2 in HOCON. When on, each built-in serializer that has a V2 version takes + /// Turns the Serialization V2 switch (akka.actor.serialization-v2) on or off. When on, each built-in serializer that has a V2 version takes /// over the bindings of the legacy serializer it replaces; the legacy serializers stay registered for reads. /// /// Turn it on only once every node runs a version of Akka.NET that can read the V2 formats. A binding you @@ -234,9 +233,9 @@ public static AkkaConfigurationBuilder WithStrictSerialization(this AkkaConfigur /// The same instance originally passed in. public static AkkaConfigurationBuilder WithSerializationV2(this AkkaConfigurationBuilder builder, bool enabled = true) { - // one switch per system: a later call replaces an earlier one instead of racing it in the set - builder.Setups.RemoveWhere(s => s is SerializationV2Setup); - return builder.AddSetup(SerializationV2Setup.Create(enabled)); + // Prepend, like the other single-key extensions: wins over the reference config and earlier HOCON, + // and a later call wins over an earlier one + return AddHocon(builder, $"akka.actor.serialization-v2 = {(enabled ? "on" : "off")}", HoconAddMode.Prepend); } /// diff --git a/src/core/Akka.API.Tests/verify/CoreAPISpec.ApproveCore.DotNet.verified.txt b/src/core/Akka.API.Tests/verify/CoreAPISpec.ApproveCore.DotNet.verified.txt index aa9d46d2b11..ba2a180bea8 100644 --- a/src/core/Akka.API.Tests/verify/CoreAPISpec.ApproveCore.DotNet.verified.txt +++ b/src/core/Akka.API.Tests/verify/CoreAPISpec.ApproveCore.DotNet.verified.txt @@ -5284,11 +5284,6 @@ namespace Akka.Serialization public System.Func> CreateSerializers { get; } public static Akka.Serialization.SerializationSetup Create(System.Func> createSerializers) { } } - public sealed class SerializationV2Setup : Akka.Actor.Setup.Setup - { - public bool Enabled { get; } - public static Akka.Serialization.SerializationV2Setup Create(bool enabled) { } - } public abstract class Serializer { protected readonly Akka.Actor.ExtendedActorSystem system; diff --git a/src/core/Akka.Tests/Serialization/SerializationV2SwitchSpec.cs b/src/core/Akka.Tests/Serialization/SerializationV2SwitchSpec.cs index 616bdf7c716..5d05339a1ab 100644 --- a/src/core/Akka.Tests/Serialization/SerializationV2SwitchSpec.cs +++ b/src/core/Akka.Tests/Serialization/SerializationV2SwitchSpec.cs @@ -27,7 +27,7 @@ namespace Akka.Tests.Serialization { /// - /// The Serialization V2 switch (akka.actor.serialization-v2 / ), driven by + /// The Serialization V2 switch (akka.actor.serialization-v2), driven by /// a fake module keyed as "Akka.Tests": a legacy row, the read-only V2 row that supersedes it, and an unrelated /// native row the switch must leave alone. No first-party module ships a V2 row yet. /// @@ -340,26 +340,6 @@ await WithSystem("akka.actor.serialization-v2 = on", async system => }, setup); } - [Theory(DisplayName = "Should_let_SerializationV2Setup_win_over_HOCON_When_both_set_the_switch")] - [InlineData("akka.actor.serialization-v2 = on", false, true)] - [InlineData("akka.actor.serialization-v2 = on", false, false)] - [InlineData("akka.actor.serialization-v2 = off", true, true)] - [InlineData("akka.actor.serialization-v2 = off", true, false)] - [InlineData(null, true, false)] - public async Task Should_let_SerializationV2Setup_win_over_HOCON_When_both_set_the_switch(string? hocon, bool setupEnabled, bool dynamicTypeLoading) - { - await WithSystem(hocon, async system => - { - var serialization = await Build(system, dynamicTypeLoading); - - if (setupEnabled) - AssertV2Bindings(serialization); - else - AssertLegacyBindings(serialization); - AssertBothFormatsRead(serialization); - }, SerializationV2Setup.Create(setupEnabled)); - } - [Theory(DisplayName = "Should_move_the_taken_over_bindings_to_the_override_When_user_HOCON_overrides_the_V2_alias")] [InlineData(true)] [InlineData(false)] diff --git a/src/core/Akka/Configuration/akka.conf b/src/core/Akka/Configuration/akka.conf index fdf653d7695..38fef9bdbd5 100644 --- a/src/core/Akka/Configuration/akka.conf +++ b/src/core/Akka/Configuration/akka.conf @@ -558,7 +558,6 @@ akka { # Turn it on only once every node runs such a version. To roll back, turn it off. # A type you bind in serialization-bindings (or in a SerializationSetup) keeps # your binding, so you can pin one subsystem to its legacy alias. - # A SerializationV2Setup passed to the ActorSystem overrides this setting. serialization-v2 = off # extra settings that can be custom to a serializer implementation diff --git a/src/core/Akka/Serialization/ModuleSerializers.cs b/src/core/Akka/Serialization/ModuleSerializers.cs index 530aadcab6c..2bceb3ae22d 100644 --- a/src/core/Akka/Serialization/ModuleSerializers.cs +++ b/src/core/Akka/Serialization/ModuleSerializers.cs @@ -32,7 +32,7 @@ internal abstract class ModuleSerializers /// The legacy row each of this module's V2 rows supersedes, as V2 alias to legacy alias - for example /// ["reliable-delivery-v2"] = "reliable-delivery". Both rows must come from , and the /// V2 row ships read-only (empty ). When the Serialization V2 switch - /// (akka.actor.serialization-v2 or ) is on, the V2 row takes over the + /// (akka.actor.serialization-v2) is on, the V2 row takes over the /// legacy row's default bindings; when it is off, nothing moves. Both ids stay registered either way. /// public virtual ImmutableDictionary Supersedes => ImmutableDictionary.Empty; diff --git a/src/core/Akka/Serialization/Serialization.cs b/src/core/Akka/Serialization/Serialization.cs index ff79c1361ce..8ed66868f5a 100644 --- a/src/core/Akka/Serialization/Serialization.cs +++ b/src/core/Akka/Serialization/Serialization.cs @@ -206,7 +206,7 @@ public static T WithTransport(ActorSystem system, Address address, Func ac private Dictionary _defaultSerializerIdAlias = new(); private Dictionary UseFor)> _moduleDefaultsByAlias = new(StringComparer.Ordinal); - /// The HOCON key of the Serialization V2 switch; a overrides it. + /// The HOCON key of the Serialization V2 switch. internal const string SerializationV2Key = "akka.actor.serialization-v2"; // the last Serialization V2 line logged per system, so a rebuilt Serialization doesn't repeat it @@ -336,8 +336,7 @@ internal Serialization(ExtendedActorSystem system, ModuleSerializerTable modules // the Serialization V2 switch decides which module row holds a superseded legacy row's default bindings; // it never changes which ids are registered, so reads don't depend on it - var serializationV2 = system.Settings.Setup.Get().Select(s => s.Enabled) - .GetOrElse(system.Settings.Config.GetBoolean(SerializationV2Key, false)); + var serializationV2 = system.Settings.Config.GetBoolean(SerializationV2Key, false); List takeoverModules = null; // modules this system ships with, by assembly name. Every module the table knows about is built here, diff --git a/src/core/Akka/Serialization/SerializationV2Setup.cs b/src/core/Akka/Serialization/SerializationV2Setup.cs deleted file mode 100644 index 5d1568dc94e..00000000000 --- a/src/core/Akka/Serialization/SerializationV2Setup.cs +++ /dev/null @@ -1,54 +0,0 @@ -//----------------------------------------------------------------------- -// -// Copyright (C) 2009-2025 Lightbend Inc. -// Copyright (C) 2013-2025 .NET Foundation -// -//----------------------------------------------------------------------- - -#nullable enable -using Akka.Actor; -using Akka.Actor.Setup; - -namespace Akka.Serialization -{ - /// - /// The Serialization V2 switch, the programmatic equivalent of akka.actor.serialization-v2. When a - /// is present, its value wins over the HOCON setting. - /// - /// - /// - /// Off by default. When on, each built-in serializer that has a V2 version takes over the bindings of the legacy - /// serializer it replaces, so this node writes those messages in the V2 format. The legacy serializers stay - /// registered, so reads never depend on the switch: a node reads both formats whether it is on or off. - /// Turn it on only once every node runs a version of Akka.NET that can read the V2 formats. - /// - /// - /// A binding set in akka.actor.serialization-bindings or by a always wins - /// over the switch, so one subsystem can stay on its legacy serializer by binding its types to the legacy alias. - /// Serializers that became native implementations under their old id are not affected. - /// - /// - /// Like any , a second passed to - /// replaces the first. - /// - /// - public sealed class SerializationV2Setup : Setup - { - private SerializationV2Setup(bool enabled) - { - Enabled = enabled; - } - - /// - /// true to move built-in bindings to their V2 serializers; false to keep every binding on its - /// legacy serializer, whatever akka.actor.serialization-v2 says. - /// - public bool Enabled { get; } - - /// - /// Creates a that turns the Serialization V2 switch on or off. - /// - /// true to turn the switch on; false to force it off. - public static SerializationV2Setup Create(bool enabled) => new(enabled); - } -}