From 0c9677f700b6115090d3a277d28f310046e0235f Mon Sep 17 00:00:00 2001 From: Gregorius Soedharmo Date: Wed, 29 Jun 2022 02:17:33 +0700 Subject: [PATCH] Add ValueTask support to PipeTo (#6025) * Add ValueTask support to PipeTo * Update API Verify list * Exclude NETFX from Akka.Tests linux build --- .../CoreAPISpec.ApproveCore.Core.verified.txt | 4 ++ ...oreAPISpec.ApproveCore.DotNet.verified.txt | 4 ++ .../CoreAPISpec.ApproveCore.Net.verified.txt | 4 ++ .../Akka.Tests/Actor/PipeToSupportSpec.cs | 63 +++++++++++++++++ src/core/Akka.Tests/Akka.Tests.csproj | 3 +- src/core/Akka/Actor/PipeToSupport.cs | 69 +++++++++++++++++++ 6 files changed, 146 insertions(+), 1 deletion(-) diff --git a/src/core/Akka.API.Tests/verify/CoreAPISpec.ApproveCore.Core.verified.txt b/src/core/Akka.API.Tests/verify/CoreAPISpec.ApproveCore.Core.verified.txt index d0dc80d309a..9def3f96825 100644 --- a/src/core/Akka.API.Tests/verify/CoreAPISpec.ApproveCore.Core.verified.txt +++ b/src/core/Akka.API.Tests/verify/CoreAPISpec.ApproveCore.Core.verified.txt @@ -1387,9 +1387,13 @@ namespace Akka.Actor public class static PipeToSupport { public static System.Threading.Tasks.Task PipeTo(this System.Threading.Tasks.Task taskToPipe, Akka.Actor.ICanTell recipient, Akka.Actor.IActorRef sender = null, System.Func success = null, System.Func failure = null) { } + public static System.Threading.Tasks.Task PipeTo(this System.Threading.Tasks.ValueTask taskToPipe, Akka.Actor.ICanTell recipient, Akka.Actor.IActorRef sender = null, System.Func success = null, System.Func failure = null) { } public static System.Threading.Tasks.Task PipeTo(this System.Threading.Tasks.Task taskToPipe, Akka.Actor.ICanTell recipient, bool useConfigureAwait, Akka.Actor.IActorRef sender = null, System.Func success = null, System.Func failure = null) { } + public static System.Threading.Tasks.Task PipeTo(this System.Threading.Tasks.ValueTask taskToPipe, Akka.Actor.ICanTell recipient, bool useConfigureAwait, Akka.Actor.IActorRef sender = null, System.Func success = null, System.Func failure = null) { } public static System.Threading.Tasks.Task PipeTo(this System.Threading.Tasks.Task taskToPipe, Akka.Actor.ICanTell recipient, Akka.Actor.IActorRef sender = null, System.Func success = null, System.Func failure = null) { } + public static System.Threading.Tasks.Task PipeTo(this System.Threading.Tasks.ValueTask taskToPipe, Akka.Actor.ICanTell recipient, Akka.Actor.IActorRef sender = null, System.Func success = null, System.Func failure = null) { } public static System.Threading.Tasks.Task PipeTo(this System.Threading.Tasks.Task taskToPipe, Akka.Actor.ICanTell recipient, bool useConfigureAwait, Akka.Actor.IActorRef sender = null, System.Func success = null, System.Func failure = null) { } + public static System.Threading.Tasks.Task PipeTo(this System.Threading.Tasks.ValueTask taskToPipe, Akka.Actor.ICanTell recipient, bool useConfigureAwait, Akka.Actor.IActorRef sender = null, System.Func success = null, System.Func failure = null) { } } public sealed class PoisonPill : Akka.Actor.IAutoReceivedMessage, Akka.Actor.IPossiblyHarmful, Akka.Event.IDeadLetterSuppression { 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 b0585057327..f48e2b499ed 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 @@ -1389,9 +1389,13 @@ namespace Akka.Actor public class static PipeToSupport { public static System.Threading.Tasks.Task PipeTo(this System.Threading.Tasks.Task taskToPipe, Akka.Actor.ICanTell recipient, Akka.Actor.IActorRef sender = null, System.Func success = null, System.Func failure = null) { } + public static System.Threading.Tasks.Task PipeTo(this System.Threading.Tasks.ValueTask taskToPipe, Akka.Actor.ICanTell recipient, Akka.Actor.IActorRef sender = null, System.Func success = null, System.Func failure = null) { } public static System.Threading.Tasks.Task PipeTo(this System.Threading.Tasks.Task taskToPipe, Akka.Actor.ICanTell recipient, bool useConfigureAwait, Akka.Actor.IActorRef sender = null, System.Func success = null, System.Func failure = null) { } + public static System.Threading.Tasks.Task PipeTo(this System.Threading.Tasks.ValueTask taskToPipe, Akka.Actor.ICanTell recipient, bool useConfigureAwait, Akka.Actor.IActorRef sender = null, System.Func success = null, System.Func failure = null) { } public static System.Threading.Tasks.Task PipeTo(this System.Threading.Tasks.Task taskToPipe, Akka.Actor.ICanTell recipient, Akka.Actor.IActorRef sender = null, System.Func success = null, System.Func failure = null) { } + public static System.Threading.Tasks.Task PipeTo(this System.Threading.Tasks.ValueTask taskToPipe, Akka.Actor.ICanTell recipient, Akka.Actor.IActorRef sender = null, System.Func success = null, System.Func failure = null) { } public static System.Threading.Tasks.Task PipeTo(this System.Threading.Tasks.Task taskToPipe, Akka.Actor.ICanTell recipient, bool useConfigureAwait, Akka.Actor.IActorRef sender = null, System.Func success = null, System.Func failure = null) { } + public static System.Threading.Tasks.Task PipeTo(this System.Threading.Tasks.ValueTask taskToPipe, Akka.Actor.ICanTell recipient, bool useConfigureAwait, Akka.Actor.IActorRef sender = null, System.Func success = null, System.Func failure = null) { } } public sealed class PoisonPill : Akka.Actor.IAutoReceivedMessage, Akka.Actor.IPossiblyHarmful, Akka.Event.IDeadLetterSuppression { 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 d0dc80d309a..9def3f96825 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 @@ -1387,9 +1387,13 @@ namespace Akka.Actor public class static PipeToSupport { public static System.Threading.Tasks.Task PipeTo(this System.Threading.Tasks.Task taskToPipe, Akka.Actor.ICanTell recipient, Akka.Actor.IActorRef sender = null, System.Func success = null, System.Func failure = null) { } + public static System.Threading.Tasks.Task PipeTo(this System.Threading.Tasks.ValueTask taskToPipe, Akka.Actor.ICanTell recipient, Akka.Actor.IActorRef sender = null, System.Func success = null, System.Func failure = null) { } public static System.Threading.Tasks.Task PipeTo(this System.Threading.Tasks.Task taskToPipe, Akka.Actor.ICanTell recipient, bool useConfigureAwait, Akka.Actor.IActorRef sender = null, System.Func success = null, System.Func failure = null) { } + public static System.Threading.Tasks.Task PipeTo(this System.Threading.Tasks.ValueTask taskToPipe, Akka.Actor.ICanTell recipient, bool useConfigureAwait, Akka.Actor.IActorRef sender = null, System.Func success = null, System.Func failure = null) { } public static System.Threading.Tasks.Task PipeTo(this System.Threading.Tasks.Task taskToPipe, Akka.Actor.ICanTell recipient, Akka.Actor.IActorRef sender = null, System.Func success = null, System.Func failure = null) { } + public static System.Threading.Tasks.Task PipeTo(this System.Threading.Tasks.ValueTask taskToPipe, Akka.Actor.ICanTell recipient, Akka.Actor.IActorRef sender = null, System.Func success = null, System.Func failure = null) { } public static System.Threading.Tasks.Task PipeTo(this System.Threading.Tasks.Task taskToPipe, Akka.Actor.ICanTell recipient, bool useConfigureAwait, Akka.Actor.IActorRef sender = null, System.Func success = null, System.Func failure = null) { } + public static System.Threading.Tasks.Task PipeTo(this System.Threading.Tasks.ValueTask taskToPipe, Akka.Actor.ICanTell recipient, bool useConfigureAwait, Akka.Actor.IActorRef sender = null, System.Func success = null, System.Func failure = null) { } } public sealed class PoisonPill : Akka.Actor.IAutoReceivedMessage, Akka.Actor.IPossiblyHarmful, Akka.Event.IDeadLetterSuppression { diff --git a/src/core/Akka.Tests/Actor/PipeToSupportSpec.cs b/src/core/Akka.Tests/Actor/PipeToSupportSpec.cs index f9e469c5f01..7ca5f466f1b 100644 --- a/src/core/Akka.Tests/Actor/PipeToSupportSpec.cs +++ b/src/core/Akka.Tests/Actor/PipeToSupportSpec.cs @@ -8,6 +8,7 @@ using System; using System.Linq; using System.Threading.Tasks; +using System.Threading.Tasks.Sources; using Akka.Actor; using Akka.Event; using Akka.TestKit; @@ -22,11 +23,18 @@ public class PipeToSupportSpec : AkkaSpec private readonly Task _task; private readonly Task _taskWithoutResult; + private readonly ValueTask _valueTask; + private readonly ValueTask _valueTaskWithoutResult; + public PipeToSupportSpec() { _taskCompletionSource = new TaskCompletionSource(); _task = _taskCompletionSource.Task; _taskWithoutResult = _taskCompletionSource.Task; + + _valueTask = new ValueTask(_taskCompletionSource.Task); + _valueTaskWithoutResult = new ValueTask(_taskCompletionSource.Task); + Sys.EventStream.Subscribe(TestActor, typeof(DeadLetter)); } @@ -38,6 +46,14 @@ public async Task Should_immediately_PipeTo_completed_Task() await ExpectMsgAsync("foo"); } + [Fact] + public async Task ValueTask_Should_immediately_PipeTo_completed_Task() + { + var task = new ValueTask("foo"); + task.PipeTo(TestActor); + await ExpectMsgAsync("foo"); + } + [Fact] public async Task Should_by_default_send_task_result_as_message() { @@ -46,6 +62,14 @@ public async Task Should_by_default_send_task_result_as_message() await ExpectMsgAsync("Hello"); } + [Fact] + public async Task ValueTask_Should_by_default_send_task_result_as_message() + { + _valueTask.PipeTo(TestActor); + _taskCompletionSource.SetResult("Hello"); + await ExpectMsgAsync("Hello"); + } + [Fact] public async Task Should_by_default_not_send_a_success_message_if_the_task_does_not_produce_a_result() { @@ -54,6 +78,14 @@ public async Task Should_by_default_not_send_a_success_message_if_the_task_does_ await ExpectNoMsgAsync(TimeSpan.FromMilliseconds(100)); } + [Fact] + public async Task ValueTask_Should_by_default_not_send_a_success_message_if_the_task_does_not_produce_a_result() + { + _valueTaskWithoutResult.PipeTo(TestActor); + _taskCompletionSource.SetResult("Hello"); + await ExpectNoMsgAsync(TimeSpan.FromMilliseconds(100)); + } + [Fact] public async Task Should_by_default_send_task_exception_as_status_failure_message() { @@ -64,6 +96,16 @@ public async Task Should_by_default_send_task_exception_as_status_failure_messag await ExpectMsgAsync(x => x.Cause.Message == "Boom"); } + [Fact] + public async Task ValueTask_Should_by_default_send_task_exception_as_status_failure_message() + { + _valueTask.PipeTo(TestActor); + _valueTaskWithoutResult.PipeTo(TestActor); + _taskCompletionSource.SetException(new Exception("Boom")); + await ExpectMsgAsync(x => x.Cause.Message == "Boom"); + await ExpectMsgAsync(x => x.Cause.Message == "Boom"); + } + [Fact] public async Task Should_use_success_handling_to_transform_task_result() { @@ -75,6 +117,17 @@ public async Task Should_use_success_handling_to_transform_task_result() pipeTo.Should().Contain("Hello World"); } + [Fact] + public async Task ValueTask_Should_use_success_handling_to_transform_task_result() + { + _valueTask.PipeTo(TestActor, success: x => "Hello " + x); + _valueTaskWithoutResult.PipeTo(TestActor, success: () => "Hello"); + _taskCompletionSource.SetResult("World"); + var pipeTo = await ReceiveNAsync(2, default).Cast().ToListAsync(); + pipeTo.Should().Contain("Hello"); + pipeTo.Should().Contain("Hello World"); + } + [Fact] public async Task Should_use_failure_handling_to_transform_task_exception() { @@ -84,5 +137,15 @@ public async Task Should_use_failure_handling_to_transform_task_exception() await ExpectMsgAsync("Such a failure..."); await ExpectMsgAsync("Such a failure..."); } + + [Fact] + public async Task ValueTask_Should_use_failure_handling_to_transform_task_exception() + { + _valueTask.PipeTo(TestActor, failure: e => "Such a " + e.Message); + _valueTaskWithoutResult.PipeTo(TestActor, failure: e => "Such a " + e.Message); + _taskCompletionSource.SetException(new Exception("failure...")); + await ExpectMsgAsync("Such a failure..."); + await ExpectMsgAsync("Such a failure..."); + } } } diff --git a/src/core/Akka.Tests/Akka.Tests.csproj b/src/core/Akka.Tests/Akka.Tests.csproj index 92c0382e71c..80a9b5c354d 100644 --- a/src/core/Akka.Tests/Akka.Tests.csproj +++ b/src/core/Akka.Tests/Akka.Tests.csproj @@ -4,7 +4,8 @@ Akka.Tests - $(NetFrameworkTestVersion);$(NetTestVersion);$(NetCoreTestVersion) + $(NetFrameworkTestVersion);$(NetTestVersion);$(NetCoreTestVersion) + $(NetTestVersion);$(NetCoreTestVersion) diff --git a/src/core/Akka/Actor/PipeToSupport.cs b/src/core/Akka/Actor/PipeToSupport.cs index 474ce5945e7..66c31f27877 100644 --- a/src/core/Akka/Actor/PipeToSupport.cs +++ b/src/core/Akka/Actor/PipeToSupport.cs @@ -36,6 +36,14 @@ public static class PipeToSupport Func failure = null) => PipeTo(taskToPipe, recipient, false, sender, success, failure); + public static Task PipeTo( + this ValueTask taskToPipe, + ICanTell recipient, + IActorRef sender = null, + Func success = null, + Func failure = null) + => PipeTo(taskToPipe, recipient, false, sender, success, failure); + /// /// Pipes the output of a Task directly to the 's mailbox once /// the task completes @@ -74,6 +82,32 @@ public static class PipeToSupport } } + public static async Task PipeTo( + this ValueTask taskToPipe, + ICanTell recipient, + bool useConfigureAwait, + IActorRef sender = null, + Func success = null, + Func failure = null) + { + sender ??= ActorRefs.NoSender; + + try + { + var result = await taskToPipe.ConfigureAwait(useConfigureAwait); + + recipient.Tell(success != null + ? success(result) + : result, sender); + } + catch (Exception ex) + { + recipient.Tell(failure != null + ? failure(ex) + : new Status.Failure(ex), sender); + } + } + /// /// Pipes the output of a Task directly to the 's mailbox once /// the task completes. As this task has no result, only exceptions will be piped to the @@ -92,6 +126,14 @@ public static class PipeToSupport Func failure = null) => PipeTo(taskToPipe, recipient, false, sender, success, failure); + public static Task PipeTo( + this ValueTask taskToPipe, + ICanTell recipient, + IActorRef sender = null, + Func success = null, + Func failure = null) + => PipeTo(taskToPipe, recipient, false, sender, success, failure); + /// /// Pipes the output of a Task directly to the 's mailbox once /// the task completes. As this task has no result, only exceptions will be piped to the @@ -129,6 +171,33 @@ public static class PipeToSupport : new Status.Failure(ex), sender); } } + + public static async Task PipeTo( + this ValueTask taskToPipe, + ICanTell recipient, + bool useConfigureAwait, + IActorRef sender = null, + Func success = null, + Func failure = null) + { + sender = sender ?? ActorRefs.NoSender; + + try + { + await taskToPipe.ConfigureAwait(useConfigureAwait); + + if (success != null) + { + recipient.Tell(success(), sender); + } + } + catch (Exception ex) + { + recipient.Tell(failure != null + ? failure(ex) + : new Status.Failure(ex), sender); + } + } } }