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 c8dcb164155..c1f66881205 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 @@ -1264,6 +1264,15 @@ namespace Akka.Actor public Akka.Actor.IActorRef Watch(Akka.Actor.IActorRef subject) { } public Akka.Actor.IActorRef WatchWith(Akka.Actor.IActorRef subject, object message) { } } + public sealed class IntentionalActorRestartException : Akka.Actor.AkkaException + { + public IntentionalActorRestartException() { } + } + public sealed class IntentionalRestart : Akka.Actor.IAutoReceivedMessage + { + public static Akka.Actor.IntentionalRestart Instance { get; } + public override string ToString() { } + } [Akka.Annotations.InternalApiAttribute()] public abstract class InternalActorRefBase : Akka.Actor.ActorRefBase, Akka.Actor.IActorRef, Akka.Actor.IActorRefScope, Akka.Actor.ICanTell, Akka.Actor.IInternalActorRef, Akka.Util.ISurrogated, System.IComparable, System.IComparable, System.IEquatable { 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 d1553938cc4..b1b21cc00a1 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 @@ -1262,6 +1262,15 @@ namespace Akka.Actor public Akka.Actor.IActorRef Watch(Akka.Actor.IActorRef subject) { } public Akka.Actor.IActorRef WatchWith(Akka.Actor.IActorRef subject, object message) { } } + public sealed class IntentionalActorRestartException : Akka.Actor.AkkaException + { + public IntentionalActorRestartException() { } + } + public sealed class IntentionalRestart : Akka.Actor.IAutoReceivedMessage + { + public static Akka.Actor.IntentionalRestart Instance { get; } + public override string ToString() { } + } [Akka.Annotations.InternalApiAttribute()] public abstract class InternalActorRefBase : Akka.Actor.ActorRefBase, Akka.Actor.IActorRef, Akka.Actor.IActorRefScope, Akka.Actor.ICanTell, Akka.Actor.IInternalActorRef, Akka.Util.ISurrogated, System.IComparable, System.IComparable, System.IEquatable { diff --git a/src/core/Akka.Remote.Tests/Serialization/MiscMessageSerializerSpec.cs b/src/core/Akka.Remote.Tests/Serialization/MiscMessageSerializerSpec.cs index 6eb75f2cd69..39a12a46ece 100644 --- a/src/core/Akka.Remote.Tests/Serialization/MiscMessageSerializerSpec.cs +++ b/src/core/Akka.Remote.Tests/Serialization/MiscMessageSerializerSpec.cs @@ -159,6 +159,13 @@ public void Can_serialize_Kill() AssertEqual(kill); } + [Fact] + public void Can_serialize_IntentionalRestart() + { + var restart = IntentionalRestart.Instance; + AssertEqual(restart); + } + [Fact] public void Can_serialize_PoisonPill() { diff --git a/src/core/Akka.Remote/Configuration/Remote.conf b/src/core/Akka.Remote/Configuration/Remote.conf index 9e6413ac0df..9da126e1fde 100644 --- a/src/core/Akka.Remote/Configuration/Remote.conf +++ b/src/core/Akka.Remote/Configuration/Remote.conf @@ -37,6 +37,7 @@ akka { "Akka.Actor.ActorIdentity, Akka" = akka-misc "Akka.Actor.IActorRef, Akka" = akka-misc "Akka.Actor.PoisonPill, Akka" = akka-misc + "Akka.Actor.IntentionalRestart, Akka" = akka-misc "Akka.Actor.Kill, Akka" = akka-misc "Akka.Actor.PoisonPill, Akka" = akka-misc "Akka.Actor.Status+Failure, Akka" = akka-misc diff --git a/src/core/Akka.Remote/Serialization/MiscMessageSerializer.cs b/src/core/Akka.Remote/Serialization/MiscMessageSerializer.cs index c2d496b01cd..c75bddcde28 100644 --- a/src/core/Akka.Remote/Serialization/MiscMessageSerializer.cs +++ b/src/core/Akka.Remote/Serialization/MiscMessageSerializer.cs @@ -28,6 +28,7 @@ public sealed class MiscMessageSerializer : SerializerWithStringManifest private const string ActorRefManifest = "AR"; private const string PoisonPillManifest = "PP"; private const string KillManifest = "K"; + private const string IntentionalRestartManifest = "IR"; private const string RemoteWatcherHearthbeatManifest = "RWHB"; private const string RemoteWatcherHearthbeatRspManifest = "RWHR"; private const string LocalScopeManifest = "LS"; @@ -74,6 +75,7 @@ public override byte[] ToBinary(object obj) case PoisonPill _: case Kill _: case RemoteWatcher.Heartbeat _: + case IntentionalRestart _: return EmptyBytes; case RemoteWatcher.HeartbeatRsp heartbeatRsp: return HeartbeatRspToProto(heartbeatRsp); @@ -125,6 +127,8 @@ public override string Manifest(object obj) return PoisonPillManifest; case Kill _: return KillManifest; + case IntentionalRestart _: + return IntentionalRestartManifest; case RemoteWatcher.Heartbeat _: return RemoteWatcherHearthbeatManifest; case RemoteWatcher.HeartbeatRsp _: @@ -177,6 +181,8 @@ public override object FromBinary(byte[] bytes, string manifest) return PoisonPill.Instance; case KillManifest: return Kill.Instance; + case IntentionalRestartManifest: + return IntentionalRestart.Instance; case RemoteWatcherHearthbeatManifest: return RemoteWatcher.Heartbeat.Instance; case RemoteWatcherHearthbeatRspManifest: diff --git a/src/core/Akka.Tests/Actor/ActorLifeCycleSpec.cs b/src/core/Akka.Tests/Actor/ActorLifeCycleSpec.cs index 60ed4f4250a..f24e95590a1 100644 --- a/src/core/Akka.Tests/Actor/ActorLifeCycleSpec.cs +++ b/src/core/Akka.Tests/Actor/ActorLifeCycleSpec.cs @@ -22,28 +22,28 @@ public class ActorLifeCycleSpec : AkkaSpec { public class LifeCycleTestActor : UntypedActor { - private AtomicCounter generationProvider; - private string id; - private IActorRef testActor; - private int CurrentGeneration; + private AtomicCounter _generationProvider; + private readonly string _id; + private readonly IActorRef _testActor; + private readonly int _currentGeneration; public LifeCycleTestActor(IActorRef testActor,string id,AtomicCounter generationProvider) { - this.testActor = testActor; - this.id = id; - this.generationProvider = generationProvider; - this.CurrentGeneration = generationProvider.Next(); + _testActor = testActor; + _id = id; + _generationProvider = generationProvider; + _currentGeneration = generationProvider.Next(); } private void Report(object message) { - testActor.Tell(((string)message,id,CurrentGeneration)); + _testActor.Tell(((string)message,_id,_currentGeneration)); } protected override void OnReceive(object message) { if (message is string s && s == "status") { - testActor.Tell(("OK",id,CurrentGeneration)); + _testActor.Tell(("OK",_id,_currentGeneration)); } } @@ -323,16 +323,16 @@ public class Count { } public class KillableActor : UntypedActor { - private IActorRef testActor; + private readonly IActorRef _testActor; public KillableActor(IActorRef testActor) { - this.testActor = testActor; + _testActor = testActor; } protected override void PostStop() { Debug.WriteLine("inside poststop"); - testActor.Tell(("Terminated", Self.Path.Name)); + _testActor.Tell(("Terminated", Self.Path.Name)); } protected override void OnReceive(object message) @@ -378,7 +378,7 @@ public async Task Clear_child_upon_terminated() } - class MyCustomException : Exception {} + private class MyCustomException : Exception {} [Fact(DisplayName="PreRestart should receive correct cause, message and sender")] public async Task Call_PreStart_with_correct_message_and_sender() @@ -393,7 +393,12 @@ public async Task Call_PreStart_with_correct_message_and_sender() c.OnPreRestart = (ex, mess, context) => { TestActor.Tell(ex); - TestActor.Tell(mess); + + // can't relay the Restart back because that will blow up the TestActor + if (mess is not IntentionalRestart) + { + TestActor.Tell(mess); + } TestActor.Tell(context.Sender); }; }); @@ -405,6 +410,11 @@ public async Task Call_PreStart_with_correct_message_and_sender() await ExpectMsgAsync(); await ExpectMsgAsync(message); await ExpectMsgAsync(TestActor); + + // test the `Restart` built-in message + broken.Tell(IntentionalRestart.Instance); + await ExpectMsgAsync(); + await ExpectMsgAsync(TestActor); } } } diff --git a/src/core/Akka/Actor/ActorCell.DefaultMessages.cs b/src/core/Akka/Actor/ActorCell.DefaultMessages.cs index 434ef7ada7e..e0716140e68 100644 --- a/src/core/Akka/Actor/ActorCell.DefaultMessages.cs +++ b/src/core/Akka/Actor/ActorCell.DefaultMessages.cs @@ -132,27 +132,38 @@ protected internal virtual void AutoReceiveMessage(Envelope envelope) switch (message) { + case ActorSelectionMessage selectionMessage: + ReceiveSelection(selectionMessage); + break; + case Identify identify: + HandleIdentity(identify); + break; case Terminated terminated: ReceivedTerminated(terminated); break; + case PoisonPill _: + HandlePoisonPill(); + break; case AddressTerminated terminated: AddressTerminated(terminated.Address); break; case Kill _: Kill(); break; - case PoisonPill _: - HandlePoisonPill(); - break; - case ActorSelectionMessage selectionMessage: - ReceiveSelection(selectionMessage); - break; - case Identify identify: - HandleIdentity(identify); + case Akka.Actor.IntentionalRestart: + TriggerIntentionalRestart(); break; } } + /// + /// Done in response to receiving a message. + /// + private static void TriggerIntentionalRestart() + { + throw new IntentionalActorRestartException(); + } + /// /// This is only intended to be called from TestKit's TestActorRef /// diff --git a/src/core/Akka/Actor/Exceptions.cs b/src/core/Akka/Actor/Exceptions.cs index 4effbaacbb6..e968ded2593 100644 --- a/src/core/Akka/Actor/Exceptions.cs +++ b/src/core/Akka/Actor/Exceptions.cs @@ -256,6 +256,17 @@ protected LoggerInitializationException(SerializationInfo info, StreamingContext } } + /// + /// Thrown when an actor is sent a message. + /// + /// + /// Meant to be used primarily for testing purposes. + /// + public sealed class IntentionalActorRestartException : AkkaException + { + public IntentionalActorRestartException() : base("Intentional actor restart") { } + } + /// /// This exception is thrown when a message has been sent to an Actor. /// will by default stop the actor. diff --git a/src/core/Akka/Actor/IAutoReceivedMessage.cs b/src/core/Akka/Actor/IAutoReceivedMessage.cs index 2689b926cf4..92058e8a527 100644 --- a/src/core/Akka/Actor/IAutoReceivedMessage.cs +++ b/src/core/Akka/Actor/IAutoReceivedMessage.cs @@ -213,6 +213,21 @@ public override string ToString() } } + /// + /// Sending a message will force it to throw a + /// when it processes the message. + /// + public sealed class IntentionalRestart : IAutoReceivedMessage + { + private IntentionalRestart() { } + public static IntentionalRestart Instance { get; } = new(); + + public override string ToString() + { + return ""; + } + } + /// /// Sending an message to an actor causes the actor to throw an /// when it processes the message, which gets handled using the normal supervisor mechanism.