Skip to content

Conversation

@brunoblank
Copy link
Collaborator

Make IAsyncInterceptor easier use.

  • Implementation can be async directly.
  • await can be used on invocation.Proceed() as it returns the value including Task support

@codecov
Copy link

codecov bot commented Feb 19, 2018

Codecov Report

Merging #40 into master will not change coverage.
The diff coverage is 100%.

Impacted file tree graph

@@          Coverage Diff          @@
##           master    #40   +/-   ##
=====================================
  Coverage     100%   100%           
=====================================
  Files           5     10    +5     
  Lines         229    254   +25     
  Branches        7      4    -3     
=====================================
+ Hits          229    254   +25
Impacted Files Coverage Δ
...le.Core.AsyncInterceptor/AsyncTimingInterceptor.cs 100% <ø> (ø) ⬆️
....Core.AsyncInterceptor/ProxyGeneratorExtensions.cs 100% <100%> (ø) ⬆️
...Interceptor/Invocations/AsyncFunctionInvocation.cs 100% <100%> (ø)
...ore.AsyncInterceptor/Invocations/InvocationBase.cs 100% <100%> (ø)
...AsyncInterceptor/Invocations/FunctionInvocation.cs 100% <100%> (ø)
...stle.Core.AsyncInterceptor/AsyncInterceptorBase.cs 100% <100%> (ø) ⬆️
...ore.AsyncInterceptor/ProcessingAsyncInterceptor.cs 100% <100%> (ø) ⬆️
....AsyncInterceptor/AsyncDeterminationInterceptor.cs 100% <100%> (ø) ⬆️
...e.AsyncInterceptor/Invocations/ActionInvocation.cs 100% <100%> (ø)
...ncInterceptor/Invocations/AsyncActionInvocation.cs 100% <100%> (ø)
... and 5 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update b4fd89f...5a5ad5e. Read the comment docs.

@brunoblank
Copy link
Collaborator Author

There's a lot to take in and I may not get a chance to give it a good look until the weekend, though I'll try and have a look sooner. In principle, I'm happy for such extensive changes, it's good to get the input of others, but I do want to get a deep understand, and that may take me a bit of time, which is challenging to fit around the day job.

@JSkimming take your time, I have no intention of stressing this through. All help / thoughts and feedback are welcome!

I realized the purpose of AsyncInterceptorBase as a bridge between the synch to asynch. I will look into adding this functionality back.

CompletedInvocation I am still a bit uncertain about the intention with this one. Are both of the virtual method supposed to be there?

Looking at the code-coverage, Adding a tests for InvocationBase will bring coverage back to around 100%

@JSkimming JSkimming self-assigned this Feb 20, 2018
@JSkimming JSkimming self-requested a review February 20, 2018 08:21
@JSkimming
Copy link
Owner

@brunoblank Thanks for this.

Looking at the code-coverage, Adding a tests for InvocationBase will bring coverage back to around 100%

Yes, it would be good to get the coverage back up, and while it was 100% I know it is sometimes unachievable, though the higher the better.

/// <param name="invocation">The method invocation.</param>
public void Intercept(IInvocation invocation)
{
try

Choose a reason for hiding this comment

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

Personally previously (with GetMethodType helper and MethodType enum) it was easier to read this method

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yes, that makes sense. Good call

}
else if (typeof(Task).IsAssignableFrom(invocation.Method.ReturnType))
{
invocation.ReturnValue = InterceptAsyncFunctionMethodInfo

Choose a reason for hiding this comment

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

Is there any reason to remove the "concurrent dictionary" implementation based on method delegates?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

No, I was under the assumption that performance cost was Getting the MethodInfo and not calling Invoke. After doing a few benchmarks it was clear that the AsyncDeterminationInterceptor was superior in performance.

Reverted back to original implementation with the addition of Function handling

}
else
{
invocation.ReturnValue = InterceptFunctionMethodInfo.MakeGenericMethod(invocation.Method.ReturnType)

Choose a reason for hiding this comment

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

The same issue with reflection

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Thank you for the link! Reverted back to AsyncDeterminationInterceptor

/// </param>
protected InvocationBase(IInvocation invocation)
{
Invocation = invocation;

Choose a reason for hiding this comment

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

Don't you want to implement the Proceed method as well?
Here I see the mix of IInvocation and IAsyncInvocation methods

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Good question.

I have intentionally not added Proceed and ReturnValue to IAsyncInvocation as it should not be visible to the Interceptor.

The return-value handling and proceed is implemented separately in each pair of

class FunctionInvocation<TResult> : InvocationBase, IFunctionInvocation<TResult>

The InvocationBase class basically wraps IInvocation from Castle.

/// <see cref = "NotImplementedException" /> will be thrown.
/// </remarks>
/// <returns>The asynchronous task</returns>
Task Proceed();

Choose a reason for hiding this comment

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

What about naming ProceedAsync?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Personally, I don't like to have the Async suffix.

But it is important to express intent and since the synchronous version is called Proceed is a good idea to change to ProceedAsync to be extra clear.

/// <see cref = "NotImplementedException" /> will be thrown.
/// </remarks>
/// <returns>The return value of the intercepted call</returns>
Task<TResult> Proceed();

Choose a reason for hiding this comment

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

What about naming ProceedAsync?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Personally, I don't like to have the Async suffix.

But it is important to express intent and since the synchronous version is called Proceed is a good idea to change to ProceedAsync to be extra clear.

@brunoblank
Copy link
Collaborator Author

Thanks @alex-training for good comments. I have updated PR.

The AsyncInterceptorBase and CompletedInvocation still needs work through as I was focusing on the review comments.

@brunoblank
Copy link
Collaborator Author

I did a small comparison benchmark comparing MethodInfo.Invoke and Create.Delegate

                   Method |         Mean |      Error |     StdDev |
------------------------- |-------------:|-----------:|-----------:|
                 Baseline |     3.691 ns |  0.0704 ns |  0.0588 ns |
         MethodInvokation | 1,331.831 ns | 12.1414 ns | 10.1386 ns |
   StaticMethodInvokation | 1,210.952 ns | 12.5258 ns | 11.7166 ns |
           CreateDelegate | 3,527.390 ns | 35.4130 ns | 31.3927 ns |
       DictionaryDelegate |    37.906 ns |  0.4435 ns |  0.4149 ns |
           StaticDelegate |     7.989 ns |  0.1237 ns |  0.1097 ns |

Baseline is using the Generic approach directly. The DictionaryDelegate is using the ConcurrentDictinary approach implemented now. The StaticDelegate version is just to compare the dictionary lookup cost.

@JSkimming
Copy link
Owner

JSkimming commented Feb 21, 2018

@alex-training thanks for joining in. It's great to get further input. I'll start weighing in soon.

I've never quite known whether this library is receiving much usage, the engagement of others is rather heartening.

I'm open to wider administration such that other interested parties can join in with merging pull requests and pushing to NuGet. If people are interested, let's discuss that in another thread.

@alex-training
Copy link

@brunoblank , @JSkimming
Thanks for your efforts and contribution.
I still hope the Castle guys will add support of async/await to the Core ...

@JSkimming
Copy link
Owner

@brunoblank I've just had a look and made a few minor changes. I've restored the intent of the WhenExceptionInterceptingAnAsynchronousMethodThatThrowsASynchronousException test.

I've added a comment stating the intent of the test, which remains the same, what has changed is the behaviour of async interception. Hence, I've changed the tests to reflect the new behaviour rather the class being intercepted.

So far I'm liking the changes 👍

@brunoblank
Copy link
Collaborator Author

Nice, I understand the intent of the WhenExceptionInterceptingAnAsynchronousMethodThatThrowsASynchronousException test. Thanks for clarifying.

I am still a bit confused about the CompletedInvocation in ProcessingAsyncInterceptor. Where the with returnValue is calling the one without. Is two methods needed? Also have you considered making them Task based?

The ProxyGeneratorExtensions class is missing the CreateInterfaceProxyWithoutTarget from Castle. Should I add them?

@JSkimming
Copy link
Owner

@brunoblank Thanks for that last commit. I'm afraid my weekend was busier than expected and I've not been able to devote much time to this. I will endeavour to review it soon.

@brunoblank
Copy link
Collaborator Author

Hi @JSkimming, how are things looking at your end?

@JSkimming
Copy link
Owner

@brunoblank rather hectic. We have a new product going live on Tuesday and I've not been able to devote any free time to this in recent weeks.

I hope to get some time next weekend.

@brunoblank
Copy link
Collaborator Author

@JSkimming I have spent some time looking at this feature again. While troubleshooting the problem in core and understand more of what the problem was. I think this PR solves a few of the problems. But I am not happy with it. There are some key benefits here, the key thing is the return-values in IAsyncInterceptor.

Then, after the discussions regarding Task.Run().GetAwaiter().GetResult() problem in AsyncInterceptorBase. I think the AsyncInterceptorBase should be deleted. But, it brings nice abstract methods.

        protected abstract Task<TResult> InterceptAsync<TResult>(
            IInvocation invocation,
            IInvocationProceedInfo proceedInfo,
            Func<IInvocation, IInvocationProceedInfo, Task<TResult>> proceed);

My suggestion is to update the IAsyncInterceptor to have that syntax (the proceedInfo can be abstracted away to make it easier for the users). So it would look something like this:

public interface IAsyncInterceptor
{
    void InterceptAction(IAsyncInvocation invocation, Action proceed);

    TResult InterceptFunction<TResult>(IAsyncInvocation invocation, Func<TResult> proceed);

    Task InterceptAsyncAction(IAsyncInvocation invocation, Func<Task> proceed);

    Task<TResult> InterceptAsyncFunction<TResult>(IAsyncInvocation invocation, Func<Task<TResult>> proceed);
}

@JSkimming
Copy link
Owner

@brunoblank I've brought this discussion, along with a couple of other's into issue #60, I'd like to see if a slightly broader discussion can be had first, though that doesn't mean we can't come back to this later

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants