Thanks to visit codestin.com
Credit goes to github.com

Skip to content

[threading] Missing frames if TaskToApm Begin callback throws #19166

@lambdageek

Description

@lambdageek

In code that uses the TaskToApm adapter where internally async Tasks are used to implement some functionality but provide a callback-based Begin/End asynchronous method programming model, if the Begin callback throws, we lose all the information about the original location of the throw.

Steps to Reproduce

  1. Compile and run the attached example (note that there's a copy of TaskToApm here, copied from our internal implementation in our fork of corefx)
using System;
using System.Threading.Tasks;
using System.IO.Pipes;
using System.Threading;
using System.Runtime.ExceptionServices;
using System.Runtime.CompilerServices;

// Copy of external/corefx/src/Common/src/System/Threading/Tasks/TaskToApm.cs
namespace BugReport.System.Threading.Tasks
{
    public static class TaskToApm
    {
        public static IAsyncResult Begin(Task task, AsyncCallback callback, object state)
        {

            // If the task has already completed, then since the Task's CompletedSynchronously==false
            // and we want it to be true, we need to create a new IAsyncResult. (We also need the AsyncState to match.)
            IAsyncResult asyncResult;
            if (task.IsCompleted)
            {
                // Synchronous completion.
                asyncResult = new TaskWrapperAsyncResult(task, state, completedSynchronously: true);
                callback?.Invoke(asyncResult);
            }
            else
            {
                // For asynchronous completion we need to schedule a callback.  Whether we can use the Task as the IAsyncResult
                // depends on whether the Task's AsyncState has reference equality with the requested state.
                asyncResult = task.AsyncState == state ? (IAsyncResult)task : new TaskWrapperAsyncResult(task, state, completedSynchronously: false);
                if (callback != null)
                {
                    InvokeCallbackWhenTaskCompletes(task, callback, asyncResult);
                }
            }
            return asyncResult;
        }

        public static void End(IAsyncResult asyncResult)
        {
            Task task;

            // If the IAsyncResult is our task-wrapping IAsyncResult, extract the Task.
            var twar = asyncResult as TaskWrapperAsyncResult;
            if (twar != null)
            {
                task = twar.Task;
            }
            else
            {
                // Otherwise, the IAsyncResult should be a Task.
                task = asyncResult as Task;
            }

            // Make sure we actually got a task, then complete the operation by waiting on it.
            if (task == null)
            {
                throw new ArgumentNullException();
            }

            task.GetAwaiter().GetResult();
        }

        public static TResult End<TResult>(IAsyncResult asyncResult)
        {
            Task<TResult> task;

            // If the IAsyncResult is our task-wrapping IAsyncResult, extract the Task.
            var twar = asyncResult as TaskWrapperAsyncResult;
            if (twar != null)
            {
                task = twar.Task as Task<TResult>;
            }
            else
            {
                // Otherwise, the IAsyncResult should be a Task<TResult>.
                task = asyncResult as Task<TResult>;
            }

            // Make sure we actually got a task, then complete the operation by waiting on it.
            if (task == null)
            {
                throw new ArgumentNullException();
            }

            return task.GetAwaiter().GetResult();
        }

        private static void InvokeCallbackWhenTaskCompletes(Task antecedent, AsyncCallback callback, IAsyncResult asyncResult)
        {

            // We use OnCompleted rather than ContinueWith in order to avoid running synchronously
            // if the task has already completed by the time we get here.  This is separated out into
            // its own method currently so that we only pay for the closure if necessary.
            antecedent.ConfigureAwait(continueOnCapturedContext: false)
                      .GetAwaiter()
                      .OnCompleted(() => callback(asyncResult));
        }

        private sealed class TaskWrapperAsyncResult : IAsyncResult
        {
            internal readonly Task Task;
            private readonly object _state;
            private readonly bool _completedSynchronously;

            internal TaskWrapperAsyncResult(Task task, object state, bool completedSynchronously)
            {
                this.Task = task;
                _state = state;
                _completedSynchronously = completedSynchronously;
            }

            object IAsyncResult.AsyncState { get { return _state; } }
            bool IAsyncResult.CompletedSynchronously { get { return _completedSynchronously; } }
            bool IAsyncResult.IsCompleted { get { return this.Task.IsCompleted; } }
            WaitHandle IAsyncResult.AsyncWaitHandle { get { return ((IAsyncResult)this.Task).AsyncWaitHandle; } }
        }
    }
}

namespace MainProgram
{
	class Program
	{
		static void Main(string[] args)
		{
			RunServer();
			Thread.Sleep (500);
		}

		static void RunServer ()
		{
			IAsyncResult fooResult = BugReport.System.Threading.Tasks.TaskToApm.Begin (Foo (), TheCallback, "abcd");

			Console.WriteLine ("ran Foo");
			int i = BugReport.System.Threading.Tasks.TaskToApm.End<int> (fooResult);
		}

		static async Task<int> Foo () {
			await Task.Delay (1000);
			return 42;
		}

		[MethodImpl (MethodImplOptions.NoInlining)]
		private static void TheCallback (IAsyncResult res) {
			Console.WriteLine ("abcd = {0}, {1}", res, res.AsyncState);
			throw new ArgumentException ("Lets have an argument");
		}
	}
}

Current Behavior

Note that the top frames of the exception don't mention TheCallback

ran Foo
abcd = BugReport.System.Threading.Tasks.TaskToApm+TaskWrapperAsyncResult, abcd

Unhandled Exception:
System.ArgumentException: Lets have an argument
  at System.Threading.Tasks.AwaitTaskContinuation.RunOrScheduleAction (System.Action action, System.Boolean allowInlining, System.Threading.Tasks.Task& currentTask) [0x00022] in /Users/alklig/work/mono/external/corert/src/System.Private.CoreLib/src/System/Threading/Tasks/TaskContinuation.cs:680 
  at System.Threading.Tasks.Task.FinishContinuations () [0x00052] in /Users/alklig/work/mono/external/corert/src/System.Private.CoreLib/src/System/Threading/Tasks/Task.cs:3165 
  at System.Threading.Tasks.Task.FinishStageThree () [0x0003c] in /Users/alklig/work/mono/external/corert/src/System.Private.CoreLib/src/System/Threading/Tasks/Task.cs:2222 
  at System.Threading.Tasks.Task`1[TResult].TrySetResult (TResult result) [0x0004f] in /Users/alklig/work/mono/external/corert/src/System.Private.CoreLib/src/System/Threading/Tasks/Future.cs:422 
  at System.Threading.Tasks.Task+DelayPromise.Complete () [0x00034] in /Users/alklig/work/mono/external/corert/src/System.Private.CoreLib/src/System/Threading/Tasks/Task.cs:5349 
  at System.Threading.Tasks.Task+<>c.<Delay>b__247_1 (System.Object state) [0x00000] in /Users/alklig/work/mono/external/corert/src/System.Private.CoreLib/src/System/Threading/Tasks/Task.cs:5311 
  at System.Threading.Timer+Scheduler.TimerCB (System.Object o) [0x00007] in /Users/alklig/work/mono/mcs/class/corlib/System.Threading/Timer.cs:369 
  at System.Threading.QueueUserWorkItemCallback.System.Threading.IThreadPoolWorkItem.ExecuteWorkItem () [0x00015] in /Users/alklig/work/mono/mcs/class/referencesource/mscorlib/system/threading/threadpool.cs:1337 
  at System.Threading.ThreadPoolWorkQueue.Dispatch () [0x00074] in /Users/alklig/work/mono/mcs/class/referencesource/mscorlib/system/threading/threadpool.cs:899 
  at System.Threading._ThreadPoolWaitCallback.PerformWaitCallback () [0x00000] in /Users/alklig/work/mono/mcs/class/referencesource/mscorlib/system/threading/threadpool.cs:1261 
[ERROR] FATAL UNHANDLED EXCEPTION: System.ArgumentException: Lets have an argument
  at System.Threading.Tasks.AwaitTaskContinuation.RunOrScheduleAction (System.Action action, System.Boolean allowInlining, System.Threading.Tasks.Task& currentTask) [0x00022] in /Users/alklig/work/mono/external/corert/src/System.Private.CoreLib/src/System/Threading/Tasks/TaskContinuation.cs:680 
  at System.Threading.Tasks.Task.FinishContinuations () [0x00052] in /Users/alklig/work/mono/external/corert/src/System.Private.CoreLib/src/System/Threading/Tasks/Task.cs:3165 
  at System.Threading.Tasks.Task.FinishStageThree () [0x0003c] in /Users/alklig/work/mono/external/corert/src/System.Private.CoreLib/src/System/Threading/Tasks/Task.cs:2222 
  at System.Threading.Tasks.Task`1[TResult].TrySetResult (TResult result) [0x0004f] in /Users/alklig/work/mono/external/corert/src/System.Private.CoreLib/src/System/Threading/Tasks/Future.cs:422 
  at System.Threading.Tasks.Task+DelayPromise.Complete () [0x00034] in /Users/alklig/work/mono/external/corert/src/System.Private.CoreLib/src/System/Threading/Tasks/Task.cs:5349 
  at System.Threading.Tasks.Task+<>c.<Delay>b__247_1 (System.Object state) [0x00000] in /Users/alklig/work/mono/external/corert/src/System.Private.CoreLib/src/System/Threading/Tasks/Task.cs:5311 
  at System.Threading.Timer+Scheduler.TimerCB (System.Object o) [0x00007] in /Users/alklig/work/mono/mcs/class/corlib/System.Threading/Timer.cs:369 
  at System.Threading.QueueUserWorkItemCallback.System.Threading.IThreadPoolWorkItem.ExecuteWorkItem () [0x00015] in /Users/alklig/work/mono/mcs/class/referencesource/mscorlib/system/threading/threadpool.cs:1337 
  at System.Threading.ThreadPoolWorkQueue.Dispatch () [0x00074] in /Users/alklig/work/mono/mcs/class/referencesource/mscorlib/system/threading/threadpool.cs:899 
  at System.Threading._ThreadPoolWaitCallback.PerformWaitCallback () [0x00000] in /Users/alklig/work/mono/mcs/class/referencesource/mscorlib/system/threading/threadpool.cs:1261 

Expected Behavior

Unhandled exception includes info about the original throw site TheCallback

On which platforms did you notice this

[ ] macOS
[ ] Linux
[ ] Windows

Version Used:

Mono JIT compiler version 6.8.0.121 (2019-10/fb4d8f5eb1c Mon Feb 24 16:03:59 EST 2020)

Also observed on 2020-02 and recent master

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions