Skip to content
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

Clear current message - fixes #1609 #1613

Merged
merged 1 commit into from
Jan 7, 2016
Merged

Clear current message - fixes #1609 #1613

merged 1 commit into from
Jan 7, 2016

Conversation

rogeralsing
Copy link
Contributor

Fixes #1609

cc @JeffCyr please review that the current message on async ActorTaskSchedulerMessage messages looks OK.

@@ -60,6 +60,7 @@ public void Invoke(Envelope envelope)
AutoReceiveMessage(envelope);
else
ReceiveMessage(message);
CurrentMessage = null;
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From the Scala code

final def invoke(messageHandle: Envelope): Unit = try {
    currentMessage = messageHandle
    cancelReceiveTimeout() // FIXME: leave this here???
    messageHandle.message match {
      case msg: AutoReceivedMessage  autoReceiveMessage(messageHandle)
      case msg                       receiveMessage(msg)
    }
    ///########## current message is cleared here
    currentMessage = null // reset current message after successful invocation
  } catch handleNonFatalOrInterruptedException { e 
    handleInvokeFailure(Nil, e)
  } finally {
    checkReceiveTimeout // Reschedule receive timeout
  }

@rogeralsing
Copy link
Contributor Author

Some notes.
The rationale for the implementation here is that we should stick to how Scala does this, and all the magic of async operations is dealt with externally.
We set and clear the current message on entry and exit of the ActorCell.Invoke, just like Scala.

When doing this, we also need to make sure that async operations have access to the current message in their continuations, this is solved by capturing the current user message when an async operation starts, and then pass that message back to the actor cell using the ActorTaskSchedulerMessage message.

@JeffCyr
Copy link
Contributor

JeffCyr commented Jan 5, 2016

ActorCell.CurrentMessage won't be set after the first await in the async receive. It will be restored properly before calling HandleInvokeFailure in case of an exception, but it won't be set for other usage:

AbstractStash.Stash
DefaultResizer.Pressure
PersistentView.ReplayStarted

Stash won't work after the first await, not sure how it impacts the other usages.

This could be fixed by creating an ActorTaskScheduler instance per async receive, the current message would be kept in the instance so it can be restored on each ActorTaskSchedulerMessage handling.

An instance per message is not that bad for async receive, the throughput of async receive should not be high anyway. However this issue may impact the performance of this approach.

An alternative would be to use a custom ActorSynchronizationContext instead of an ActorTaskScheduler.

@rogeralsing
Copy link
Contributor Author

ActorCell.CurrentMessage won't be set after the first await in the async receive.

I have updated the code to restore the current message on all ActorTaskSchedulerMessage messages.

Do note that there should never be any new or changed current messages while doing an async operation as the mailbox is suspended for user messages.
So IMO, we should be able to just capture the current user message when an async op starts. as it cannot change during that async flow.

@JeffCyr
Copy link
Contributor

JeffCyr commented Jan 5, 2016

Looks good!

{
_actorCell.Self.Tell(new ActorTaskSchedulerMessage(this, task), ActorRefs.NoSender);
{
_actorCell.Self.Tell(new ActorTaskSchedulerMessage(this, task, _actorCell.CurrentMessage), ActorRefs.NoSender);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm wait, I think this approach doesn't work if the task is scheduled from outside of the actor's context.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AFAIK, no one could ever use this outside of the actor context as each scheduler is bound to an individual actor and only used from within a context.
Are there any cases I am missing?
(I can not find any usage in the references that hints that would be the case)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Receive(async m =>
{
    // ...
    await Task.Delay(1000); // ActorTaskScheduler.QueueTask will be called outside the actor's context when the timer expires
    // ...
});

@JeffCyr
Copy link
Contributor

JeffCyr commented Jan 5, 2016

@rogeralsing Just saw your edit, that's right ActorTaskScheduler could capture CurrentMessage in a field as long as it is cleared when the async receive completes to not recreate the original issue.

}

//clear the current message field of the scheduler
actorScheduler.CurrentMessage = null;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clear the current message of the scheduler

@rogeralsing
Copy link
Contributor Author

cc @JeffCyr , I've updated the scheduler to keep a field for the current message.
I've also updated the tests to ensure that we do have the correct current message after awaiting a Task.Delay.

@JeffCyr
Copy link
Contributor

JeffCyr commented Jan 5, 2016

Looks good! 👍

@Aaronontheweb
Copy link
Member

Should we add a spec to ensure that the current message is cleared?

var actor = Sys.ActorOf(Props.Create(() => new DummyActor(autoResetEvent)));
actor.Tell("hello");
//ensure the message was received
autoResetEvent.WaitOne();
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wait until the actor have received our message

@rogeralsing
Copy link
Contributor Author

@Aaronontheweb I've added a spec to ensure that the current message is cleared.

re-set curret message on async callbacks

store current message in a field

clear CurrentMessage

Added clear message spec

added async await test
//wait while current message is not null (that is, receive is not yet completed/exited)
SpinWait.SpinUntil(() => cell.CurrentMessage == null, TimeSpan.FromSeconds(2));

cell.CurrentMessage.ShouldBe(null);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wait for current message to become null, or timeout
Then assert that current message is null

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can use AwaitAssert for this in the future, but this change is fine

@Aaronontheweb
Copy link
Member

Looks like #1608 dirtied the working directory and failed this spec, which is bad. This is an issue with how I've configured TeamCity, not anything to do with our code. I'll get to work on fixing this.

@Aaronontheweb
Copy link
Member

All of the tests pass - will review this when I get back from a meeting

@Aaronontheweb
Copy link
Member

Looks fine to me.

Aaronontheweb added a commit that referenced this pull request Jan 7, 2016
…receive-fixes1609

Clear current message - fixes #1609
@Aaronontheweb Aaronontheweb merged commit aed87e3 into akkadotnet:dev Jan 7, 2016
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants