-
Notifications
You must be signed in to change notification settings - Fork 3.8k
Closed
Description
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
- Compile and run the attached example (note that there's a copy of
TaskToApmhere, copied from ourinternalimplementation 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
kdubau