21

I have a following code example that is used in ASP.NET MVC application. The purpose of this code is to create "fire and forget" request for queuing some long running operation.

public JsonResult SomeAction() {
   HttpContext ctx = HttpContext.Current;            

   Task.Run(() => {
       HttpContext.Current = ctx;
       //Other long running code here.
   });

   return Json("{ 'status': 'Work Queued' }");
}

I know this is not a good way for handling HttpContext.Current in asynchronous code, but currently our implementation not allows us to do something else. I would like to understand how much this code is dangerous...

The question: Is it theoretically possible that setting the HttpContext inside Task.Run, will set the context to totally another request?

I think yes, but I'm not sure. How I understand it: Request1 is handled with Thread1 from thread pool, then while Thread1 is handling absolutelly another request (Request2), the code inside Task.Run will set context from Request1 to Request2.

Maybe I am wrong, but my knowledge of ASP.NET internals not allows me to understand it correctly.

Thanks!

Alex Dn
  • 5,465
  • 7
  • 41
  • 79
  • 3
    Would it not be simpler to just grab the information from the HttpContext that you need, rather than pass in the whole HttpContext? (I realize this does not answer your question, but I'm curious as to the need of the whole Context). – vcsjones Sep 26 '16 at 13:39
  • 2
    Right, this is much correct way, but unfortunately, currently I cannot change it. Our code have access for HttpContext.Current deep inside business logic and changing it is a huge effort that currently we have no. – Alex Dn Sep 26 '16 at 13:42
  • 1
    Not related to your question - having long running task in web context is not a great idea - the server can get restarted & there is only so many threads in pool - once you run out of threads you'll stop serving requests. Have you considered something like HangFire or Quartz ? – Ondrej Svejdar Sep 26 '16 at 14:28
  • @OndrejSvejdar, you are correct, but this is what we currently have and cannot afford changing the architecture now. – Alex Dn Sep 26 '16 at 14:32
  • Are you by any chance using "await" inside the //Other long running code here. ? – Ondrej Svejdar Sep 26 '16 at 14:34
  • @OndrejSvejdar we have some methods inside of business logic that uses async/await, but I don't think it somehow affects the fact the "main" execution is just Task.Run. – Alex Dn Sep 26 '16 at 14:51
  • Possible duplicate of [Using HttpContext in Async Task](https://stackoverflow.com/questions/13748267/using-httpcontext-in-async-task) – Liam Aug 17 '18 at 10:05

2 Answers2

14

Let me bump a little internals on you:

public static HttpContext Current
{
    get { return ContextBase.Current as HttpContext; }
    set { ContextBase.Current = value; }
}

internal class ContextBase
{
    internal static object Current
    {
        get { return CallContext.HostContext; }
        set { CallContext.HostContext = value; }
    }
}

public static object HostContext
{
    get 
    {
        var executionContextReader = Thread.CurrentThread.GetExecutionContextReader();
        object hostContext = executionContextReader.IllogicalCallContext.HostContext;
        if (hostContext == null)
        {
            hostContext = executionContextReader.LogicalCallContext.HostContext;
        }
        return hostContext;
   }
   set
   {
        var mutableExecutionContext = Thread.CurrentThread.GetMutableExecutionContext();
        if (value is ILogicalThreadAffinative)
        {
            mutableExecutionContext.IllogicalCallContext.HostContext = null;
            mutableExecutionContext.LogicalCallContext.HostContext = value;
            return;
        }
        mutableExecutionContext.IllogicalCallContext.HostContext = value;
        mutableExecutionContext.LogicalCallContext.HostContext = null;
   }
}

So

var context = HttpContext.Current;

is equal to (pseudocode)

var context = CurrentThread.HttpContext;

and inside your Task.Run something like this happens

CurrentThread.HttpContext= context;

Task.Run will start new task with thread from thread pool. So you're telling that your new thread "HttpContext property" is reference to starter thread "HttpContext property" - so far so good (well with all the NullReference/Dispose exceptions you'll be facing after your starter thread finishes). Problem is if inside your

//Other long running code here.

You have statement like

var foo = await Bar();

Once you hit await, your current thread is returned to thread pool, and after IO finishes you grab new thread from thread pool - wonder what its "HttpContext property" is, right ? I don't know :) Most probably you'll end with NullReferenceException.

Ondrej Svejdar
  • 21,349
  • 5
  • 54
  • 89
  • Thank you for the much detailed answer! From what you wrote, i understand that technically it can happen (Request2 will get the context of Request1), but then most likely I will get null references and dispose exceptions, because actually Request1 is finished and ASP.NET "cleared" it's context...am i understand your answer right? Thanks! – Alex Dn Sep 26 '16 at 15:56
5

The issue you will run into here is that the HttpContext will dispose when the request is complete. Since you aren't awaiting the result of the Task.Run, you are essentially creating a race condition between the disposal of the HttpContext and it's usage within the task.

I'm pretty sure that the only issue your task will run into is a NullReferenceException or an ObjectDisposedException. I don't see any way where you could accidentally steal another request's context.

Also, unless you are handling & logging exceptions within your task, your fire and forget will throw and you'll never know about it.

Check out HangFire or consider using a message queue for processing backend jobs from a separate process.

scottt732
  • 3,877
  • 2
  • 21
  • 23
  • Thanks for an answer. I'm aware about race condition and possible NullReference / Disposed exception. But I would like to understand technically why you think that way cannot steal another request context...as I know, technically HttpContext.Current manages contexts by Thread Id, so if two requests will receive Thread with same Id, that HttpContext.Current can return the reference to same object from two requests. – Alex Dn Sep 26 '16 at 15:03