question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Discussion: Directions to take async interception

See original GitHub issue

We’ve had several discussions on issues and pull requests on this repo, and on Castle.Core, and I wanted to bring it together (if possible).

Firstly, I want to thank you guys for getting involved. I imagine we’ve all got day jobs, so any time we can give to this is fantastic.

Here is my current thinking, it’s by no means an exhaustive list, it’s just what’s top of mind at the moment.

  1. The design of AsyncInterceptorBase is a mistake.
    • In attempting to provide just two methods to override, both of which are asynchronous, it has made synchronous interception problematic with the potential of causing deadlocks when transitioning back from the asynchronous interceptor to the underlying synchronous method being intercepted.
    • Here’s my comment on PR #54 going into a little more detail.
    • I, therefore, agree with @brunoblank in his comment here on PR #40, specifically “I think the AsyncInterceptorBase should be deleted. But, it brings nice abstract methods.”
  2. What to do with return value?
    • I think one of the challenges we’ve had with async before Proceed has been to do with the return value being out of band and as a result out of our control.
    • I like the idea raised by @brunoblank in PR #40, and I wonder what other’s think.
    • I believe you @stakx think differently, though I can’t remember the comment that gave me that impression.
  3. Closely align with the design of Castle.Core
    • This is related to the return value discussion since the design of the IInvocaton has the return value as a property.
    • I think it is a benefit to match the design of Castle.Core as it adheres to the Principle of least astonishment and developers familiar with Castle.Core will find it easier to pick up AsyncInterceptor.
    • Close alignment to Castle.Core may improve the chances of this becoming incorporated into Castle.Core. This is something I considered when I first started this library, and I’ve received comments along that line on other issues. Thoughts @stakx?

The current alpha introduces some breaking changes, and I believe we must introduce more, given point 1. I think now is an excellent time to tackle some other gnarly issues if you guys are happy to get involved?

All views regretfully received.

Issue Analytics

  • State:open
  • Created 4 years ago
  • Comments:16 (4 by maintainers)

github_iconTop GitHub Comments

2reactions
brunoblankcommented, Apr 10, 2019

When using the Castle.Core - IInterceptor for synchronous operations is straight forward and works very well. So having the IAsyncInterceptor align with Castle.Core being asynchronous would look like this

note: that there must be two separate methods for synchronous vs asynchronous as we do not want to introduce sync-over-async or async-over-sync problems

public interface IAsyncInterceptor
{
  void Intercept(IInvocation invocation);
  Task InterceptAsync(IInvocation invocation);
}

However, when trying to use the same concept for asynchronous is hard. Accessing the ReturnValue and casting it is problematic as Task<object> is not compatible with Task<string>. This could be solved by adding a generic based asynchronous “overload”.

public interface IAsyncInterceptor
{
  void Intercept(IInvocation invocation);
  Task InterceptAsync(IInvocation invocation);
  Task InterceptAsync<TResult>(IInvocation invocation);
}

So when implementing an interceptor based on this interface (not addressing the async before proceed problem) is when can/should invocation.ReturnValue be read/written. Calling Proceed in the asynchronous case must read the return-value and await and the method must also set return-value before doing any await in order not to break.

I think this should be handled by AsyncInterceptor. This can be solved by taking care of return-values which would make this IAsyncInterceptor look like this:

public interface IAsyncInterceptor
{
    void Intercept(IInvocation invocation);
    TResult Intercept<TResult>(IInvocation invocation);
    Task InterceptAsync(IInvocation invocation);
    Task<TResult> InterceptAsync<TResult>(IInvocation invocation);
}

From the discussions in PR 428 - castle.core.

Personally, I think that moving Proceed out of IInvocation might be a good thing, as it arguably improves the abstraction of the latter: IInvocation would then (more than now) describe an invocation without tying it to the interception pipeline that processes it. This would arguably fit its name better. So far, so good.

Now we make break out the Proceed usage to own method (also: handling the CaptureProceedInfo in library).

public interface IAsyncInterceptor
{
    void Intercept(IInvocation invocation, Action proceed);
    TResult Intercept<TResult>(IInvocation invocation, Func<TResult> proceed);
    Task InterceptAsync(IInvocation invocation, Func<Task> proceed);
    Task<TResult> InterceptAsync<TResult>(IInvocation invocation, Func<Task<TResult>> proceed);
}

The the last touch is to make an IAsyncInvocation that hides Proceed and ReturnValue from IInvocation just to remove possibility to use it the wrong way.

public interface IAsyncInterceptor
{
    void Intercept(IAsyncInvocation invocation, Action proceed);
    TResult Intercept<TResult>(IAsyncInvocation invocation, Func<TResult> proceed);
    Task InterceptAsync(IAsyncInvocation invocation, Func<Task> proceed);
    Task<TResult> InterceptAsync<TResult>(IAsyncInvocation invocation, Func<Task<TResult>> proceed);
}
1reaction
stakxcommented, May 1, 2020

Hey everyone, I’d like to share something with you that I cooked up just now.

A little earlier today, I was looking at the code produced by the C# compiler for async methods containing any awaits. I had the thought that it might be possible to follow the same pattern inside a DynamicProxy IInterceptor, and I came up with an alternate AsyncInterceptor base class. (Its public interface is close to what I described in my above posts.)

If you’re interested and if you would like to take a look, here’s the resulting code: https://github.com/stakx/DynamicProxy.AsyncInterceptor. (It’s only a draft at this stage, though it appears to work, at least in the few simple cases that I’ve tested.)

Read more comments on GitHub >

github_iconTop Results From Across the Web

Core/docs/dynamicproxy-async-interception.md at master
This article discusses several interception scenarios related to ... This article contains C# code examples that make use of the async / await...
Read more >
Intercepting Asynchronous Methods Using Unity Interception
We discussed several strategies for intercepting asynchronous methods and demonstrated them on an example that logs the completion of asynchronous operations.
Read more >
Using an async method in a RxJS NextObserver
I'm trying to use an async function in a NestJS interceptor. These interceptors use RxJS Observables like this:
Read more >
Intercepting HTTP Requests with Playwright
In this post, we take a look at the benefits and possibilities while intercepting HTTP requests in your Playwright tests.
Read more >
Interceptors | NestJS - A progressive Node.js framework
Hint Nest interceptors work with both synchronous and asynchronous intercept() methods. You can simply switch the method to async if necessary. With the...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found