diff --git a/src/Testing/CoreTests/Acceptance/remote_invocation.cs b/src/Testing/CoreTests/Acceptance/remote_invocation.cs index 564940086..9d4f930ae 100644 --- a/src/Testing/CoreTests/Acceptance/remote_invocation.cs +++ b/src/Testing/CoreTests/Acceptance/remote_invocation.cs @@ -63,7 +63,8 @@ public async Task InitializeAsync() opts.PublishMessage().ToPort(_receiver1Port); opts.PublishMessage().ToPort(_receiver2Port); - + opts.PublishMessage().ToPort(_receiver1Port); + opts.EnableAutomaticFailureAcks = true; }).StartAsync(); } @@ -400,6 +401,27 @@ public async Task sad_path_send_and_wait_with_no_subscription() await Should.ThrowAsync(() => publisher.InvokeAsync(new RequestWithNoHandler())); } + + [Fact] + public async Task always_publish_response_should_also_publish_on_remote_request_reply() + { + AlwaysPublishResponseReceivedHandler.Received = new TaskCompletionSource(); + + var (session, response) = await _sender.TrackActivity() + .AlsoTrack(_receiver1) + .Timeout(10.Seconds()) + .WaitForMessageToBeReceivedAt(_receiver1) + .InvokeAndWaitAsync(new AlwaysPublishRequest { Name = "test" }); + + // The response should have been returned to the sender via request/reply + response.ShouldNotBeNull(); + response.Name.ShouldBe("test"); + + // The response should ALSO have been published as a cascading message + // and handled by AlwaysPublishResponseReceivedHandler on the receiver + var handled = await AlwaysPublishResponseReceivedHandler.Received.Task.WaitAsync(10.Seconds()); + handled.ShouldBeTrue(); + } } public class Request1 @@ -434,6 +456,35 @@ public class Response3 public string Name { get; set; } } +public class AlwaysPublishRequest +{ + public string Name { get; set; } +} + +public class AlwaysPublishResponse +{ + public string Name { get; set; } +} + +public static class AlwaysPublishResponseReceivedHandler +{ + public static TaskCompletionSource Received { get; set; } = new(); + + public static void Handle(AlwaysPublishResponse response) + { + Received.TrySetResult(true); + } +} + +public static class AlwaysPublishRequestHandler +{ + [AlwaysPublishResponse] + public static AlwaysPublishResponse Handle(AlwaysPublishRequest request) + { + return new AlwaysPublishResponse { Name = request.Name }; + } +} + public class RequestHandler { [MessageTimeout(3)] diff --git a/src/Wolverine/Attributes/AlwaysPublishResponseAttribute.cs b/src/Wolverine/Attributes/AlwaysPublishResponseAttribute.cs index 26d949a56..edd13ddc3 100644 --- a/src/Wolverine/Attributes/AlwaysPublishResponseAttribute.cs +++ b/src/Wolverine/Attributes/AlwaysPublishResponseAttribute.cs @@ -26,6 +26,7 @@ public override void GenerateCode(GeneratedMethod method, ISourceWriter writer) { writer.WriteComment("Always publish the response as a message due to the [AlwaysPublishResponse] usage"); writer.WriteLine($"{_envelope.Usage}.{nameof(Envelope.DoNotCascadeResponse)} = false;"); + writer.WriteLine($"{_envelope.Usage}.{nameof(Envelope.AlwaysPublishResponse)} = true;"); Next?.GenerateCode(method, writer); } diff --git a/src/Wolverine/Envelope.Internals.cs b/src/Wolverine/Envelope.Internals.cs index 33b90420a..099e090ef 100644 --- a/src/Wolverine/Envelope.Internals.cs +++ b/src/Wolverine/Envelope.Internals.cs @@ -79,6 +79,15 @@ internal Envelope(object message, IMessageSerializer writer) [JsonIgnore] public bool DoNotCascadeResponse { get; set; } + /// + /// Used by the [AlwaysPublishResponse] attribute to explicitly signal + /// that the response should be published as a cascading message in addition + /// to being sent back via request/reply. This flag works for both in-process + /// and remote request/reply scenarios. + /// + [JsonIgnore] + public bool AlwaysPublishResponse { get; set; } + /// /// Status according to the message persistence /// diff --git a/src/Wolverine/Runtime/MessageContext.cs b/src/Wolverine/Runtime/MessageContext.cs index b1dc16316..0b570db1e 100644 --- a/src/Wolverine/Runtime/MessageContext.cs +++ b/src/Wolverine/Runtime/MessageContext.cs @@ -626,6 +626,13 @@ public async Task EnqueueCascadingAsync(object? message) if (Envelope?.ReplyUri != null && message.GetType().ToMessageTypeName() == Envelope.ReplyRequested) { await EndpointFor(Envelope.ReplyUri!).SendAsync(message, new DeliveryOptions { IsResponse = true }); + + // If [AlwaysPublishResponse] was used, also publish as a cascading message + if (Envelope.AlwaysPublishResponse) + { + await PublishAsync(message); + } + return; }