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
Ability to specify interleaving per message type #2246
Ability to specify interleaving per message type #2246
Conversation
What do you think would be the ideal way to specify per-message reentrancy? We already have Alternatively, we could use Are there other options? |
Any implementation which doesn't require clients (senders) to know about which message should be sent as reentrant. AFAIU both of suggestions (per method / per interface attribute and `RequestContext') doesn't fit that requirement. Also, those won't work with F#. Discriminated unions require special handling due to how cases are implemented internally (as static properties!). That's why I've added a function callback,. so I can install a different implementation for C#/F# respectively. |
I should probably add some details about upcoming 1.0 client-server implementation in Orleankka. Previously, we were having fixed (static) set of grain implementations and interfaces covering all possible grain configurations (one for worker, one for actor with random placement, with activation count placement, etc) and acting as generic hosts. The actual type of the actor (in the form of its type code) that need to be instantiated had been sent with every request. Now, in 1.0 we got rid of this in favor of pre-generating (using Roslyn) grain interfaces dynamically. Since, all interfaces have exactly the same shape - this was only about generating interfaces with exactly the same type name. On server-side, we generate grain interface/implementation and since we have all required information there, we amend the auto generated grain implementation with all neccessary attributes, such as I don't (and shouldn't) have any server-side information on client. I gues this rules out |
What criteria would be used to decide whether to interleave a particular request? Argument type? An expression calculated over argument(s)? You mentioned in the code that you envision this per type predicate to be installed by a boorstrap provider. Do you see a problem with defining such a predicate as part of the grain class, so that it could be auto-discovered and installed without the need for a bootstrap provider? |
Or am I completely missing the point? |
It's argument type for both C# and F#. Thus, there is a some weirdness wrt to how properly calculate the argument type when argument is F#'s Discriminated Union. As F# is functional the callback style looks more idiomatic and will cover both C# and F#. In F# this was previously implemented as conventional static function on actor's type. For C# - this was supported via |
No, ok for me. I just don't know how to add this to Orleans so it doesn't look innocent to its core design. |
I can imagine something similar to how stream filters are defined now, like putting on the method: [AlwaysInterleave("SomeClass.SomeStaticCallbackMethod")]
Task SomeMethod(object msg) Where bool Predicate(Type grainType, object msg); |
Shouldn't it rather be What about F#? Will such approach work for it? |
Yes, it will |
If we could work this into grain-level or silo-level interceptors, would that work for you? I'm not sure that it can be done cleanly |
Interceptor would be fine. Anything that calls my predicate with grain type and method argument info will do. |
Silo wide interceptors require a bootstrap provider or some other way to install them globally. I think we better avoid such a burden here if we can. Hence, grain level interceptors seem like a more convenient pattern to me. However, if you envisions no grain class specific logic in the predicate, then defining a per class predicate may become a burden. We could add a method to public interface IGrainReentrancyOverride
{
bool CanInterleaveRequest(TypeInfo grainType, InvokeMethodRequest request);
} The scheduler will likely have to treat grain classes with a reentrancy overrides as reentrant (they are handled differently today) but interleave requests only when |
Sounds complicated to me but you know better how to make it in a most lean and logical way. I'm ok with grain level interceptors if they can amend how the message is delievered (interleaved or not) |
I don't know if I know better. :-) Just throwing some thoughts for other people to weigh in. |
Any other opinions here? @ReubenBond, what do you think? @jdom? We need to unblock @yevhen. |
Whatever design we adopt, it cannot break the single-threaded invariant. If we are going to add a new method/interface, are there any other provisions we'd like in that interface? Just to make sure that we're not making adding a specific feature when we could solve this with a more general approach which might result in a less complicated API in the long run. As an example, we're talking about classifying a message as being I think my comment about using grain-level interceptors is silly, since it executes too late. |
Here's a proposal.
bool CanInterleaveRequest(InvokeMethodRequest request); method to
Will this be sufficient? Did I miss something? |
Sorry, I am not fully following why this is needed. Can u please briefly summarize what will this achieve? |
@gabikliot You mean the whole purpose of the change? In Orleankka, @yevhen implements the Erlang/Akka style model with a single Now for different message types he needs to have different reentrancy rules, as if for different methods in a grain interface. He wants to do that dynamically via a callback. We could consider expanding the scope of this change, so that @yevhen, is this an accurate description? |
absolutely. |
Sergey, yes I meant the whole purpose. |
@yevhen has a single method that we wants to interleave for some requests and not to interleave for others. The decision would be made based on the argument types and potentially other data in the request. Hence, a static |
Ohh, I think I understand now. So alternatively, he could have implemented the queuing himself, in his dispatch logic in the silo interceptor. In this approach, from Orleans perspective all calls can interleave (there will be a single call 'DO' with Is that a correct description? |
@gabikliot ye, close to. Previously, I have these two methods: interface IActor : IGrainWithStringKey
{
Task<object> Receive(object msg);
[AlwaysInterleave] Task<object> ReceiveReentrant(object msg);
} so grains we not Unfortunately, when sending message from client to server with that approach it means that server configuration (which type of message need to be sent via interleaved method) need to be available on the client, which doesn't seem right. Also, what's more important - it doesn't work with streams 😞 |
I kinda solved it by marking all actors as Reentrant and then using AsyncSerialExecutor to execute non-interleaved calls serially but it adds additional overhead to every actor and call. So it would be really cool if such support is available out-of-the-box from underlying framework. Will make it 100% friendly for message-based style of communication. |
What's the problem with streams?
Yes, exactly - the callback would supply a kind of a dynamic reentrancy policy. Do you see any holes in my proposal above? I assume no changes in the scheduler will be required, as we'll leverage the Dispatcher's reentrancy logic, but I'm not 100% sure I'm not missing something. |
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 general I like the approach, but as I mentioned, I'd combine it with some way of statically defining the message interleaving, instead of having to dig into all GrainTypeData
objects when starting up to inject this functionality.
|
||
public bool IsMessageInterleaves(Message message) | ||
{ | ||
return IsReentrant || IsMessageInterleavesPredicate(message.BodyObject.GetType()); |
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 take it that in Orleankka you do not use the InvokeMethodRequest
as the BodyObject, otherwise this wouldn't give you what you want
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.
that's just spike :)
Ye, I need a method request argument.
@@ -127,5 +128,19 @@ internal static MultiClusterRegistrationStrategy GetMultiClusterRegistrationStra | |||
grainClass.Name)); | |||
} | |||
} | |||
|
|||
// used by Orleankka's bootstrapper | |||
internal void SetMessageAlwaysInterleaves(Func<object, bool> predicate) |
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.
it would be good to combine this with @sergeybykov's suggestion of being able to define it statically, with an attribute or something on the grain implementation or interface, or even on the argument type as defined in the interfaces that the silo uses. I would prefer on the grain type itself, since the argument type could be re-used across grain implementations, and each have different interleaving implications, or even different grain implementations for the same grain interface might have different needs.
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.
grain implementation or interface
implementation. I think interface is not a good candidate since it's reentrancy is an implementation detail.
In current design grains are not marked as Reentrant so stream subscriptions inside actor are not reentrant as well. If I mark it as Reentrant I'll loose ability to specify reentrancy in a granular way. |
ee8ad76
to
4d7ba57
Compare
Be careful that if you want to re-open the PR eventually, you have to re-open with exactly the same original commits as before closing, and only after that rebase the branch. Otherwise GitHub just thinks they are entirely different branches and want allow you to re-open the same PR. |
btw, my preference would also be |
I think it's easier to create new PR. |
As you prefer. Nevertheless if it was my choice, I'd prefer to re-open this one, as all the discussions happened here. Basically just do the rebase (or re-do) on a separate branch, reopen this, and then force push to this branch |
Ok, figured out what was wrong. |
521ffa8
to
4269cb0
Compare
It would be better to squash changes but for now could be seen here https://github.com/dotnet/orleans/pull/2246/files I think |
4269cb0
to
b4105c4
Compare
Looks good to me as is. I kicked off a load test just to confirm there is no impact on perf. But I can't imagine that. Will merge after that unless I hear objections.
Did you rather mean |
@@ -68,6 +71,84 @@ public Task SetSelf(INonReentrantGrain self) | |||
} | |||
} | |||
|
|||
[MayInterleave("MayInterleave")] |
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.
Does [MayInterleave(nameof(MayInterleave)]
compile at this scope (or call the method MayInterleavePredicate to make it more explicit that it has hothing to do with the attribute name)? If so, that would be preferred, as people might look at this test to get a sense of how the feature works
Perf test looks good. |
Cool, thanks! When to expect next minor release? )) |
1.3.1. is in the works. See #2350. |
* Ability to specify interleaving per message type * Rename methods as per @gabikliot review comments * Add tests for message interleaving predicate (false/true) * Add tests for stream delivery and when predicate throws * Introduce attribute. Review fixes * Make naming consistent * Use nameof instead of string
Hi guys!
As you know Orleankka provides a functional message-based style API for Orleans and all grains have a uniform interface like:
To support some advanced scenarios like ability to specify message interleaving in a more granular way (per-message type) which is incredibly valuable feature we extended this interface with additional method:
So when sending a message via actor reference we were selecting one method ot the other depending on the configuration for this message/actor type.
I'm preparing next big and updated release (1.0, ya finally!) and most anticipated feature is to be able to send messages from clients without need to reference server-side libraries (containing actor definitions). Basically, proper client-server separation.
Unfortunately, current implementation of per-message reentrancy requires to have that information on client (so that we can choose either
Receive
orReceiveReentrant
). Which is a leak of server-side knowledge and is very illogical.I haven't found any other way then to extend implementation upstream. It's just a rough sketch not a final code, something to discuss further. I will highly appreciate you help on this issue. Please advice, mesage reentrancy is very important (and used) feature in Orleankka and now this stops me from releasing 1.0 😞