-
Notifications
You must be signed in to change notification settings - Fork 2k
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
Feature: Server-side request interceptor (#749) #963
Feature: Server-side request interceptor (#749) #963
Conversation
You don't need XML file for the test. We have programmable API for providers and there is a virtual base method in the unit test class that can be overwritten to tweak the config object . |
Thumbs up. |
Many thanks, @gabikliot. I'll push an update in a minute. |
d26b8ef
to
c2b9eb0
Compare
Awesome! Very simple implementation and pretty much exactly what I'd like to use for pre-method call setup (in my case, extracting the User ID from the RequestContext, then setting up the Thread.CurrentPrincipal, then invoking the method). One suggestion for my use case (only me?) would be for this interceptor to return an IDisposable, which is disposed of when the call completes, so we can dismantle any context (eg, set Thread.CurrentPrincipal back to NULL, by returning a class that will do that upon disposal): // From
public Action<InvokeMethodRequest, IGrain> PreInvokeCallback
// To
public Func<InvokeMethodRequest, IGrain, IDisposable> PreInvokeCallback Then in the calling code: // From
SiloProviderRuntime.Instance.CallPreInvokeCallback(request, target);
resultObject = await invoker.Invoke(target, request.InterfaceId, request.MethodId, request.Arguments);
// To
using (SiloProviderRuntime.Instance.CallPreInvokeCallback(request, target))
resultObject = await invoker.Invoke(target, request.InterfaceId, request.MethodId, request.Arguments); Thoughts? |
@Plasma I don't understand why would you need a Disposable here? If interception will be implemented in a way it is implemented in every other project out there, specifically as decorator pattern, where you control invocation - you will have an easy way to understand when the call is finished. But I suspect it want happen due to blocking from a core team, as they're scared that we'll abuse it (for the greater go0d). Poor puppy's are we ... 😄 |
@Plasma Wouldn't a symmetric post-invoke callback be a cleaner solution for what you need than IDosposable? |
@yevhen @sergeybykov Yep either of those two options sound great to me. |
c2b9eb0
to
3001fa7
Compare
@sergeybykov forget about IDisposable. That solution was a obvious no-go for anyone. We are not even discussing per-type interceptor. What we are really discussing is 2 different interception approaches:
The first model has a number of clear advantages such as:
The second model:
Regarding second model: been there, done that. It was incredibly frustrating experience. Anyone who tried to do anything meaningful with ASP.NET WebForms Pre/Post model know what I'm talking about. If you're against that virtual method - fine. I do care only about approach. If it will be pipelining implemented only as global callback - it's fine. I'll be able to add per-type interception in my own sandbox then. But if it will be a pre/post callback - it is the same as having no feature for me. And let's remove personalities from this discussion. I don't really care what @yevhen or @Plasma need or think is enough for his particular situation. But I do care about other people looking at that and thinking: Holy crap what they were smoking when designed that feature? The end. |
The second model:
|
But what if there are valid reason not to throw but still cancel?
no, it's not. Due to interleaving (reentrant) grains, PostInvoke will not correlate with PreInvoke. Users will need to correlate themselves.
Sure. The correlation problem described above, is exactly due to lack of composition. Try to do this with Pre/Post model: PerformLogging(
CollectStatistics(MessageCategory.Command,
PublishEvents(Log, publisher,
RetryOnConcurrencyConflicts(retryOnConcurrency, Log,
RetryOnDuplicates(retryOnDuplicates, Log,
HandleIdempotency(Log,
WithinDbTransaction(store, tx =>
request => handler(request)
)))))))(x)); |
Good point about reentrant grains. OK, I remove my objection to pipelining interceptors. The need to manually correlate the pre and post for reentrant calls is a problem. |
Thumbs up!
|
Went to a run and thought more about it. Still did not see a single example where a value need to be returned from the pre decorator without invoking the actual logic ("cancelation and not throwing" scenario). Caching I think is too application specific, and is also different per method and not per grain type, so better done inside the grain logic itself. |
Using Having request interceptors as described allows for a much cleaner design: it gives consumers the option of accessing the grain directly. They can access/modify private members or use the request scoping of their IoC container EDIT: I rather we do not merge this until the design in #965 is settled. |
Note to self: |
@ReubenBond Can you rebase this one? Will you have time to complete it? If not, we can try to do that. |
@sergeybykov when comparing this PR to #965 (comment) - does #965 adequately address server side interception? Maybe I'm misreading it but does the grain intercept code in #965 happen on the server side (thus it can access memory / logical state on the silo and do whatever it wants before the code actually executes)? If so, I personally don't need this PR anymore. |
@Plasma yes, #965 is for server-side grain interception, too. It does not allow for a single global interceptor, though. @sergeybykov I've rebased privately, but this PR is not ready to prime-time yet. |
501dd6d
to
cf31d53
Compare
/// An <see cref="IGrainMethodInvoker"/> which redirects execution for grains which implement | ||
/// <see cref="IGrainInvokeInterceptor"/>. | ||
/// </summary> | ||
internal class InterceptedMethodInvoker : IGrainMethodInvoker |
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 was shifted from another file & should not be functionally different to how it was with #965
cf31d53
to
fd0cf75
Compare
Rebased & squashed 😄 Please critique & consider merging. |
@@ -355,6 +355,7 @@ public static TimeSpan GetResponseTimeout() | |||
/// such as <c>Orleans.RequestContext</c> will be picked up. | |||
/// </summary> | |||
/// <remarks>This callback method should return promptly and do a minimum of work, to avoid blocking calling thread or impacting throughput.</remarks> | |||
/// <param name="method"><see cref="MethodInfo"/> of the method to be invoked.</param> |
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.
These params don't match the callback definition.
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.
Thanks, will fix.
fd0cf75
to
6f9ef20
Compare
|
||
public Task Init(string name, IProviderRuntime providerRuntime, IProviderConfiguration config) | ||
{ | ||
providerRuntime.InvokeInterceptor += (method, request, grain, invoker) => |
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.
Since InvokeInterceptor
is a delegate, if I add more than one interceptor, they will be called in parallel, not as a pipeline? And each interceptor will invoke the grain via invoker
?
6f9ef20
to
c0a886e
Compare
Feature: Server-side request interceptor (#749)
Thank you, @ReubenBond for completing it! |
Based on the discussion in #749.