diff --git a/src/contrib/cluster/Akka.Cluster.Metrics/Serialization/ClusterMetricsMessageSerializer.cs b/src/contrib/cluster/Akka.Cluster.Metrics/Serialization/ClusterMetricsMessageSerializer.cs index 6574cae9438..8f7b110d8c9 100644 --- a/src/contrib/cluster/Akka.Cluster.Metrics/Serialization/ClusterMetricsMessageSerializer.cs +++ b/src/contrib/cluster/Akka.Cluster.Metrics/Serialization/ClusterMetricsMessageSerializer.cs @@ -44,6 +44,12 @@ public ClusterMetricsMessageSerializer(ExtendedActorSystem system) _serialization = new Lazy(() => new Akka.Serialization.Serialization(system)); } + /// + /// + /// A subclass keeps resolving its own id from HOCON via . + /// + public override int Identifier => GetType() == typeof(ClusterMetricsMessageSerializer) ? 10 : base.Identifier; + /// public override byte[] ToBinary(object obj) { diff --git a/src/contrib/cluster/Akka.Cluster.Sharding/Serialization/ClusterShardingMessageSerializer.cs b/src/contrib/cluster/Akka.Cluster.Sharding/Serialization/ClusterShardingMessageSerializer.cs index 71dc3b2a265..3dc9c748f8d 100644 --- a/src/contrib/cluster/Akka.Cluster.Sharding/Serialization/ClusterShardingMessageSerializer.cs +++ b/src/contrib/cluster/Akka.Cluster.Sharding/Serialization/ClusterShardingMessageSerializer.cs @@ -148,6 +148,12 @@ public ClusterShardingMessageSerializer(ExtendedActorSystem system) : base(syste }; } + /// + /// + /// A subclass keeps resolving its own id from HOCON via . + /// + public override int Identifier => GetType() == typeof(ClusterShardingMessageSerializer) ? 13 : base.Identifier; + private ShardingEnvelope ShardingEnvelopeFromBinary(byte[] bytes) { var proto = Proto.Msg.ShardingEnvelope.Parser.ParseFrom(bytes); diff --git a/src/contrib/cluster/Akka.Cluster.Tools/Client/Serialization/ClusterClientMessageSerializer.cs b/src/contrib/cluster/Akka.Cluster.Tools/Client/Serialization/ClusterClientMessageSerializer.cs index dbb55b2c731..67d19516c81 100644 --- a/src/contrib/cluster/Akka.Cluster.Tools/Client/Serialization/ClusterClientMessageSerializer.cs +++ b/src/contrib/cluster/Akka.Cluster.Tools/Client/Serialization/ClusterClientMessageSerializer.cs @@ -48,6 +48,12 @@ public ClusterClientMessageSerializer(ExtendedActorSystem system) : base(system) _payloadSupport = new WrappedPayloadSupport(system); } + /// + /// + /// A subclass keeps resolving its own id from HOCON via . + /// + public override int Identifier => GetType() == typeof(ClusterClientMessageSerializer) ? 15 : base.Identifier; + /// /// Serializes the given object into a byte array /// diff --git a/src/contrib/cluster/Akka.Cluster.Tools/PublishSubscribe/Serialization/DistributedPubSubMessageSerializer.cs b/src/contrib/cluster/Akka.Cluster.Tools/PublishSubscribe/Serialization/DistributedPubSubMessageSerializer.cs index c37c81fae02..7e53c8ec40d 100644 --- a/src/contrib/cluster/Akka.Cluster.Tools/PublishSubscribe/Serialization/DistributedPubSubMessageSerializer.cs +++ b/src/contrib/cluster/Akka.Cluster.Tools/PublishSubscribe/Serialization/DistributedPubSubMessageSerializer.cs @@ -55,6 +55,12 @@ public DistributedPubSubMessageSerializer(ExtendedActorSystem system) : base(sys }; } + /// + /// + /// A subclass keeps resolving its own id from HOCON via . + /// + public override int Identifier => GetType() == typeof(DistributedPubSubMessageSerializer) ? 9 : base.Identifier; + /// /// Serializes the given object into a byte array /// diff --git a/src/contrib/cluster/Akka.Cluster.Tools/Singleton/Serialization/ClusterSingletonMessageSerializer.cs b/src/contrib/cluster/Akka.Cluster.Tools/Singleton/Serialization/ClusterSingletonMessageSerializer.cs index a5ef3e6fee2..f7f2a1ffd91 100644 --- a/src/contrib/cluster/Akka.Cluster.Tools/Singleton/Serialization/ClusterSingletonMessageSerializer.cs +++ b/src/contrib/cluster/Akka.Cluster.Tools/Singleton/Serialization/ClusterSingletonMessageSerializer.cs @@ -41,6 +41,12 @@ public ClusterSingletonMessageSerializer(ExtendedActorSystem system) : base(syst }; } + /// + /// + /// A subclass keeps resolving its own id from HOCON via . + /// + public override int Identifier => GetType() == typeof(ClusterSingletonMessageSerializer) ? 14 : base.Identifier; + /// /// Serializes the given object into a byte array /// diff --git a/src/contrib/cluster/Akka.DistributedData/Serialization/ReplicatedDataSerializer.cs b/src/contrib/cluster/Akka.DistributedData/Serialization/ReplicatedDataSerializer.cs index d18dfb7b691..fa026096a77 100644 --- a/src/contrib/cluster/Akka.DistributedData/Serialization/ReplicatedDataSerializer.cs +++ b/src/contrib/cluster/Akka.DistributedData/Serialization/ReplicatedDataSerializer.cs @@ -75,6 +75,8 @@ public ReplicatedDataSerializer(ExtendedActorSystem system) : base(system) system.Settings.Config.GetBoolean("akka.cluster.sharding.distributed-data.backward-compatible-wire-format"); } + /// + public override int Identifier => 11; public override byte[] ToBinary(object obj) { diff --git a/src/contrib/cluster/Akka.DistributedData/Serialization/ReplicatorMessageSerializer.cs b/src/contrib/cluster/Akka.DistributedData/Serialization/ReplicatorMessageSerializer.cs index 562f01ad19e..5771645936b 100644 --- a/src/contrib/cluster/Akka.DistributedData/Serialization/ReplicatorMessageSerializer.cs +++ b/src/contrib/cluster/Akka.DistributedData/Serialization/ReplicatorMessageSerializer.cs @@ -168,6 +168,9 @@ public ReplicatorMessageSerializer(Akka.Actor.ExtendedActorSystem system) : base }); } + /// + public override int Identifier => 12; + public override string Manifest(object o) { switch (o) diff --git a/src/core/Akka.API.Tests/BuiltInSerializerIdentifierSpec.cs b/src/core/Akka.API.Tests/BuiltInSerializerIdentifierSpec.cs new file mode 100644 index 00000000000..b199f40a1bb --- /dev/null +++ b/src/core/Akka.API.Tests/BuiltInSerializerIdentifierSpec.cs @@ -0,0 +1,66 @@ +//----------------------------------------------------------------------- +// +// Copyright (C) 2009-2025 Lightbend Inc. +// Copyright (C) 2013-2025 .NET Foundation +// +//----------------------------------------------------------------------- + +using System; +using System.Runtime.CompilerServices; +using Akka.Actor; +using Akka.Cluster.Metrics; +using Akka.Cluster.Sharding; +using Akka.Cluster.Tools.Client; +using Akka.Cluster.Tools.PublishSubscribe; +using Akka.Cluster.Tools.Singleton; +using Akka.Configuration; +using Akka.Serialization; +using Akka.Streams; +using Xunit; +using DistributedDataExtension = Akka.DistributedData.DistributedData; +using PersistenceExtension = Akka.Persistence.Persistence; + +namespace Akka.API.Tests +{ + /// + /// Every built-in serializer now declares its wire id in code. This is the one project that references all of + /// Remote, Cluster, Cluster.Tools, Cluster.Sharding, DistributedData, Cluster.Metrics, Persistence, Streams and + /// core, so it's the one place that can check every built-in serializer's code + /// against every module's shipped akka.actor.serialization-identifiers row in a single test. + /// + /// Each serializer is created with , which skips the + /// constructor entirely, leaving the instance's actor-system field null. A built-in serializer's Identifier + /// is either a hardcoded constant or a GetType() guard around one - never a HOCON read - so this is safe + /// for all of them. It also proves D1: a serializer that still resolved its id lazily from HOCON would touch the + /// null actor-system field on first access and throw, instead of quietly returning the right answer. + /// + public class BuiltInSerializerIdentifierSpec + { + [Fact(DisplayName = "Should_match_HOCON_serialization_identifiers_When_reading_every_builtin_serializer_Identifier")] + public void Should_match_HOCON_serialization_identifiers_When_reading_every_builtin_serializer_Identifier() + { + var config = ConfigurationFactory.FromResource("Akka.Remote.Configuration.Remote.conf") + .WithFallback(ConfigurationFactory.FromResource("Akka.Cluster.Configuration.Cluster.conf")) + .WithFallback(ClusterSharding.DefaultConfig()) + .WithFallback(ClusterSingleton.DefaultConfig()) + .WithFallback(DistributedPubSub.DefaultConfig()) + .WithFallback(ClusterClientReceptionist.DefaultConfig()) + .WithFallback(DistributedDataExtension.DefaultConfig()) + .WithFallback(ClusterMetrics.DefaultConfig()) + .WithFallback(PersistenceExtension.DefaultConfig()) + .WithFallback(ActorMaterializer.DefaultConfig()); + + // config only: no ActorSystem needed to read the rows + var rows = config.WithFallback(ConfigurationFactory.Default()).GetConfig("akka.actor.serialization-identifiers"); + foreach (var row in rows.AsEnumerable()) + { + var type = Type.GetType(row.Key, throwOnError: true); + var expectedId = row.Value.GetInt(); + + var serializer = (Serializer)RuntimeHelpers.GetUninitializedObject(type); + Assert.True(expectedId == serializer.Identifier, + $"{type.FullName} declared Identifier {serializer.Identifier}, but its HOCON row says {expectedId}"); + } + } + } +} diff --git a/src/core/Akka.API.Tests/verify/CoreAPISpec.ApproveCluster.DotNet.verified.txt b/src/core/Akka.API.Tests/verify/CoreAPISpec.ApproveCluster.DotNet.verified.txt index c6e251644ad..7c735f2cf4d 100644 --- a/src/core/Akka.API.Tests/verify/CoreAPISpec.ApproveCluster.DotNet.verified.txt +++ b/src/core/Akka.API.Tests/verify/CoreAPISpec.ApproveCluster.DotNet.verified.txt @@ -447,6 +447,7 @@ namespace Akka.Cluster.Serialization public class ClusterMessageSerializer : Akka.Serialization.SerializerWithStringManifest { public ClusterMessageSerializer(Akka.Actor.ExtendedActorSystem system) { } + public override int Identifier { get; } public override object FromBinary(byte[] bytes, string manifest) { } public override string Manifest(object o) { } public override byte[] ToBinary(object obj) { } diff --git a/src/core/Akka.API.Tests/verify/CoreAPISpec.ApproveCluster.Net.verified.txt b/src/core/Akka.API.Tests/verify/CoreAPISpec.ApproveCluster.Net.verified.txt index fd0a4eef4a5..7b1b47ea628 100644 --- a/src/core/Akka.API.Tests/verify/CoreAPISpec.ApproveCluster.Net.verified.txt +++ b/src/core/Akka.API.Tests/verify/CoreAPISpec.ApproveCluster.Net.verified.txt @@ -447,6 +447,7 @@ namespace Akka.Cluster.Serialization public class ClusterMessageSerializer : Akka.Serialization.SerializerWithStringManifest { public ClusterMessageSerializer(Akka.Actor.ExtendedActorSystem system) { } + public override int Identifier { get; } public override object FromBinary(byte[] bytes, string manifest) { } public override string Manifest(object o) { } public override byte[] ToBinary(object obj) { } diff --git a/src/core/Akka.API.Tests/verify/CoreAPISpec.ApproveClusterMetrics.DotNet.verified.txt b/src/core/Akka.API.Tests/verify/CoreAPISpec.ApproveClusterMetrics.DotNet.verified.txt index 1546014be53..b94fd4a9c24 100644 --- a/src/core/Akka.API.Tests/verify/CoreAPISpec.ApproveClusterMetrics.DotNet.verified.txt +++ b/src/core/Akka.API.Tests/verify/CoreAPISpec.ApproveClusterMetrics.DotNet.verified.txt @@ -280,6 +280,7 @@ namespace Akka.Cluster.Metrics.Serialization public class ClusterMetricsMessageSerializer : Akka.Serialization.SerializerWithStringManifest { public ClusterMetricsMessageSerializer(Akka.Actor.ExtendedActorSystem system) { } + public override int Identifier { get; } public override object FromBinary(byte[] bytes, string manifest) { } public override string Manifest(object o) { } public override byte[] ToBinary(object obj) { } diff --git a/src/core/Akka.API.Tests/verify/CoreAPISpec.ApproveClusterMetrics.Net.verified.txt b/src/core/Akka.API.Tests/verify/CoreAPISpec.ApproveClusterMetrics.Net.verified.txt index df6f6dff572..aff1e3df1c1 100644 --- a/src/core/Akka.API.Tests/verify/CoreAPISpec.ApproveClusterMetrics.Net.verified.txt +++ b/src/core/Akka.API.Tests/verify/CoreAPISpec.ApproveClusterMetrics.Net.verified.txt @@ -280,6 +280,7 @@ namespace Akka.Cluster.Metrics.Serialization public class ClusterMetricsMessageSerializer : Akka.Serialization.SerializerWithStringManifest { public ClusterMetricsMessageSerializer(Akka.Actor.ExtendedActorSystem system) { } + public override int Identifier { get; } public override object FromBinary(byte[] bytes, string manifest) { } public override string Manifest(object o) { } public override byte[] ToBinary(object obj) { } diff --git a/src/core/Akka.API.Tests/verify/CoreAPISpec.ApproveClusterSharding.DotNet.verified.txt b/src/core/Akka.API.Tests/verify/CoreAPISpec.ApproveClusterSharding.DotNet.verified.txt index c11cce02217..3ccd5f60715 100644 --- a/src/core/Akka.API.Tests/verify/CoreAPISpec.ApproveClusterSharding.DotNet.verified.txt +++ b/src/core/Akka.API.Tests/verify/CoreAPISpec.ApproveClusterSharding.DotNet.verified.txt @@ -581,6 +581,7 @@ namespace Akka.Cluster.Sharding.Serialization public class ClusterShardingMessageSerializer : Akka.Serialization.SerializerWithStringManifest { public ClusterShardingMessageSerializer(Akka.Actor.ExtendedActorSystem system) { } + public override int Identifier { get; } public override object FromBinary(byte[] bytes, string manifest) { } public override string Manifest(object o) { } public override byte[] ToBinary(object obj) { } diff --git a/src/core/Akka.API.Tests/verify/CoreAPISpec.ApproveClusterSharding.Net.verified.txt b/src/core/Akka.API.Tests/verify/CoreAPISpec.ApproveClusterSharding.Net.verified.txt index 6e7b5561fcf..9f54ca1ffe7 100644 --- a/src/core/Akka.API.Tests/verify/CoreAPISpec.ApproveClusterSharding.Net.verified.txt +++ b/src/core/Akka.API.Tests/verify/CoreAPISpec.ApproveClusterSharding.Net.verified.txt @@ -581,6 +581,7 @@ namespace Akka.Cluster.Sharding.Serialization public class ClusterShardingMessageSerializer : Akka.Serialization.SerializerWithStringManifest { public ClusterShardingMessageSerializer(Akka.Actor.ExtendedActorSystem system) { } + public override int Identifier { get; } public override object FromBinary(byte[] bytes, string manifest) { } public override string Manifest(object o) { } public override byte[] ToBinary(object obj) { } diff --git a/src/core/Akka.API.Tests/verify/CoreAPISpec.ApproveClusterTools.DotNet.verified.txt b/src/core/Akka.API.Tests/verify/CoreAPISpec.ApproveClusterTools.DotNet.verified.txt index d67af4853e1..6a65bffda6d 100644 --- a/src/core/Akka.API.Tests/verify/CoreAPISpec.ApproveClusterTools.DotNet.verified.txt +++ b/src/core/Akka.API.Tests/verify/CoreAPISpec.ApproveClusterTools.DotNet.verified.txt @@ -214,6 +214,7 @@ namespace Akka.Cluster.Tools.Client.Serialization public class ClusterClientMessageSerializer : Akka.Serialization.SerializerWithStringManifest { public ClusterClientMessageSerializer(Akka.Actor.ExtendedActorSystem system) { } + public override int Identifier { get; } public override object FromBinary(byte[] bytes, string manifest) { } public override string Manifest(object o) { } public override byte[] ToBinary(object obj) { } @@ -423,6 +424,7 @@ namespace Akka.Cluster.Tools.PublishSubscribe.Serialization public class DistributedPubSubMessageSerializer : Akka.Serialization.SerializerWithStringManifest { public DistributedPubSubMessageSerializer(Akka.Actor.ExtendedActorSystem system) { } + public override int Identifier { get; } public override object FromBinary(byte[] bytes, string manifest) { } public override string Manifest(object o) { } public override byte[] ToBinary(object obj) { } @@ -584,6 +586,7 @@ namespace Akka.Cluster.Tools.Singleton.Serialization public class ClusterSingletonMessageSerializer : Akka.Serialization.SerializerWithStringManifest { public ClusterSingletonMessageSerializer(Akka.Actor.ExtendedActorSystem system) { } + public override int Identifier { get; } public override object FromBinary(byte[] bytes, string manifest) { } public override string Manifest(object o) { } public override byte[] ToBinary(object obj) { } diff --git a/src/core/Akka.API.Tests/verify/CoreAPISpec.ApproveClusterTools.Net.verified.txt b/src/core/Akka.API.Tests/verify/CoreAPISpec.ApproveClusterTools.Net.verified.txt index 98ed12298c9..9211b95af79 100644 --- a/src/core/Akka.API.Tests/verify/CoreAPISpec.ApproveClusterTools.Net.verified.txt +++ b/src/core/Akka.API.Tests/verify/CoreAPISpec.ApproveClusterTools.Net.verified.txt @@ -214,6 +214,7 @@ namespace Akka.Cluster.Tools.Client.Serialization public class ClusterClientMessageSerializer : Akka.Serialization.SerializerWithStringManifest { public ClusterClientMessageSerializer(Akka.Actor.ExtendedActorSystem system) { } + public override int Identifier { get; } public override object FromBinary(byte[] bytes, string manifest) { } public override string Manifest(object o) { } public override byte[] ToBinary(object obj) { } @@ -423,6 +424,7 @@ namespace Akka.Cluster.Tools.PublishSubscribe.Serialization public class DistributedPubSubMessageSerializer : Akka.Serialization.SerializerWithStringManifest { public DistributedPubSubMessageSerializer(Akka.Actor.ExtendedActorSystem system) { } + public override int Identifier { get; } public override object FromBinary(byte[] bytes, string manifest) { } public override string Manifest(object o) { } public override byte[] ToBinary(object obj) { } @@ -584,6 +586,7 @@ namespace Akka.Cluster.Tools.Singleton.Serialization public class ClusterSingletonMessageSerializer : Akka.Serialization.SerializerWithStringManifest { public ClusterSingletonMessageSerializer(Akka.Actor.ExtendedActorSystem system) { } + public override int Identifier { get; } public override object FromBinary(byte[] bytes, string manifest) { } public override string Manifest(object o) { } public override byte[] ToBinary(object obj) { } 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 049d4228463..478d2d92e74 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 @@ -5158,6 +5158,7 @@ namespace Akka.Serialization public class ByteArraySerializer : Akka.Serialization.SerializerV2 { public ByteArraySerializer(Akka.Actor.ExtendedActorSystem system) { } + public override int Identifier { get; } public override object Deserialize(System.Buffers.ReadOnlySequence bytes, string manifest) { } public override object FromBinary(byte[] bytes, System.Type type) { } public override object FromBinary(byte[] bytes, string manifest) { } @@ -5183,6 +5184,7 @@ namespace Akka.Serialization public NewtonSoftJsonSerializer(Akka.Actor.ExtendedActorSystem system) { } public NewtonSoftJsonSerializer(Akka.Actor.ExtendedActorSystem system, Akka.Configuration.Config config) { } public NewtonSoftJsonSerializer(Akka.Actor.ExtendedActorSystem system, Akka.Serialization.NewtonSoftJsonSerializerSettings settings) { } + public override int Identifier { get; } public override bool IncludeManifest { get; } public object Serializer { get; } public Newtonsoft.Json.JsonSerializerSettings Settings { get; } diff --git a/src/core/Akka.API.Tests/verify/CoreAPISpec.ApproveCore.Net.verified.txt b/src/core/Akka.API.Tests/verify/CoreAPISpec.ApproveCore.Net.verified.txt index 9b0cfe0a887..f6ab3fe0eb5 100644 --- a/src/core/Akka.API.Tests/verify/CoreAPISpec.ApproveCore.Net.verified.txt +++ b/src/core/Akka.API.Tests/verify/CoreAPISpec.ApproveCore.Net.verified.txt @@ -5147,6 +5147,7 @@ namespace Akka.Serialization public class ByteArraySerializer : Akka.Serialization.SerializerV2 { public ByteArraySerializer(Akka.Actor.ExtendedActorSystem system) { } + public override int Identifier { get; } public override object Deserialize(System.Buffers.ReadOnlySequence bytes, string manifest) { } public override object FromBinary(byte[] bytes, System.Type type) { } public override object FromBinary(byte[] bytes, string manifest) { } @@ -5172,6 +5173,7 @@ namespace Akka.Serialization public NewtonSoftJsonSerializer(Akka.Actor.ExtendedActorSystem system) { } public NewtonSoftJsonSerializer(Akka.Actor.ExtendedActorSystem system, Akka.Configuration.Config config) { } public NewtonSoftJsonSerializer(Akka.Actor.ExtendedActorSystem system, Akka.Serialization.NewtonSoftJsonSerializerSettings settings) { } + public override int Identifier { get; } public override bool IncludeManifest { get; } public object Serializer { get; } public Newtonsoft.Json.JsonSerializerSettings Settings { get; } diff --git a/src/core/Akka.API.Tests/verify/CoreAPISpec.ApproveDistributedData.DotNet.verified.txt b/src/core/Akka.API.Tests/verify/CoreAPISpec.ApproveDistributedData.DotNet.verified.txt index 497781a86ec..55f318f263d 100644 --- a/src/core/Akka.API.Tests/verify/CoreAPISpec.ApproveDistributedData.DotNet.verified.txt +++ b/src/core/Akka.API.Tests/verify/CoreAPISpec.ApproveDistributedData.DotNet.verified.txt @@ -1122,6 +1122,7 @@ namespace Akka.DistributedData.Serialization public sealed class ReplicatedDataSerializer : Akka.Serialization.SerializerWithStringManifest { public ReplicatedDataSerializer(Akka.Actor.ExtendedActorSystem system) { } + public override int Identifier { get; } public override object FromBinary(byte[] bytes, string manifest) { } public override string Manifest(object o) { } public override byte[] ToBinary(object obj) { } @@ -1129,6 +1130,7 @@ namespace Akka.DistributedData.Serialization public sealed class ReplicatorMessageSerializer : Akka.Serialization.SerializerWithStringManifest { public ReplicatorMessageSerializer(Akka.Actor.ExtendedActorSystem system) { } + public override int Identifier { get; } public override object FromBinary(byte[] bytes, string manifest) { } public override string Manifest(object o) { } public override byte[] ToBinary(object obj) { } diff --git a/src/core/Akka.API.Tests/verify/CoreAPISpec.ApproveDistributedData.Net.verified.txt b/src/core/Akka.API.Tests/verify/CoreAPISpec.ApproveDistributedData.Net.verified.txt index c4220094b54..a2365167074 100644 --- a/src/core/Akka.API.Tests/verify/CoreAPISpec.ApproveDistributedData.Net.verified.txt +++ b/src/core/Akka.API.Tests/verify/CoreAPISpec.ApproveDistributedData.Net.verified.txt @@ -1122,6 +1122,7 @@ namespace Akka.DistributedData.Serialization public sealed class ReplicatedDataSerializer : Akka.Serialization.SerializerWithStringManifest { public ReplicatedDataSerializer(Akka.Actor.ExtendedActorSystem system) { } + public override int Identifier { get; } public override object FromBinary(byte[] bytes, string manifest) { } public override string Manifest(object o) { } public override byte[] ToBinary(object obj) { } @@ -1129,6 +1130,7 @@ namespace Akka.DistributedData.Serialization public sealed class ReplicatorMessageSerializer : Akka.Serialization.SerializerWithStringManifest { public ReplicatorMessageSerializer(Akka.Actor.ExtendedActorSystem system) { } + public override int Identifier { get; } public override object FromBinary(byte[] bytes, string manifest) { } public override string Manifest(object o) { } public override byte[] ToBinary(object obj) { } diff --git a/src/core/Akka.API.Tests/verify/CoreAPISpec.ApprovePersistence.DotNet.verified.txt b/src/core/Akka.API.Tests/verify/CoreAPISpec.ApprovePersistence.DotNet.verified.txt index 561959a1b63..e49cfef947b 100644 --- a/src/core/Akka.API.Tests/verify/CoreAPISpec.ApprovePersistence.DotNet.verified.txt +++ b/src/core/Akka.API.Tests/verify/CoreAPISpec.ApprovePersistence.DotNet.verified.txt @@ -1256,6 +1256,7 @@ namespace Akka.Persistence.Serialization public sealed class PersistenceMessageSerializer : Akka.Serialization.Serializer { public PersistenceMessageSerializer(Akka.Actor.ExtendedActorSystem system) { } + public override int Identifier { get; } public override bool IncludeManifest { get; } public override object FromBinary(byte[] bytes, System.Type type) { } public override byte[] ToBinary(object obj) { } @@ -1263,6 +1264,7 @@ namespace Akka.Persistence.Serialization public class PersistenceSnapshotSerializer : Akka.Serialization.Serializer { public PersistenceSnapshotSerializer(Akka.Actor.ExtendedActorSystem system) { } + public override int Identifier { get; } public override bool IncludeManifest { get; } public override object FromBinary(byte[] bytes, System.Type type) { } public override byte[] ToBinary(object obj) { } diff --git a/src/core/Akka.API.Tests/verify/CoreAPISpec.ApprovePersistence.Net.verified.txt b/src/core/Akka.API.Tests/verify/CoreAPISpec.ApprovePersistence.Net.verified.txt index 4ee34bd39ac..1004e5039a6 100644 --- a/src/core/Akka.API.Tests/verify/CoreAPISpec.ApprovePersistence.Net.verified.txt +++ b/src/core/Akka.API.Tests/verify/CoreAPISpec.ApprovePersistence.Net.verified.txt @@ -1256,6 +1256,7 @@ namespace Akka.Persistence.Serialization public sealed class PersistenceMessageSerializer : Akka.Serialization.Serializer { public PersistenceMessageSerializer(Akka.Actor.ExtendedActorSystem system) { } + public override int Identifier { get; } public override bool IncludeManifest { get; } public override object FromBinary(byte[] bytes, System.Type type) { } public override byte[] ToBinary(object obj) { } @@ -1263,6 +1264,7 @@ namespace Akka.Persistence.Serialization public class PersistenceSnapshotSerializer : Akka.Serialization.Serializer { public PersistenceSnapshotSerializer(Akka.Actor.ExtendedActorSystem system) { } + public override int Identifier { get; } public override bool IncludeManifest { get; } public override object FromBinary(byte[] bytes, System.Type type) { } public override byte[] ToBinary(object obj) { } diff --git a/src/core/Akka.API.Tests/verify/CoreAPISpec.ApproveRemote.DotNet.verified.txt b/src/core/Akka.API.Tests/verify/CoreAPISpec.ApproveRemote.DotNet.verified.txt index e20f156893a..1603f75248a 100644 --- a/src/core/Akka.API.Tests/verify/CoreAPISpec.ApproveRemote.DotNet.verified.txt +++ b/src/core/Akka.API.Tests/verify/CoreAPISpec.ApproveRemote.DotNet.verified.txt @@ -428,6 +428,7 @@ namespace Akka.Remote.Serialization public class DaemonMsgCreateSerializer : Akka.Serialization.Serializer { public DaemonMsgCreateSerializer(Akka.Actor.ExtendedActorSystem system) { } + public override int Identifier { get; } public override bool IncludeManifest { get; } public override object FromBinary(byte[] bytes, System.Type type) { } public override byte[] ToBinary(object obj) { } @@ -435,6 +436,7 @@ namespace Akka.Remote.Serialization public class MessageContainerSerializer : Akka.Serialization.Serializer { public MessageContainerSerializer(Akka.Actor.ExtendedActorSystem system) { } + public override int Identifier { get; } public override bool IncludeManifest { get; } public override object FromBinary(byte[] bytes, System.Type type) { } public override byte[] ToBinary(object obj) { } @@ -442,6 +444,7 @@ namespace Akka.Remote.Serialization public sealed class MiscMessageSerializer : Akka.Serialization.SerializerWithStringManifest { public MiscMessageSerializer(Akka.Actor.ExtendedActorSystem system) { } + public override int Identifier { get; } public override object FromBinary(byte[] bytes, string manifest) { } public override string Manifest(object obj) { } public override byte[] ToBinary(object obj) { } @@ -449,6 +452,7 @@ namespace Akka.Remote.Serialization public sealed class PrimitiveSerializers : Akka.Serialization.SerializerWithStringManifest { public PrimitiveSerializers(Akka.Actor.ExtendedActorSystem system, Akka.Configuration.Config config) { } + public override int Identifier { get; } public override object FromBinary(byte[] bytes, string manifest) { } public override string Manifest(object obj) { } public override byte[] ToBinary(object obj) { } @@ -456,6 +460,7 @@ namespace Akka.Remote.Serialization public class ProtobufSerializer : Akka.Serialization.Serializer { public ProtobufSerializer(Akka.Actor.ExtendedActorSystem system) { } + public override int Identifier { get; } public override bool IncludeManifest { get; } public override object FromBinary(byte[] bytes, System.Type type) { } public override byte[] ToBinary(object obj) { } @@ -463,6 +468,7 @@ namespace Akka.Remote.Serialization public sealed class SystemMessageSerializer : Akka.Serialization.Serializer { public SystemMessageSerializer(Akka.Actor.ExtendedActorSystem system) { } + public override int Identifier { get; } public override bool IncludeManifest { get; } public override object FromBinary(byte[] bytes, System.Type type) { } public override byte[] ToBinary(object obj) { } diff --git a/src/core/Akka.API.Tests/verify/CoreAPISpec.ApproveRemote.Net.verified.txt b/src/core/Akka.API.Tests/verify/CoreAPISpec.ApproveRemote.Net.verified.txt index 5637795f550..710553dcb1d 100644 --- a/src/core/Akka.API.Tests/verify/CoreAPISpec.ApproveRemote.Net.verified.txt +++ b/src/core/Akka.API.Tests/verify/CoreAPISpec.ApproveRemote.Net.verified.txt @@ -428,6 +428,7 @@ namespace Akka.Remote.Serialization public class DaemonMsgCreateSerializer : Akka.Serialization.Serializer { public DaemonMsgCreateSerializer(Akka.Actor.ExtendedActorSystem system) { } + public override int Identifier { get; } public override bool IncludeManifest { get; } public override object FromBinary(byte[] bytes, System.Type type) { } public override byte[] ToBinary(object obj) { } @@ -435,6 +436,7 @@ namespace Akka.Remote.Serialization public class MessageContainerSerializer : Akka.Serialization.Serializer { public MessageContainerSerializer(Akka.Actor.ExtendedActorSystem system) { } + public override int Identifier { get; } public override bool IncludeManifest { get; } public override object FromBinary(byte[] bytes, System.Type type) { } public override byte[] ToBinary(object obj) { } @@ -442,6 +444,7 @@ namespace Akka.Remote.Serialization public sealed class MiscMessageSerializer : Akka.Serialization.SerializerWithStringManifest { public MiscMessageSerializer(Akka.Actor.ExtendedActorSystem system) { } + public override int Identifier { get; } public override object FromBinary(byte[] bytes, string manifest) { } public override string Manifest(object obj) { } public override byte[] ToBinary(object obj) { } @@ -449,6 +452,7 @@ namespace Akka.Remote.Serialization public sealed class PrimitiveSerializers : Akka.Serialization.SerializerWithStringManifest { public PrimitiveSerializers(Akka.Actor.ExtendedActorSystem system, Akka.Configuration.Config config) { } + public override int Identifier { get; } public override object FromBinary(byte[] bytes, string manifest) { } public override string Manifest(object obj) { } public override byte[] ToBinary(object obj) { } @@ -456,6 +460,7 @@ namespace Akka.Remote.Serialization public class ProtobufSerializer : Akka.Serialization.Serializer { public ProtobufSerializer(Akka.Actor.ExtendedActorSystem system) { } + public override int Identifier { get; } public override bool IncludeManifest { get; } public override object FromBinary(byte[] bytes, System.Type type) { } public override byte[] ToBinary(object obj) { } @@ -463,6 +468,7 @@ namespace Akka.Remote.Serialization public sealed class SystemMessageSerializer : Akka.Serialization.Serializer { public SystemMessageSerializer(Akka.Actor.ExtendedActorSystem system) { } + public override int Identifier { get; } public override bool IncludeManifest { get; } public override object FromBinary(byte[] bytes, System.Type type) { } public override byte[] ToBinary(object obj) { } diff --git a/src/core/Akka.API.Tests/verify/CoreAPISpec.ApproveStreams.DotNet.verified.txt b/src/core/Akka.API.Tests/verify/CoreAPISpec.ApproveStreams.DotNet.verified.txt index 72fbdfa87aa..a859684a446 100644 --- a/src/core/Akka.API.Tests/verify/CoreAPISpec.ApproveStreams.DotNet.verified.txt +++ b/src/core/Akka.API.Tests/verify/CoreAPISpec.ApproveStreams.DotNet.verified.txt @@ -4792,6 +4792,7 @@ namespace Akka.Streams.Serialization public sealed class StreamRefSerializer : Akka.Serialization.SerializerWithStringManifest { public StreamRefSerializer(Akka.Actor.ExtendedActorSystem system) { } + public override int Identifier { get; } public override object FromBinary(byte[] bytes, string manifest) { } public override string Manifest(object o) { } public override byte[] ToBinary(object o) { } diff --git a/src/core/Akka.API.Tests/verify/CoreAPISpec.ApproveStreams.Net.verified.txt b/src/core/Akka.API.Tests/verify/CoreAPISpec.ApproveStreams.Net.verified.txt index 87cd5551256..40182de3ff0 100644 --- a/src/core/Akka.API.Tests/verify/CoreAPISpec.ApproveStreams.Net.verified.txt +++ b/src/core/Akka.API.Tests/verify/CoreAPISpec.ApproveStreams.Net.verified.txt @@ -4791,6 +4791,7 @@ namespace Akka.Streams.Serialization public sealed class StreamRefSerializer : Akka.Serialization.SerializerWithStringManifest { public StreamRefSerializer(Akka.Actor.ExtendedActorSystem system) { } + public override int Identifier { get; } public override object FromBinary(byte[] bytes, string manifest) { } public override string Manifest(object o) { } public override byte[] ToBinary(object o) { } diff --git a/src/core/Akka.Cluster/Serialization/ClusterMessageSerializer.cs b/src/core/Akka.Cluster/Serialization/ClusterMessageSerializer.cs index b8fec3211c4..b6e8adc6394 100644 --- a/src/core/Akka.Cluster/Serialization/ClusterMessageSerializer.cs +++ b/src/core/Akka.Cluster/Serialization/ClusterMessageSerializer.cs @@ -72,6 +72,12 @@ public ClusterMessageSerializer(ExtendedActorSystem system) : base(system) } + /// + /// + /// A subclass keeps resolving its own id from HOCON via . + /// + public override int Identifier => GetType() == typeof(ClusterMessageSerializer) ? 5 : base.Identifier; + public override byte[] ToBinary(object obj) { switch (obj) diff --git a/src/core/Akka.Cluster/Serialization/ReliableDeliverySerializer.cs b/src/core/Akka.Cluster/Serialization/ReliableDeliverySerializer.cs index 55fb8933b45..f8f437f8043 100644 --- a/src/core/Akka.Cluster/Serialization/ReliableDeliverySerializer.cs +++ b/src/core/Akka.Cluster/Serialization/ReliableDeliverySerializer.cs @@ -46,6 +46,9 @@ public ReliableDeliverySerializer(ExtendedActorSystem system) : base(system) _payloadSupport = new WrappedPayloadSupport(system); } + /// + public override int Identifier => 36; + public override byte[] ToBinary(object obj) { switch (obj) diff --git a/src/core/Akka.Persistence/Serialization/PersistenceMessageSerializer.cs b/src/core/Akka.Persistence/Serialization/PersistenceMessageSerializer.cs index 36b2cea7af5..f88846ef94e 100644 --- a/src/core/Akka.Persistence/Serialization/PersistenceMessageSerializer.cs +++ b/src/core/Akka.Persistence/Serialization/PersistenceMessageSerializer.cs @@ -25,6 +25,9 @@ public PersistenceMessageSerializer(ExtendedActorSystem system) : base(system) { } + /// + public override int Identifier => 7; + public override bool IncludeManifest { get; } = true; public override byte[] ToBinary(object obj) diff --git a/src/core/Akka.Persistence/Serialization/PersistenceSnapshotSerializer.cs b/src/core/Akka.Persistence/Serialization/PersistenceSnapshotSerializer.cs index e2e06978a99..5d4e074d328 100644 --- a/src/core/Akka.Persistence/Serialization/PersistenceSnapshotSerializer.cs +++ b/src/core/Akka.Persistence/Serialization/PersistenceSnapshotSerializer.cs @@ -20,6 +20,12 @@ public PersistenceSnapshotSerializer(ExtendedActorSystem system) : base(system) IncludeManifest = true; } + /// + /// + /// A subclass keeps resolving its own id from HOCON via . + /// + public override int Identifier => GetType() == typeof(PersistenceSnapshotSerializer) ? 8 : base.Identifier; + public override bool IncludeManifest { get; } public override byte[] ToBinary(object obj) diff --git a/src/core/Akka.Remote/Serialization/DaemonMsgCreateSerializer.cs b/src/core/Akka.Remote/Serialization/DaemonMsgCreateSerializer.cs index ab7f54cd440..66aa3fc1047 100644 --- a/src/core/Akka.Remote/Serialization/DaemonMsgCreateSerializer.cs +++ b/src/core/Akka.Remote/Serialization/DaemonMsgCreateSerializer.cs @@ -30,6 +30,12 @@ public DaemonMsgCreateSerializer(ExtendedActorSystem system) : base(system) { } + /// + /// + /// A subclass keeps resolving its own id from HOCON via . + /// + public override int Identifier => GetType() == typeof(DaemonMsgCreateSerializer) ? 3 : base.Identifier; + /// public override bool IncludeManifest => false; diff --git a/src/core/Akka.Remote/Serialization/MessageContainerSerializer.cs b/src/core/Akka.Remote/Serialization/MessageContainerSerializer.cs index 6d08d18950e..b18753a9628 100644 --- a/src/core/Akka.Remote/Serialization/MessageContainerSerializer.cs +++ b/src/core/Akka.Remote/Serialization/MessageContainerSerializer.cs @@ -31,6 +31,12 @@ public MessageContainerSerializer(ExtendedActorSystem system) : base(system) _payloadSupport = new WrappedPayloadSupport(system); } + /// + /// + /// A subclass keeps resolving its own id from HOCON via . + /// + public override int Identifier => GetType() == typeof(MessageContainerSerializer) ? 6 : base.Identifier; + /// public override bool IncludeManifest => false; diff --git a/src/core/Akka.Remote/Serialization/MiscMessageSerializer.cs b/src/core/Akka.Remote/Serialization/MiscMessageSerializer.cs index d88a9473eb1..4ada5289274 100644 --- a/src/core/Akka.Remote/Serialization/MiscMessageSerializer.cs +++ b/src/core/Akka.Remote/Serialization/MiscMessageSerializer.cs @@ -61,6 +61,9 @@ public MiscMessageSerializer(ExtendedActorSystem system) : base(system) _exceptionSupport = new ExceptionSupport(system); } + /// + public override int Identifier => 16; + /// public override byte[] ToBinary(object obj) { diff --git a/src/core/Akka.Remote/Serialization/PrimitiveSerializers.cs b/src/core/Akka.Remote/Serialization/PrimitiveSerializers.cs index a3badc9f27c..e083063b434 100644 --- a/src/core/Akka.Remote/Serialization/PrimitiveSerializers.cs +++ b/src/core/Akka.Remote/Serialization/PrimitiveSerializers.cs @@ -45,6 +45,9 @@ public PrimitiveSerializers(ExtendedActorSystem system, Config config) : base(sy _useLegacyBehavior = config.GetBoolean("use-legacy-behavior"); } + /// + public override int Identifier => 17; + /// public override byte[] ToBinary(object obj) { diff --git a/src/core/Akka.Remote/Serialization/ProtobufSerializer.cs b/src/core/Akka.Remote/Serialization/ProtobufSerializer.cs index f2956721185..763b7bb0e01 100644 --- a/src/core/Akka.Remote/Serialization/ProtobufSerializer.cs +++ b/src/core/Akka.Remote/Serialization/ProtobufSerializer.cs @@ -29,6 +29,12 @@ public ProtobufSerializer(ExtendedActorSystem system) : base(system) { } + /// + /// + /// A subclass keeps resolving its own id from HOCON via . + /// + public override int Identifier => GetType() == typeof(ProtobufSerializer) ? 2 : base.Identifier; + /// public override bool IncludeManifest => true; diff --git a/src/core/Akka.Remote/Serialization/SystemMessageSerializer.cs b/src/core/Akka.Remote/Serialization/SystemMessageSerializer.cs index 14f41af3dc0..754db99dee0 100644 --- a/src/core/Akka.Remote/Serialization/SystemMessageSerializer.cs +++ b/src/core/Akka.Remote/Serialization/SystemMessageSerializer.cs @@ -33,6 +33,9 @@ public SystemMessageSerializer(ExtendedActorSystem system) : base(system) _exceptionSupport = new ExceptionSupport(system); } + /// + public override int Identifier => 22; + /// public override bool IncludeManifest { get; } = true; // TODO: should be false diff --git a/src/core/Akka.Streams/Serialization/StreamRefSerializer.cs b/src/core/Akka.Streams/Serialization/StreamRefSerializer.cs index 44f64c724c6..49b1656555b 100644 --- a/src/core/Akka.Streams/Serialization/StreamRefSerializer.cs +++ b/src/core/Akka.Streams/Serialization/StreamRefSerializer.cs @@ -38,6 +38,9 @@ public StreamRefSerializer(ExtendedActorSystem system) : base(system) _system = system; } + /// + public override int Identifier => 30; + public override string Manifest(object o) { return o switch diff --git a/src/core/Akka.Tests/Serialization/SerializerSubclassIdentifierSpec.cs b/src/core/Akka.Tests/Serialization/SerializerSubclassIdentifierSpec.cs new file mode 100644 index 00000000000..be654b3031b --- /dev/null +++ b/src/core/Akka.Tests/Serialization/SerializerSubclassIdentifierSpec.cs @@ -0,0 +1,46 @@ +//----------------------------------------------------------------------- +// +// Copyright (C) 2009-2025 Lightbend Inc. +// Copyright (C) 2013-2025 .NET Foundation +// +//----------------------------------------------------------------------- + +#nullable enable +using Akka.Actor; +using Akka.Configuration; +using Akka.Serialization; +using FluentAssertions; +using Xunit; + +namespace Akka.Tests.Serialization +{ + /// + /// A public, unsealed built-in serializer's Identifier override guards on GetType(), so a user + /// subclass registered under its own HOCON id keeps resolving that id from HOCON instead of inheriting the + /// built-in's hardcoded constant. + /// + public class SerializerSubclassIdentifierSpec + { + public sealed class MyJsonSerializer : NewtonSoftJsonSerializer + { + public MyJsonSerializer(ExtendedActorSystem system) : base(system) + { + } + } + + [Fact(DisplayName = "Should_keep_resolving_its_own_HOCON_id_When_a_user_subclasses_a_builtin_serializer")] + public void Should_keep_resolving_its_own_HOCON_id_When_a_user_subclasses_a_builtin_serializer() + { + var config = ConfigurationFactory.ParseString(@" + akka.actor.serialization-identifiers { + ""Akka.Tests.Serialization.SerializerSubclassIdentifierSpec+MyJsonSerializer, Akka.Tests"" = 100 + }"); + + using var system = ActorSystem.Create(nameof(SerializerSubclassIdentifierSpec), config); + var extendedSystem = (ExtendedActorSystem)system; + + new MyJsonSerializer(extendedSystem).Identifier.Should().Be(100); + new NewtonSoftJsonSerializer(extendedSystem).Identifier.Should().Be(1); + } + } +} diff --git a/src/core/Akka/Serialization/ByteArraySerializer.cs b/src/core/Akka/Serialization/ByteArraySerializer.cs index 577e3bd2137..3e98ca4c654 100644 --- a/src/core/Akka/Serialization/ByteArraySerializer.cs +++ b/src/core/Akka/Serialization/ByteArraySerializer.cs @@ -26,6 +26,12 @@ public ByteArraySerializer(ExtendedActorSystem system) { } + /// + /// + /// A subclass keeps resolving its own id from HOCON via . + /// + public override int Identifier => GetType() == typeof(ByteArraySerializer) ? 4 : base.Identifier; + /// /// Byte arrays preserve their legacy empty manifest for wire compatibility. /// diff --git a/src/core/Akka/Serialization/NewtonSoftJsonSerializer.cs b/src/core/Akka/Serialization/NewtonSoftJsonSerializer.cs index 7474e21cad8..f35c8abf7be 100644 --- a/src/core/Akka/Serialization/NewtonSoftJsonSerializer.cs +++ b/src/core/Akka/Serialization/NewtonSoftJsonSerializer.cs @@ -238,7 +238,11 @@ public NewtonSoftJsonSerializer(ExtendedActorSystem system, NewtonSoftJsonSerial _surrogateExcludedSerializer.Converters.Remove(surrogateConverter); } - + /// + /// + /// A subclass keeps resolving its own id from HOCON via . + /// + public override int Identifier => GetType() == typeof(NewtonSoftJsonSerializer) ? 1 : base.Identifier; private static JsonConverter CreateConverter(Type converterType, ExtendedActorSystem actorSystem) {