-
Notifications
You must be signed in to change notification settings - Fork 1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Ask interface should be clean #3220
Conversation
var actor = Sys.ActorOf<SomeActor>(); | ||
var cts = new CancellationTokenSource(TimeSpan.FromSeconds(3)); | ||
Assert.Throws<AggregateException>(() => { actor.Ask<string>("timeout", Timeout.InfiniteTimeSpan, cts.Token).Wait(); }); | ||
Assert.True(cts.IsCancellationRequested); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No need to test CancellationToken source here - it working just fine.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agree
Assert.True(cts.IsCancellationRequested); | ||
using (var cts = new CancellationTokenSource(TimeSpan.FromSeconds(3))) | ||
{ | ||
await Assert.ThrowsAsync<TaskCanceledException>(async () => await actor.Ask<string>("timeout", Timeout.InfiniteTimeSpan, cts.Token)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No mess with the AggregateExceptions and unwrapping... clear well defined expectation!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good
@maxcherednik we don't use async tests methods, because they introduced races when the test suite has been run in parallel mode. |
{ | ||
await Assert.ThrowsAsync<TaskCanceledException>(async () => await actor.Ask<string>("cancel", TimeSpan.FromSeconds(30), cts.Token)); | ||
} | ||
|
||
Are_Temp_Actors_Removed(actor); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Aaronontheweb @Horusiath What is this thing doing here?
We kinda testing Ask here. As far as I can see it should not affect an actor it's used upon.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Internally in order to work Ask
makes a lightweight temporary actor reference. One of the bugs in the past was that those temporary actors where not recycled after Ask
task completed, causing memory leaks.
@Horusiath hm... and the reason is ? Cause semantically they are the same. |
@maxcherednik the problem is not in semantics, but in internals of xunit test runner, which works (or at least it worked in the past) in mysterious ways and did weird things with scheduling tests to be executed. While you won't feel that in simple unit tests, if you do integration tests (which are most of the Akka.NET test suite) it resulted in false negatives, since often assertions were running into timeouts. |
@Horusiath probably long in the past. We use them all the time all over. Never had any issues of such nature. I thought it's related to Akka.net implementation or something. So here specifically can we proceed with the modern way? |
Looks like |
@Aaronontheweb I know, that's the purpose. The problem here is that we were never throwing the AskTimeoutException. Instead we were throwing the TaskCancelledException, which is not really clear. |
{ | ||
var actor = Sys.ActorOf<SomeActor>(); | ||
Assert.Throws<AggregateException>(() => { actor.Ask<string>("timeout", TimeSpan.FromSeconds(3)).Wait(); }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here because we didn't use the async await syntax - we started to catch AggregateException and never actually checked the underneath type. And this probably caused us picking the wrong type of exception to throw(TaskCancelledException instead of AskTimeoutException)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Aaronontheweb Look at the code above. It's not actually "to be modern", it's just to help not to hide things.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see
@Aaronontheweb @Horusiath So if we agree on the interface of the Ask method, I will satisfy the failing test by implementing the right way. Shall I? |
I agree with:
Don't agree with
Doesn't matter if we block in the test suites or not. As @Horusiath mentioned, we had historical reasons for not doing this in the past. There's no benefit to changing it for the sake of "making it modern." Unless there's a compelling benefit to change something, don't do it. |
Go forward with your changes @maxcherednik - makes sense. |
{ | ||
IActorRefProvider provider = ResolveProvider(self); | ||
if (provider == null) | ||
throw new ArgumentException("Unable to resolve the target Provider", nameof(self)); | ||
|
||
return AskEx(self, messageFactory, provider, timeout, cancellationToken).CastTask<object, T>(); | ||
return (T)await AskEx(self, messageFactory, provider, timeout, cancellationToken); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We don't need this CastTask at all. All we need to do here is to take await and cast the value from object to T.
The rest will be managed by the .net itself. All the exceptions will be propagated as is.
@@ -410,49 +410,60 @@ internal static IActorRefProvider ResolveProvider(ICanTell self) | |||
return null; | |||
} | |||
|
|||
private static Task<object> AskEx(ICanTell self, Func<IActorRef, object> messageFactory, IActorRefProvider provider, TimeSpan? timeout, CancellationToken cancellationToken) | |||
private static async Task<object> AskEx(ICanTell self, Func<IActorRef, object> messageFactory, IActorRefProvider provider, TimeSpan? timeout, CancellationToken cancellationToken) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In this method I did a few changes:
- Setting the right exception in case of Timeout
- With the help of the async/await - removing one level of indirection(unregister callback is gone) - this logic happens in the try/finaly
- Right order of disposing things in the finally block
src/core/Akka.Tests/Actor/AskSpec.cs
Outdated
@@ -123,6 +123,15 @@ protected override void OnReceive(object message) | |||
Are_Temp_Actors_Removed(actor); | |||
} | |||
|
|||
[Fact] | |||
public async Task ShouldFailWhenAskExpectsWrongType() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Extra test for InvalidCastException
/// <typeparam name="TResult">TBD</typeparam> | ||
/// <param name="task">TBD</param> | ||
/// <returns>TBD</returns> | ||
public static Task<TResult> CastTask<TTask, TResult>(this Task<TTask> task) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's not use this guy anymore, but the features of the C# itself.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agree
src/core/Akka/Actor/ActorRef.cs
Outdated
@@ -76,30 +76,29 @@ public class FutureActorRef : MinimalActorRef | |||
/// TBD |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This class is simplified-the only purpose of this class now is to listen for the response message and put it into the completion source. Less callbacks, more readable code.
@Horusiath @Aaronontheweb got a q. |
In general we follow extend-only design principles, so we don't remove stuff once it's been added to the API. Not 100&% always, but usually. I'd just leave the |
@Aaronontheweb ok, got it. I don't insist, it's just redundant code with zero value. Was just wondering if there were other hidden design reasons to have it. |
Someone added it a while back and I probably should have said "no" at the time. |
@Aaronontheweb or @Horusiath could you please review the approach taken and if it's fine, I will add more documentation around Ask interface. |
If the ask method were made async, would that cause hazards for anyone currently using Ask outside of an asynchronous method? |
@to11mtm the signature of the method stays the same - that is what's important. The rest is implementation details! We will try to implement it not to break current usages. |
AskSpecs consolidated Api change approval - internal CastTask removed
@Aaronontheweb @Horusiath Could you please review the PR |
@maxcherednik sure thing Maxim; sorry for the delay. Had my hands full debugging cluster stuff over the past couple of days! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some minor changes needed. Flagged them in latest review.
var actor = Sys.ActorOf<SomeActor>(); | ||
var cts = new CancellationTokenSource(TimeSpan.FromSeconds(3)); | ||
Assert.Throws<AggregateException>(() => { actor.Ask<string>("timeout", Timeout.InfiniteTimeSpan, cts.Token).Wait(); }); | ||
Assert.True(cts.IsCancellationRequested); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agree
@@ -0,0 +1,72 @@ | |||
//----------------------------------------------------------------------- | |||
// <copyright file="ArrayExtensions.cs" company="Akka.NET Project"> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Filename is wrong @maxcherednik
return this; | ||
} | ||
|
||
public void GetResult() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there meant to be something here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nah... this method there is to make this class awaitable.
/// <typeparam name="TResult">TBD</typeparam> | ||
/// <param name="task">TBD</param> | ||
/// <returns>TBD</returns> | ||
public static Task<TResult> CastTask<TTask, TResult>(this Task<TTask> task) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agree
|
||
private async Task<object> SendMessage(object message) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like the redesign of this class. Much more understandable.
One thing I really like about this PR is the performance improvement. I want to see more data from our build server to support this, but.... BeforeTotals
Per-second Totals
AfterTotals
Per-second Totals
|
The steep drop in allocations and the improvement in throughput are correlated. Probably GC behavior. |
@Aaronontheweb Fixed the header and put a comment in the empty method. |
@Aaronontheweb up:) |
Re-running the failed specs... |
* Tests should be precise - in temrs of what to expect * Ask interface refined #3220 * ClusterRouter unit test fix #3220 * Ask deadlock test added #3220 * Handle deadlock by removing the SynchronizationContext #3220 * Fixing ScatterGather router test #3220 * Ask interface refined #3220 AskSpecs consolidated Api change approval - internal CastTask removed * Fixing header #3220
* Tests should be precise - in temrs of what to expect * Ask interface refined #3220 * ClusterRouter unit test fix #3220 * Ask deadlock test added #3220 * Handle deadlock by removing the SynchronizationContext #3220 * Fixing ScatterGather router test #3220 * Ask interface refined #3220 AskSpecs consolidated Api change approval - internal CastTask removed * Fixing header #3220
This closes #3162, closes #3182
Tests should be precise - in terms of what to expect
Plus: we should use the power of the C# itself. Don't block on async functions - .Result and .Wait() are not cool.