Skip to content

Commit 0cf567a

Browse files
Fixed: IWrappedMessage + IDeadLetterSuppression handling (#7414)
* WIP dead letter suppression testing * added `WrappedMessage.IsDeadLetterSuppressedAnywhere` * cleaned up message-handling for `DeadLetterActorRef` * added checks for wrapped messages in DeadLetter handling * fixed issue with recursive dead letter detection * added test cases for `ActorSelection` also * checking in `SuppressedDeadLetter` API changes
1 parent 96645a5 commit 0cf567a

8 files changed

+136
-24
lines changed

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -3686,7 +3686,7 @@ namespace Akka.Event
36863686
}
36873687
public sealed class SuppressedDeadLetter : Akka.Event.AllDeadLetters
36883688
{
3689-
public SuppressedDeadLetter(Akka.Event.IDeadLetterSuppression message, Akka.Actor.IActorRef sender, Akka.Actor.IActorRef recipient) { }
3689+
public SuppressedDeadLetter(object message, Akka.Actor.IActorRef sender, Akka.Actor.IActorRef recipient) { }
36903690
}
36913691
public class TraceLogger : Akka.Actor.UntypedActor
36923692
{

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -3676,7 +3676,7 @@ namespace Akka.Event
36763676
}
36773677
public sealed class SuppressedDeadLetter : Akka.Event.AllDeadLetters
36783678
{
3679-
public SuppressedDeadLetter(Akka.Event.IDeadLetterSuppression message, Akka.Actor.IActorRef sender, Akka.Actor.IActorRef recipient) { }
3679+
public SuppressedDeadLetter(object message, Akka.Actor.IActorRef sender, Akka.Actor.IActorRef recipient) { }
36803680
}
36813681
public class TraceLogger : Akka.Actor.UntypedActor
36823682
{

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

+44-3
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313

1414
namespace Akka.Tests
1515
{
16-
1716
public class DeadLettersSpec : AkkaSpec
1817
{
1918
[Fact]
@@ -23,6 +22,48 @@ public async Task Can_send_messages_to_dead_letters()
2322
Sys.DeadLetters.Tell("foobar");
2423
await ExpectMsgAsync<DeadLetter>(deadLetter=>deadLetter.Message.Equals("foobar"));
2524
}
26-
}
27-
}
2825

26+
private sealed record WrappedClass(object Message) : IWrappedMessage;
27+
28+
private sealed class SuppressedMessage : IDeadLetterSuppression
29+
{
30+
31+
}
32+
33+
[Fact]
34+
public async Task ShouldLogNormalWrappedMessages()
35+
{
36+
Sys.EventStream.Subscribe(TestActor, typeof(DeadLetter));
37+
Sys.DeadLetters.Tell(new WrappedClass("chocolate-beans"));
38+
await ExpectMsgAsync<DeadLetter>();
39+
}
40+
41+
[Fact]
42+
public async Task ShouldNotLogWrappedMessagesWithDeadLetterSuppression()
43+
{
44+
Sys.EventStream.Subscribe(TestActor, typeof(AllDeadLetters));
45+
Sys.DeadLetters.Tell(new WrappedClass(new SuppressedMessage()));
46+
var msg = await ExpectMsgAsync<SuppressedDeadLetter>();
47+
msg.Message.ToString()!.Contains("SuppressedMessage").ShouldBeTrue();
48+
}
49+
50+
[Fact]
51+
public async Task ShouldLogNormalActorSelectionWrappedMessages()
52+
{
53+
Sys.EventStream.Subscribe(TestActor, typeof(DeadLetter));
54+
var selection = Sys.ActorSelection("/user/foobar");
55+
selection.Tell(new WrappedClass("chocolate-beans"));
56+
await ExpectMsgAsync<DeadLetter>();
57+
}
58+
59+
[Fact]
60+
public async Task ShouldNotLogActorSelectionWrappedMessagesWithDeadLetterSuppression()
61+
{
62+
Sys.EventStream.Subscribe(TestActor, typeof(AllDeadLetters));
63+
var selection = Sys.ActorSelection("/user/foobar");
64+
selection.Tell(new WrappedClass(new SuppressedMessage()));
65+
var msg = await ExpectMsgAsync<SuppressedDeadLetter>();
66+
msg.Message.ToString()!.Contains("SuppressedMessage").ShouldBeTrue();
67+
}
68+
}
69+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// -----------------------------------------------------------------------
2+
// <copyright file="WrappedMessagesSpec.cs" company="Akka.NET Project">
3+
// Copyright (C) 2009-2024 Lightbend Inc. <http://www.lightbend.com>
4+
// Copyright (C) 2013-2024 .NET Foundation <https://github.com/akkadotnet/akka.net>
5+
// </copyright>
6+
// -----------------------------------------------------------------------
7+
8+
using Akka.Actor;
9+
using Akka.Event;
10+
using Akka.TestKit;
11+
using Xunit;
12+
13+
namespace Akka.Tests;
14+
15+
public class WrappedMessagesSpec
16+
{
17+
private sealed record WrappedClass(object Message) : IWrappedMessage;
18+
19+
private sealed record WrappedSuppressedClass(object Message) : IWrappedMessage, IDeadLetterSuppression;
20+
21+
private sealed class SuppressedMessage : IDeadLetterSuppression
22+
{
23+
24+
}
25+
26+
27+
[Fact]
28+
public void ShouldUnwrapWrappedMessage()
29+
{
30+
var message = new WrappedClass("chocolate-beans");
31+
var unwrapped = WrappedMessage.Unwrap(message);
32+
unwrapped.ShouldBe("chocolate-beans");
33+
}
34+
35+
public static readonly TheoryData<object, bool> SuppressedMessages = new()
36+
{
37+
{new SuppressedMessage(), true},
38+
{new WrappedClass(new SuppressedMessage()), true},
39+
{new WrappedClass(new WrappedClass(new SuppressedMessage())), true},
40+
{new WrappedClass(new WrappedClass("chocolate-beans")), false},
41+
{new WrappedSuppressedClass("foo"), true},
42+
{new WrappedClass(new WrappedSuppressedClass("chocolate-beans")), true},
43+
{new WrappedClass("chocolate-beans"), false},
44+
{"chocolate-beans", false}
45+
};
46+
47+
[Theory]
48+
[MemberData(nameof(SuppressedMessages))]
49+
public void ShouldDetectIfWrappedMessageIsSuppressed(object message, bool shouldBeSuppressed)
50+
{
51+
var isSuppressed = WrappedMessage.IsDeadLetterSuppressedAnywhere(message);
52+
isSuppressed.ShouldBe(shouldBeSuppressed);
53+
}
54+
}

src/core/Akka/Actor/BuiltInActors.cs

+24-11
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,18 @@ public static object Unwrap(object message)
197197
}
198198
return message;
199199
}
200+
201+
internal static bool IsDeadLetterSuppressedAnywhere(object message)
202+
{
203+
var isSuppressed = message is IDeadLetterSuppression;
204+
while(!isSuppressed && message is IWrappedMessage wm)
205+
{
206+
message = wm.Message;
207+
isSuppressed = message is IDeadLetterSuppression;
208+
}
209+
210+
return isSuppressed;
211+
}
200212
}
201213

202214
/// <summary>
@@ -226,19 +238,20 @@ public DeadLetterActorRef(IActorRefProvider provider, ActorPath path, EventStrea
226238
/// <exception cref="InvalidMessageException">This exception is thrown if the given <paramref name="message"/> is undefined.</exception>
227239
protected override void TellInternal(object message, IActorRef sender)
228240
{
229-
if (message == null) throw new InvalidMessageException("Message is null");
230-
var i = message as Identify;
231-
if (i != null)
241+
switch (message)
232242
{
233-
sender.Tell(new ActorIdentity(i.MessageId, ActorRefs.Nobody));
234-
return;
235-
}
236-
var d = message as DeadLetter;
237-
if (d != null)
238-
{
239-
if (!SpecialHandle(d.Message, d.Sender)) { _eventStream.Publish(d); }
240-
return;
243+
case null:
244+
throw new InvalidMessageException("Message is null");
245+
case Identify i:
246+
sender.Tell(new ActorIdentity(i.MessageId, ActorRefs.Nobody));
247+
return;
248+
case DeadLetter d:
249+
{
250+
if (!SpecialHandle(d.Message, d.Sender)) { _eventStream.Publish(d); }
251+
return;
252+
}
241253
}
254+
242255
if (!SpecialHandle(message, sender)) { _eventStream.Publish(new DeadLetter(message, sender.IsNobody() ? Provider.DeadLetters : sender, this)); }
243256
}
244257

src/core/Akka/Actor/DeadLetterMailbox.cs

+6-2
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,13 @@ public DeadLetterMessageQueue(IActorRef deadLetters)
4646
/// <param name="envelope">TBD</param>
4747
public void Enqueue(IActorRef receiver, Envelope envelope)
4848
{
49-
if (envelope.Message is DeadLetter)
49+
if (envelope.Message is AllDeadLetters)
5050
{
51-
// actor subscribing to DeadLetter. Drop it.
51+
/* We're receiving a DeadLetter sent to us by someone else (which is not normal - usually only happens
52+
* if we were explicitly subscribed to DeadLetters on the EventStream).
53+
*
54+
* Have to terminate here in order to prevent a stack overflow.
55+
*/
5256
return;
5357
}
5458

src/core/Akka/Actor/EmptyLocalActorRef.cs

+5-5
Original file line numberDiff line numberDiff line change
@@ -111,9 +111,9 @@ protected virtual bool SpecialHandle(object message, IActorRef sender)
111111
}
112112
else
113113
{
114-
if (actorSelectionMessage.Message is IDeadLetterSuppression selectionDeadLetterSuppression)
114+
if (WrappedMessage.IsDeadLetterSuppressedAnywhere(actorSelectionMessage.Message))
115115
{
116-
PublishSupressedDeadLetter(selectionDeadLetterSuppression, sender);
116+
PublishSupressedDeadLetter(actorSelectionMessage.Message, sender);
117117
}
118118
else
119119
{
@@ -123,16 +123,16 @@ protected virtual bool SpecialHandle(object message, IActorRef sender)
123123
return true;
124124
}
125125

126-
if (message is IDeadLetterSuppression deadLetterSuppression)
126+
if (WrappedMessage.IsDeadLetterSuppressedAnywhere(message))
127127
{
128-
PublishSupressedDeadLetter(deadLetterSuppression, sender);
128+
PublishSupressedDeadLetter(message, sender);
129129
return true;
130130
}
131131

132132
return false;
133133
}
134134

135-
private void PublishSupressedDeadLetter(IDeadLetterSuppression msg, IActorRef sender)
135+
private void PublishSupressedDeadLetter(object msg, IActorRef sender)
136136
{
137137
_eventStream.Publish(new SuppressedDeadLetter(msg, sender.IsNobody() ? _provider.DeadLetters : sender, this));
138138
}

src/core/Akka/Event/DeadLetter.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ public sealed class SuppressedDeadLetter : AllDeadLetters
104104
/// <exception cref="ArgumentNullException">
105105
/// This exception is thrown when either the sender or the recipient is undefined.
106106
/// </exception>
107-
public SuppressedDeadLetter(IDeadLetterSuppression message, IActorRef sender, IActorRef recipient) : base(message, sender, recipient)
107+
public SuppressedDeadLetter(object message, IActorRef sender, IActorRef recipient) : base(message, sender, recipient)
108108
{
109109
if (sender == null) throw new ArgumentNullException(nameof(sender), "SuppressedDeadLetter sender may not be null");
110110
if (recipient == null) throw new ArgumentNullException(nameof(recipient), "SuppressedDeadLetter recipient may not be null");

0 commit comments

Comments
 (0)