Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Killing a deadlocked Task in .NET 4 TPL

I'd like to start using the Task Parallel Library, as this is the recommended framework going forward for performing asynchronous operations. One thing I haven't been able to find is any means of forcible Abort, such as what Thread.Abort provides.

My particular concern is that I schedule tasks running code that I don't wish to completely trust. In particular, I can't be sure this untrusted code won't deadlock and therefore I can't be certain if a Task I schedule using this code will ever complete. I want to stay away from true AppDomain isolation (due to the overhead and complexity of marshaling), but I also don't want to leave a Task thread hanging around, deadlocked. Is there a way to do this in TPL?

like image 276
Dan Bryant Avatar asked Apr 23 '10 01:04

Dan Bryant


1 Answers

The way to do this is with a CancellationToken and the new cancellation model. The new cancellation model is integrated into the .NET Framework in several types. The most important ones are System.Threading.Tasks, System.Threading.Tasks.Task, System.Threading.Tasks.Task and System.Linq.ParallelEnumerable.

Here's an example of your problem. This code will always deadlock because the calling code takes a lock first and then the deadlocked task tries to aquire the same lock.

public void Example()
{
    object sync = new Object();
    lock (sync)
    {
        CancellationTokenSource canceller = new CancellationTokenSource();
    ManualResetEvent started = new ManualResetEvent(false);
        Task deadlocked = Task.Factory.StartNew(() => 
            { 
            started.Set();
                // EVIL CODE: This will ALWAYS deadlock
                lock(sync) { }; 
            }, 
            canceller.Token);

        // Make sure task has started.
    started.WaitOne(); 

        canceller.Cancel();

        try
        {
            // Wait for task to cancel.
            deadlocked.Wait();
        }
        catch (AggregateException ex) 
        {
            // Ignore canceled exception. SIMPLIFIED!
            if (!(ex.InnerException is TaskCanceledException))
                throw;
        }
    }
}

Task cancellation in the TPL is cooperative. In other words this will always deadlock because nothing handles the cancellation token being set to cancelled because the task thread is locked.

There is a way around this but it still relies on the authors of the untrusted code to do the right thing:

public static void Example2()
{
    Mutex sync = new Mutex(true);

    CancellationTokenSource canceller = new CancellationTokenSource();
    bool started = false;

    Task deadlocked = Task.Factory.StartNew(() =>
        {
            started = true;
            // EVIL CODE: This will ALWAYS deadlock 
            WaitHandle.WaitAny(new WaitHandle[] { canceller.Token.WaitHandle, sync });
        },
        canceller.Token);

    // Make sure task has started.
    while (!started) { }

    canceller.Cancel();

    try
    {
        // Wait for task to cancel. 
        deadlocked.Wait();
    }
    catch (AggregateException ex)
    {
        // Ignore canceled exception. SIMPLIFIED! 
        if (!(ex.InnerException is TaskCanceledException))
            throw;
    }
} 

Points to note; cancellation is cooperative. You can use Token.WaitHandle to get a handle and wait on it along with the handle(s) of other synchronization primitives. Mutex is much slower than Monitor (or lock).

Really if you don't trust the author of the code enough to have them implement cooperative cancellation then I'd question the sanity of having them run inside your AppDomain on the same thread.

For further detail see:

http://msdn.microsoft.com/en-us/library/dd997364.aspx

http://msdn.microsoft.com/en-us/library/dd537607.aspx

http://msdn.microsoft.com/en-us/library/ee191552.aspx

like image 84
Ade Miller Avatar answered Nov 13 '22 16:11

Ade Miller