Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Safety of AsyncLocal in ASP.NET Core

For .NET Core, AsyncLocal is the replacement for CallContext. However, it is unclear how "safe" it is to use in ASP.NET Core.

In ASP.NET 4 (MVC 5) and earlier, the thread-agility model of ASP.NET made CallContext unstable. Thus in ASP.NET the only safe way to achieve the behavior of a per-request logical context, was to use HttpContext.Current.Items. Under the covers, HttpContext.Current.Items is implemented with CallContext, but it is done in a way that is safe for ASP.NET.

In contrast, in the context of OWIN/Katana Web API, the thread-agility model was not an issue. I was able to use CallContext safely, after careful considerations of how correctly to dispose it.

But now I'm dealing with ASP.NET Core. I would like to use the following middleware:

public class MultiTenancyMiddleware {     private readonly RequestDelegate next;     static int random;      private static AsyncLocal<string> tenant = new AsyncLocal<string>();     //This is the new form of "CallContext".     public static AsyncLocal<string> Tenant     {         get { return tenant; }         private set { tenant = value; }     }      //This is the new verion of [ThreadStatic].     public static ThreadLocal<string> LocalTenant;      public MultiTenancyMiddleware(RequestDelegate next)     {         this.next = next;     }      public async Task Invoke(HttpContext context)     {         //Just some garbage test value...         Tenant.Value = context.Request.Path + random++;         await next.Invoke(context);          //using (LocalTenant = new AsyncLocal<string>()) {          //    Tenant.Value = context.Request.Path + random++;         //    await next.Invoke(context);         //}     } } 

So far, the above code seems to be working just fine. But there is at least one red flag. In the past, it was critical to ensure that CallContext was treated like a resource that must be freed after each invocation.

Now I see there is no self-evident way to "clean up" AsyncLocal.

I included code, commented out, showing how ThreadLocal<T> works. It is IDisposable, and so it has an obvious clean-up mechanism. In contrast, the AsyncLocal is not IDisposable. This is unnerving.

Is this because AsyncLocal is not yet in release-candidate condition? Or is this because it is truly no longer necessary to perform cleanup?

And even if AsyncLocal is being used properly in my above example, are there any kinds of old-school "thread agility" issues in ASP.NET Core that are going to make this middleware unworkable?

Special Note

For those unfamiliar with the issues CallContext has within ASP.NET apps, in this SO post, Jon Skeet references an in-depth discussion about the problem (which in turn references commentary from Scott Hanselman). This "problem" is not a bug - it is just a circumstance that must be carefully accounted for.

Furthermore, I can personally attest to this unfortunate behavior. When I build ASP.NET applications, I normally include load-tests as part of my automation test infrastructure. It is during load tests that I can witness CallContext become unstable (where perhaps 2% to 4% of requests show CallContext being corrupted. I have also seen cases where a Web API GET has stable CallContext behavior, but the POST operations are all unstable. The only way to achieve total stability is to rely on HttpContext.Current.Items.

However, in the case of ASP.NET Core, I cannot rely on HttpContext.Items...there is no such static access point. I'm also not yet able to create load tests for the .NET Core apps I'm tinkering with, which is partly why I've not answered this question for myself. :)

Again: Please understand that the "instability" and "problem" I'm discussing is not a bug at all. CallContext is not somehow flawed. The issue is simply a consequence of the thread dispatch model employed by ASP.NET. The solution is simply to know the issue exists, and to code accordingly (e.g. use HttpContext.Current.Items instead of CallContext, when inside an ASP.NET app).

My goal with this question is to understand how this dynamic applies (or does not) in ASP.NET Core, so that I don't accidentally build unstable code when using the new AsyncLocal construct.

like image 281
Brent Arias Avatar asked Apr 09 '16 00:04

Brent Arias


People also ask

Is Asynclocal thread safe?

This type is thread-safe for all members.

Is ASP.NET Core thread safe?

NET class libraries are not thread safe by default. Avoid providing static methods that alter static state. In common server scenarios, static state is shared across requests, which means multiple threads can execute that code at the same time. This opens up the possibility of threading bugs.

What is Asynclocal in C#?

Represents ambient data that is local to a given asynchronous control flow, such as an asynchronous method.

Is ASP.NET Core stable?

ASP.NET Core is designed to allow runtime components, APIs, compilers, and languages evolve quickly, while still providing a stable and supported platform to keep apps running.


2 Answers

I'm just looking into the source code of the ExecutionContext class for CoreClr: https://github.com/dotnet/coreclr/blob/775003a4c72f0acc37eab84628fcef541533ba4e/src/mscorlib/src/System/Threading/ExecutionContext.cs

Base on my understanding of the code, the async local values are fields/variables of each ExecutionContext instance. They are not based on ThreadLocal or any thread specific persisted data store.

To verify this, in my testing with thread pool threads, an instance left in async local value is not accessible when the same thread pool thread is reused, and the "left" instance's destructor for cleaning up itself got called on next GC cycle, meaning the instance is GCed as expected.

like image 171
Teddy Ma Avatar answered Sep 21 '22 19:09

Teddy Ma


Adding my two cents if someone lands on this page (like I did) after googling if AsyncLocal is "safe" in ASP.NET classic (non Core) application (some commenters have been asking this, and also I see a deleted answer asking about the same).

I wrote a small test that simulates asp.net's ThreadPool behavior

  1. AsyncLocal is always cleared between requests even if thread pool re-uses an existing thread. So it is "safe" in that regard, no data will be leaked to another thread.

  2. However, AsyncLocal can be cleared even within the same context (for example between code that runs in global.asax and the code that runs in controller). Because MVC-methods sometimes runs on a separate thread from non-MVC code, see this question for example: asp.net mvc 4, thread changed by model binding?

  3. Using ThreadLocal is not safe b/c it preserves the value after the thread from Thread Pool is re-used. Never use ThreadLocal in web-applications. I know the question is not about ThreadLocal I'm just adding this warning to whoever considering using it, sorry.

Tested under ASP.NET MVC 5 .NET 4.7.2.

Overall, AsyncLocal seems like a perfect alternative to short-time caching stuff in HttpContext.Current in cases where you can't access the latter directly. You might end up re-calculating the cached value a bit more often though, but that's not a big problem.

like image 36
Alex from Jitbit Avatar answered Sep 18 '22 19:09

Alex from Jitbit