-
Notifications
You must be signed in to change notification settings - Fork 472
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
Add IInvocation.CaptureProceedInfo()
method
#439
Conversation
@stakx I'm gonna clone/build your PR locally and look to get a PR raised with https://github.com/JSkimming/Castle.Core.AsyncInterceptor to see how things fare. I'll feed back ASAP. |
@stakx, just some early feedback (I'm still hacking)
A property on Heap allocation due to calling Your implementation instantiates the heap object for every interception that uses Instead of GetProceedInfo() returning an interface, can it return a struct instead? The implementation is merely a reference and an |
@JSkimming - thanks a lot for trying this out right away! Hopefully that way, we can get this async riddle solved soon!
Hmm, I would say it is very likely that an interceptor will want to look at the
While I'd prefer to keep this new type as tight as possible in the beginning, I have no real objection to an additional
I've considered making it a This could be mitigated by simply having I think I can live with this very small performance dip, as long as these objects only get built on demand ("you only pay for what you use"), vs. building a whole batch of them in advance. Finally, this facility will likely only be useful when dealing with async interception peculiarities, and there's a lot more overhead involved in juggling with async / |
Good discussion and thoughts. I like the fact that it makes a fairly straight forward workaround possible for the cases where await before proceed problem occur. This adds the extra complexity to how to implement the Invoke method. This can be abstracted away in as @JSkimming is pointing out in I also agree with @stakx regarding the cost of allocating the extra object on the heap shouldn't matter that much. I am happy an fix for the long lived await problem is near :) I do think we should take the opportunity to do the breaking change in AsyncInterceptor
I also think after looking at the changes AsyncInterceptor - that the splitting up the "return" value to different methods should be looked at again. Just make the "Proceed" method return a Side-note: |
@stakx, thanks so much for these fixes. It's a top effort to navigate the challenges of maintaining backwards compatibility of a very popular library, but also to help drive it forward, so first and foremost, hats off to you my friend. I've picked up an old branch I was hacking on in a failed attempt to fix this in Castle.Core.AsyncInterceptor, and applied the changes against a local copy of Castle.Core. The changes are in this commit JSkimming/Castle.Core.AsyncInterceptor@96d9a0f. ⭐️⭐️⭐️⭐️ I can confirm it has resolved the issues. ⭐️⭐️⭐️⭐️ This is great. There is a number of outstanding issues against the library which were dependent on this fix. The fix I applied above was with the So concerning the I'm also considering a bit of a refactor of AsyncInterceptorBase as I feel it's imposing too many assumptions. It's trying to make life simple for implementations by providing just two asyncronious methods to implement, but in doing so, it forces asynchrony on synchronous method invocations (with some hackery to get around it), and now it forces So given this will be a breaking change, I may choose a bit of a larger refactor at the same time. Anyway, thanks again for this. It's very much appreciated. |
@JSkimming glad to hear the fix is resolving the issues! I think it is perfectly fine to force asynchrony on AsyncInterceptions :) When thinking on the use case for AsyncInterceptor is when you are in an "async call flow". So either the method should be async (and everything is fine) or sync then the method "should be cpu bound" (this is fine also) and not I/O (this would lead to the sync over async threadpool thread starvation). The point being - if that code was written without using Given this assumption, the Invoke method could run the non |
@JSkimming - That's some awesome news! Very happy to hear that you got things working! 🍾🎆 Thank you also for the kind words. ❤️ I must confess that I've stayed away from those async issues longer than I should have, simply because they seemed so daunting and complex. Now I'm more than a little surprised at how quickly we've been able to make progress; I didn't expect it to go so well. Thanks to you for giving feedback so quickly. I'm probably not going to merge this just yet. Ideally, we'd get @jonorossi to look at this new API and give it his blessings. Also, do you think we should wait until after you've refactored your AsyncInterceptor library, or do you think we'll be good to go either way? |
I vote for: good to go directly |
|
It could be worth releasing a beta version of Castle.Core to nuget, we could then iterate on that and solicit feedback with a beta AsyncInterceptor. |
@jonorossi - Could we do a pre-release that includes this PR anytime soon to unblock the AsyncInterceptor project? |
d3095f8
to
daba8ee
Compare
src/Castle.Core.Tests/DynamicProxy.Tests/InvocationProceedInfoTestCase.cs
Show resolved
Hide resolved
Right now you can get We do have "Do not publish NuGet package artifacts to account/project feeds on Pull Requests" set but it is publishing them anyway. I can't find anyway to filter the feed so you'll just have to look at the last build and pick that exact version. |
Could you add a test that calls proceed multiple times? E.g. an async retry interceptor. |
@ndrwrbgs - Can do. Alternatively, feel free to suggest a concrete test case, I'd be happy to cherry-pick it from you. You might be more efficient at writing that particular test, or even already have one ready for adaptation somewhere, given your previous involvement with async interception. If not I'll have a go at it. |
src/Castle.Core.Tests/DynamicProxy.Tests/InvocationProceedInfoTestCase.cs
Outdated
Show resolved
Hide resolved
a882c64
to
b378594
Compare
b378594
to
8b9255f
Compare
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.
The docs looks great (just one more comment), heaps more guidance than I expected which is good.
8b9255f
to
dc35dfd
Compare
IInvocation.GetProceedInfo()
methodIInvocation.CaptureProceedInfo()
method
which returns a descriptor for whatever a call to `Proceed` would pro- ceed to. For now, this can only be used to capture the `Proceed` call for later execution (e.g. from an async continuation). More reflection capabilities could be added later on (e.g. for discov- ering whether `Proceed` would invoke another interceptor or the proxy target object).
As requested in a review. The new name better emphasizes the fact that the method doesn't return the same value every time, and that the re- turned value is immutable / "frozen in time".
`SetReturnValueInterceptor` does not proceed (as I thought it did). Let's replace it, so the tests do what they were meant to do.
dc35dfd
to
adf642a
Compare
Seems like a good time to merge to master. Go ahead if you are ready. |
@jonorossi: Thanks for reviewing this so carefully! 👍 |
@ndrwrbgs: While I did add two tests that proceed multiple times, I opted not to add too many test about async scenarios in order to keep this PR focused. Feel free to submit a PR that e.g. adds more tests to |
@stakx I don't have the attention span right now to fully review what changes were involved, but I blindly took the change and the feature branch in https://github.com/JSkimming/Castle.Core.AsyncInterceptor and it seems that whatever you did + @JSkimming's changes in his project at https://github.com/JSkimming/Castle.Core.AsyncInterceptor/tree/hacking-for-async-fix have made it so an asynchronous retry interceptor is now possible, cool! (not to mention other solutions, like asynchronous logging interceptor) |
TL;DR: This resolves #145.
This is another attempt (after #438) at enabling asynchronous interceptors (#145) via an API addition that's as non-breaking as possible, in the sense of both "not breaking any abstractions" and "not introducing any breaking changes".
I abandoned #438 because the abstraction introduced there didn't seem quite right: We don't really need the ability to snapshot an
IInvocation
object's state (specifically,currentInterceptorIndex
) and restore it later; what we do need is a way to makeinvocation.Proceed()
calls work from an async continuation that executes when interception ofinvocation
has already completed.The new API introduced here is an
invocation.CaptureProceedInfo()
method that captures, in the form of anIInvocationProceedInfo
descriptor object (think Reflection!), whateverinvocation.Proceed()
would do at that specific moment. That action can then beInvoke
d at a later time via that returned descriptor object:This should be sufficient to let people implement some kind of
AsyncInterceptor
base class.What this does not solve is #238, i.e. the fact that
AbstractInvocation.Proceed
is inherently thread-unsafe due to the non-atomiccurrentInterceptorIndex
increment/decrement dance. #428 may possibly do a better job in that regard, FWIW./cc @JSkimming, @brunoblank (sorry for needlessly CCing you in #438; I hope this PR will fare better)