Skip to content

Commit

Permalink
Akka.Actor: Added built-in IntentionalRestart message to test actor…
Browse files Browse the repository at this point in the history
… restart behaviors (#7493)

* Added `Restart` message to trigger intentional restarts

close #7492

* renamed to `IntentionalRestart`

* added API approvals
  • Loading branch information
Aaronontheweb authored Feb 3, 2025
1 parent c8ebf77 commit 1f5688d
Show file tree
Hide file tree
Showing 9 changed files with 102 additions and 23 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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<Akka.Actor.IActorRef>, System.IEquatable<Akka.Actor.IActorRef>
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<Akka.Actor.IActorRef>, System.IEquatable<Akka.Actor.IActorRef>
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()
{
Expand Down
1 change: 1 addition & 0 deletions src/core/Akka.Remote/Configuration/Remote.conf
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 6 additions & 0 deletions src/core/Akka.Remote/Serialization/MiscMessageSerializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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 _:
Expand Down Expand Up @@ -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:
Expand Down
40 changes: 25 additions & 15 deletions src/core/Akka.Tests/Actor/ActorLifeCycleSpec.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}
}

Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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()
Expand All @@ -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);
};
});
Expand All @@ -405,6 +410,11 @@ public async Task Call_PreStart_with_correct_message_and_sender()
await ExpectMsgAsync<MyCustomException>();
await ExpectMsgAsync(message);
await ExpectMsgAsync(TestActor);

// test the `Restart` built-in message
broken.Tell(IntentionalRestart.Instance);
await ExpectMsgAsync<IntentionalActorRestartException>();
await ExpectMsgAsync(TestActor);
}
}
}
Expand Down
27 changes: 19 additions & 8 deletions src/core/Akka/Actor/ActorCell.DefaultMessages.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}

/// <summary>
/// Done in response to receiving a <see cref="IntentionalRestart"/> message.
/// </summary>
private static void TriggerIntentionalRestart()
{
throw new IntentionalActorRestartException();
}

/// <summary>
/// This is only intended to be called from TestKit's TestActorRef
/// </summary>
Expand Down
11 changes: 11 additions & 0 deletions src/core/Akka/Actor/Exceptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,17 @@ protected LoggerInitializationException(SerializationInfo info, StreamingContext
}
}

/// <summary>
/// Thrown when an actor is sent a <see cref="IntentionalRestart"/> message.
/// </summary>
/// <remarks>
/// Meant to be used primarily for testing purposes.
/// </remarks>
public sealed class IntentionalActorRestartException : AkkaException
{
public IntentionalActorRestartException() : base("Intentional actor restart") { }
}

/// <summary>
/// This exception is thrown when a <see cref="Kill"/> message has been sent to an Actor.
/// <see cref="SupervisorStrategy.DefaultDecider"/> will by default stop the actor.
Expand Down
15 changes: 15 additions & 0 deletions src/core/Akka/Actor/IAutoReceivedMessage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,21 @@ public override string ToString()
}
}

/// <summary>
/// Sending a <see cref="IntentionalRestart"/> message will force it to throw a <see cref="IntentionalActorRestartException"/>
/// when it processes the message.
/// </summary>
public sealed class IntentionalRestart : IAutoReceivedMessage
{
private IntentionalRestart() { }
public static IntentionalRestart Instance { get; } = new();

public override string ToString()
{
return "<Restart>";
}
}

/// <summary>
/// Sending an <see cref="Kill"/> message to an actor causes the actor to throw an
/// <see cref="ActorKilledException"/> when it processes the message, which gets handled using the normal supervisor mechanism.
Expand Down

0 comments on commit 1f5688d

Please sign in to comment.