Skip to content

Commit 1f5688d

Browse files
Akka.Actor: Added built-in IntentionalRestart message to test actor restart behaviors (#7493)
* Added `Restart` message to trigger intentional restarts close #7492 * renamed to `IntentionalRestart` * added API approvals
1 parent c8ebf77 commit 1f5688d

File tree

9 files changed

+102
-23
lines changed

9 files changed

+102
-23
lines changed

src/core/Akka.API.Tests/verify/CoreAPISpec.ApproveCore.DotNet.verified.txt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1264,6 +1264,15 @@ namespace Akka.Actor
12641264
public Akka.Actor.IActorRef Watch(Akka.Actor.IActorRef subject) { }
12651265
public Akka.Actor.IActorRef WatchWith(Akka.Actor.IActorRef subject, object message) { }
12661266
}
1267+
public sealed class IntentionalActorRestartException : Akka.Actor.AkkaException
1268+
{
1269+
public IntentionalActorRestartException() { }
1270+
}
1271+
public sealed class IntentionalRestart : Akka.Actor.IAutoReceivedMessage
1272+
{
1273+
public static Akka.Actor.IntentionalRestart Instance { get; }
1274+
public override string ToString() { }
1275+
}
12671276
[Akka.Annotations.InternalApiAttribute()]
12681277
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>
12691278
{

src/core/Akka.API.Tests/verify/CoreAPISpec.ApproveCore.Net.verified.txt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1262,6 +1262,15 @@ namespace Akka.Actor
12621262
public Akka.Actor.IActorRef Watch(Akka.Actor.IActorRef subject) { }
12631263
public Akka.Actor.IActorRef WatchWith(Akka.Actor.IActorRef subject, object message) { }
12641264
}
1265+
public sealed class IntentionalActorRestartException : Akka.Actor.AkkaException
1266+
{
1267+
public IntentionalActorRestartException() { }
1268+
}
1269+
public sealed class IntentionalRestart : Akka.Actor.IAutoReceivedMessage
1270+
{
1271+
public static Akka.Actor.IntentionalRestart Instance { get; }
1272+
public override string ToString() { }
1273+
}
12651274
[Akka.Annotations.InternalApiAttribute()]
12661275
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>
12671276
{

src/core/Akka.Remote.Tests/Serialization/MiscMessageSerializerSpec.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,13 @@ public void Can_serialize_Kill()
159159
AssertEqual(kill);
160160
}
161161

162+
[Fact]
163+
public void Can_serialize_IntentionalRestart()
164+
{
165+
var restart = IntentionalRestart.Instance;
166+
AssertEqual(restart);
167+
}
168+
162169
[Fact]
163170
public void Can_serialize_PoisonPill()
164171
{

src/core/Akka.Remote/Configuration/Remote.conf

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ akka {
3737
"Akka.Actor.ActorIdentity, Akka" = akka-misc
3838
"Akka.Actor.IActorRef, Akka" = akka-misc
3939
"Akka.Actor.PoisonPill, Akka" = akka-misc
40+
"Akka.Actor.IntentionalRestart, Akka" = akka-misc
4041
"Akka.Actor.Kill, Akka" = akka-misc
4142
"Akka.Actor.PoisonPill, Akka" = akka-misc
4243
"Akka.Actor.Status+Failure, Akka" = akka-misc

src/core/Akka.Remote/Serialization/MiscMessageSerializer.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ public sealed class MiscMessageSerializer : SerializerWithStringManifest
2828
private const string ActorRefManifest = "AR";
2929
private const string PoisonPillManifest = "PP";
3030
private const string KillManifest = "K";
31+
private const string IntentionalRestartManifest = "IR";
3132
private const string RemoteWatcherHearthbeatManifest = "RWHB";
3233
private const string RemoteWatcherHearthbeatRspManifest = "RWHR";
3334
private const string LocalScopeManifest = "LS";
@@ -74,6 +75,7 @@ public override byte[] ToBinary(object obj)
7475
case PoisonPill _:
7576
case Kill _:
7677
case RemoteWatcher.Heartbeat _:
78+
case IntentionalRestart _:
7779
return EmptyBytes;
7880
case RemoteWatcher.HeartbeatRsp heartbeatRsp:
7981
return HeartbeatRspToProto(heartbeatRsp);
@@ -125,6 +127,8 @@ public override string Manifest(object obj)
125127
return PoisonPillManifest;
126128
case Kill _:
127129
return KillManifest;
130+
case IntentionalRestart _:
131+
return IntentionalRestartManifest;
128132
case RemoteWatcher.Heartbeat _:
129133
return RemoteWatcherHearthbeatManifest;
130134
case RemoteWatcher.HeartbeatRsp _:
@@ -177,6 +181,8 @@ public override object FromBinary(byte[] bytes, string manifest)
177181
return PoisonPill.Instance;
178182
case KillManifest:
179183
return Kill.Instance;
184+
case IntentionalRestartManifest:
185+
return IntentionalRestart.Instance;
180186
case RemoteWatcherHearthbeatManifest:
181187
return RemoteWatcher.Heartbeat.Instance;
182188
case RemoteWatcherHearthbeatRspManifest:

src/core/Akka.Tests/Actor/ActorLifeCycleSpec.cs

Lines changed: 25 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -22,28 +22,28 @@ public class ActorLifeCycleSpec : AkkaSpec
2222
{
2323
public class LifeCycleTestActor : UntypedActor
2424
{
25-
private AtomicCounter generationProvider;
26-
private string id;
27-
private IActorRef testActor;
28-
private int CurrentGeneration;
25+
private AtomicCounter _generationProvider;
26+
private readonly string _id;
27+
private readonly IActorRef _testActor;
28+
private readonly int _currentGeneration;
2929
public LifeCycleTestActor(IActorRef testActor,string id,AtomicCounter generationProvider)
3030
{
31-
this.testActor = testActor;
32-
this.id = id;
33-
this.generationProvider = generationProvider;
34-
this.CurrentGeneration = generationProvider.Next();
31+
_testActor = testActor;
32+
_id = id;
33+
_generationProvider = generationProvider;
34+
_currentGeneration = generationProvider.Next();
3535
}
3636

3737
private void Report(object message)
3838
{
39-
testActor.Tell(((string)message,id,CurrentGeneration));
39+
_testActor.Tell(((string)message,_id,_currentGeneration));
4040
}
4141

4242
protected override void OnReceive(object message)
4343
{
4444
if (message is string s && s == "status")
4545
{
46-
testActor.Tell(("OK",id,CurrentGeneration));
46+
_testActor.Tell(("OK",_id,_currentGeneration));
4747
}
4848
}
4949

@@ -323,16 +323,16 @@ public class Count { }
323323

324324
public class KillableActor : UntypedActor
325325
{
326-
private IActorRef testActor;
326+
private readonly IActorRef _testActor;
327327
public KillableActor(IActorRef testActor)
328328
{
329-
this.testActor = testActor;
329+
_testActor = testActor;
330330
}
331331

332332
protected override void PostStop()
333333
{
334334
Debug.WriteLine("inside poststop");
335-
testActor.Tell(("Terminated", Self.Path.Name));
335+
_testActor.Tell(("Terminated", Self.Path.Name));
336336
}
337337

338338
protected override void OnReceive(object message)
@@ -378,7 +378,7 @@ public async Task Clear_child_upon_terminated()
378378
}
379379

380380

381-
class MyCustomException : Exception {}
381+
private class MyCustomException : Exception {}
382382

383383
[Fact(DisplayName="PreRestart should receive correct cause, message and sender")]
384384
public async Task Call_PreStart_with_correct_message_and_sender()
@@ -393,7 +393,12 @@ public async Task Call_PreStart_with_correct_message_and_sender()
393393
c.OnPreRestart = (ex, mess, context) =>
394394
{
395395
TestActor.Tell(ex);
396-
TestActor.Tell(mess);
396+
397+
// can't relay the Restart back because that will blow up the TestActor
398+
if (mess is not IntentionalRestart)
399+
{
400+
TestActor.Tell(mess);
401+
}
397402
TestActor.Tell(context.Sender);
398403
};
399404
});
@@ -405,6 +410,11 @@ public async Task Call_PreStart_with_correct_message_and_sender()
405410
await ExpectMsgAsync<MyCustomException>();
406411
await ExpectMsgAsync(message);
407412
await ExpectMsgAsync(TestActor);
413+
414+
// test the `Restart` built-in message
415+
broken.Tell(IntentionalRestart.Instance);
416+
await ExpectMsgAsync<IntentionalActorRestartException>();
417+
await ExpectMsgAsync(TestActor);
408418
}
409419
}
410420
}

src/core/Akka/Actor/ActorCell.DefaultMessages.cs

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -132,27 +132,38 @@ protected internal virtual void AutoReceiveMessage(Envelope envelope)
132132

133133
switch (message)
134134
{
135+
case ActorSelectionMessage selectionMessage:
136+
ReceiveSelection(selectionMessage);
137+
break;
138+
case Identify identify:
139+
HandleIdentity(identify);
140+
break;
135141
case Terminated terminated:
136142
ReceivedTerminated(terminated);
137143
break;
144+
case PoisonPill _:
145+
HandlePoisonPill();
146+
break;
138147
case AddressTerminated terminated:
139148
AddressTerminated(terminated.Address);
140149
break;
141150
case Kill _:
142151
Kill();
143152
break;
144-
case PoisonPill _:
145-
HandlePoisonPill();
146-
break;
147-
case ActorSelectionMessage selectionMessage:
148-
ReceiveSelection(selectionMessage);
149-
break;
150-
case Identify identify:
151-
HandleIdentity(identify);
153+
case Akka.Actor.IntentionalRestart:
154+
TriggerIntentionalRestart();
152155
break;
153156
}
154157
}
155158

159+
/// <summary>
160+
/// Done in response to receiving a <see cref="IntentionalRestart"/> message.
161+
/// </summary>
162+
private static void TriggerIntentionalRestart()
163+
{
164+
throw new IntentionalActorRestartException();
165+
}
166+
156167
/// <summary>
157168
/// This is only intended to be called from TestKit's TestActorRef
158169
/// </summary>

src/core/Akka/Actor/Exceptions.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,17 @@ protected LoggerInitializationException(SerializationInfo info, StreamingContext
256256
}
257257
}
258258

259+
/// <summary>
260+
/// Thrown when an actor is sent a <see cref="IntentionalRestart"/> message.
261+
/// </summary>
262+
/// <remarks>
263+
/// Meant to be used primarily for testing purposes.
264+
/// </remarks>
265+
public sealed class IntentionalActorRestartException : AkkaException
266+
{
267+
public IntentionalActorRestartException() : base("Intentional actor restart") { }
268+
}
269+
259270
/// <summary>
260271
/// This exception is thrown when a <see cref="Kill"/> message has been sent to an Actor.
261272
/// <see cref="SupervisorStrategy.DefaultDecider"/> will by default stop the actor.

src/core/Akka/Actor/IAutoReceivedMessage.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,21 @@ public override string ToString()
213213
}
214214
}
215215

216+
/// <summary>
217+
/// Sending a <see cref="IntentionalRestart"/> message will force it to throw a <see cref="IntentionalActorRestartException"/>
218+
/// when it processes the message.
219+
/// </summary>
220+
public sealed class IntentionalRestart : IAutoReceivedMessage
221+
{
222+
private IntentionalRestart() { }
223+
public static IntentionalRestart Instance { get; } = new();
224+
225+
public override string ToString()
226+
{
227+
return "<Restart>";
228+
}
229+
}
230+
216231
/// <summary>
217232
/// Sending an <see cref="Kill"/> message to an actor causes the actor to throw an
218233
/// <see cref="ActorKilledException"/> when it processes the message, which gets handled using the normal supervisor mechanism.

0 commit comments

Comments
 (0)