Skip to content
This repository has been archived by the owner on Jan 23, 2023. It is now read-only.

Add ExceptionDispatchInfo.SetCurrentStackTrace #27004

Merged
merged 2 commits into from
Oct 4, 2019

Conversation

stephentoub
Copy link
Member

@stephentoub stephentoub commented Oct 3, 2019

Contributes to https://github.com/dotnet/corefx/issues/19416

Two open questions from me as I was doing this tonight:

  1. Based on all of the places I ended up using this in corefx in Expose, test, and use ExceptionDispatchInfo.SetCurrentStackTrace corefx#41514, it made the code much simpler to have SetCurrentStackTrace return the passed in instance. @terrajobst, @bartonjs, any concerns with that from an API design perspective?
  2. I currently have the method checking/throwing if the Exception was previously thrown or if SetCurrentStackTrace was previously used, but I went back and forth on whether that was a good idea, and could be convinced otherwise. Opinions?

cc: @jkotas

Here's an example of how this manifest. This C# program:

using System;
using System.Diagnostics;
using System.Net;
using System.Net.Http;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;

class Program
{
    public static async Task Main()
    {
        using Socket listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        listener.Bind(new IPEndPoint(IPAddress.Loopback, 0));
        listener.Listen(1);

        using Socket tmp = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        tmp.Connect(listener.LocalEndPoint);

        using var c = new HttpClient();
        var cts = new CancellationTokenSource();
        Task<HttpResponseMessage> t = c.GetAsync("http://localhost:" + ((IPEndPoint)listener.LocalEndPoint).Port, cts.Token);
        await Task.Delay(1);
        cts.Cancel();
        await t;
    }
}

On .NET Core 3.0, it outputs this:

Unhandled exception. System.Threading.Tasks.TaskCanceledException: The operation was canceled.
   at System.Net.Http.ConnectHelper.ConnectAsync(String host, Int32 port, CancellationToken cancellationToken)
   at System.Net.Http.HttpConnectionPool.ConnectAsync(HttpRequestMessage request, Boolean allowHttp2, CancellationToken cancellationToken)
   at System.Net.Http.HttpConnectionPool.CreateHttp11ConnectionAsync(HttpRequestMessage request, CancellationToken cancellationToken)
   at System.Net.Http.HttpConnectionPool.GetHttpConnectionAsync(HttpRequestMessage request, CancellationToken cancellationToken)
   at System.Net.Http.HttpConnectionPool.SendWithRetryAsync(HttpRequestMessage request, Boolean doRequestAuth, CancellationToken cancellationToken)
   at System.Net.Http.RedirectHandler.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
   at System.Net.Http.HttpClient.FinishSendAsyncBuffered(Task`1 sendTask, HttpRequestMessage request, CancellationTokenSource cts, Boolean disposeCts)
   at Program.Main() in c:\Users\stoub\Desktop\tmpapp\Program.cs:line 25
   at Program.<Main>()

With this API and the corresponding changes in corefx, it now results in:

Unhandled exception. System.Threading.Tasks.TaskCanceledException: The operation was canceled.
   at System.Environment.get_StackTrace()
   at System.Net.Http.ConnectHelper.ConnectEventArgs.OnCompleted(SocketAsyncEventArgs _) in D:\repos\corefx\src\System.Net.Http\src\System\Net\Http\SocketsHttpHandler\ConnectHelper.cs:line 110
   at System.Net.Sockets.SocketAsyncEventArgs.ExecutionCallback(Object state) in D:\repos\corefx\src\System.Net.Sockets\src\System\Net\Sockets\SocketAsyncEventArgs.cs:line 423
   at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state)
   at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
   at System.Net.Sockets.SocketAsyncEventArgs.FinishConnectByNameAsyncFailure(Exception exception, Int32 bytesTransferred, SocketFlags flags) in D:\repos\corefx\src\System.Net.Sockets\src\System\Net\Sockets\SocketAsyncEventArgs.cs:line 648
   at System.Net.Sockets.MultipleConnectAsync.AsyncFail(Exception e) in D:\repos\corefx\src\System.Net.Sockets\src\System\Net\Sockets\MultipleConnectAsync.cs:line 338
   at System.Net.Sockets.MultipleConnectAsync.InternalConnectCallback(Object sender, SocketAsyncEventArgs args) in D:\repos\corefx\src\System.Net.Sockets\src\System\Net\Sockets\MultipleConnectAsync.cs:line 223
   at System.Net.Sockets.SocketAsyncEventArgs.OnCompleted(SocketAsyncEventArgs e) in D:\repos\corefx\src\System.Net.Sockets\src\System\Net\Sockets\SocketAsyncEventArgs.cs:line 203
   at System.Net.Sockets.SocketAsyncEventArgs.ExecutionCallback(Object state) in D:\repos\corefx\src\System.Net.Sockets\src\System\Net\Sockets\SocketAsyncEventArgs.cs:line 423
   at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state)
   at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
   at System.Net.Sockets.SocketAsyncEventArgs.FinishOperationAsyncFailure(SocketError socketError, Int32 bytesTransferred, SocketFlags flags) in D:\repos\corefx\src\System.Net.Sockets\src\System\Net\Sockets\SocketAsyncEventArgs.cs:line 632
   at System.Net.Sockets.SocketAsyncEventArgs.HandleCompletionPortCallbackError(UInt32 errorCode, UInt32 numBytes, NativeOverlapped* nativeOverlapped) in D:\repos\corefx\src\System.Net.Sockets\src\System\Net\Sockets\SocketAsyncEventArgs.Windows.cs:line 1305
   at System.Net.Sockets.SocketAsyncEventArgs.<>c.<.cctor>b__177_0(UInt32 errorCode, UInt32 numBytes, NativeOverlapped* nativeOverlapped) in D:\repos\corefx\src\System.Net.Sockets\src\System\Net\Sockets\SocketAsyncEventArgs.Windows.cs:line 1267
   at System.Threading.ThreadPoolBoundHandleOverlapped.CompletionCallback(UInt32 errorCode, UInt32 numBytes, NativeOverlapped* nativeOverlapped)
   at System.Threading._IOCompletionCallback.PerformIOCompletionCallback(UInt32 errorCode, UInt32 numBytes, NativeOverlapped* pNativeOverlapped)
--- End of stack trace from previous location ---
   at System.Net.Http.ConnectHelper.ConnectAsync(String host, Int32 port, CancellationToken cancellationToken) in D:\repos\corefx\src\System.Net.Http\src\System\Net\Http\SocketsHttpHandler\ConnectHelper.cs:line 55
   at System.Net.Http.HttpConnectionPool.ConnectAsync(HttpRequestMessage request, Boolean allowHttp2, CancellationToken cancellationToken) in D:\repos\corefx\src\System.Net.Http\src\System\Net\Http\SocketsHttpHandler\HttpConnectionPool.cs:line 625
   at System.Net.Http.HttpConnectionPool.CreateHttp11ConnectionAsync(HttpRequestMessage request, CancellationToken cancellationToken) in D:\repos\corefx\src\System.Net.Http\src\System\Net\Http\SocketsHttpHandler\HttpConnectionPool.cs:line 665
   at System.Net.Http.HttpConnectionPool.GetHttpConnectionAsync(HttpRequestMessage request, CancellationToken cancellationToken) in D:\repos\corefx\src\System.Net.Http\src\System\Net\Http\SocketsHttpHandler\HttpConnectionPool.cs:line 331
   at System.Net.Http.HttpConnectionPool.SendWithRetryAsync(HttpRequestMessage request, Boolean doRequestAuth, CancellationToken cancellationToken) in D:\repos\corefx\src\System.Net.Http\src\System\Net\Http\SocketsHttpHandler\HttpConnectionPool.cs:line 523
   at System.Net.Http.RedirectHandler.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) in D:\repos\corefx\src\System.Net.Http\src\System\Net\Http\SocketsHttpHandler\RedirectHandler.cs:line 33
   at System.Net.Http.HttpClient.FinishSendAsyncBuffered(Task`1 sendTask, HttpRequestMessage request, CancellationTokenSource cts, Boolean disposeCts) in D:\repos\corefx\src\System.Net.Http\src\System\Net\Http\HttpClient.cs:line 521
   at Program.Main() in d:\CoreClrTest\test.cs:line 25
   at Program.<Main>()

which provides a lot more details about why this happened.

@stephentoub
Copy link
Member Author

FYI, @benaadams, since I know you're interested in stack traces for async stuff.

@jkotas
Copy link
Member

jkotas commented Oct 3, 2019

method checking/throwing if the Exception was previously thrown or if SetCurrentStackTrace was previously used

We can always relax it later if we find that there are cases where this is called on exceptions that have stacktrace already. I think what's in the PR is fine.

Copy link
Member

@danmoseley danmoseley left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I hope we can avoid get_StackTrace showing up.

@benaadams
Copy link
Member

benaadams commented Oct 3, 2019

Aside: I also see ExecutionContext.Run in the stack trace; is this at Tier0?

I would hope that always inlined at R2R & Tier1 as its essentially just a null check; which could be elided in many cases. For example FinishOperationAsyncFailure is the following

if (context == null)
{
    OnCompleted(this);
}
else
{
    ExecutionContext.Run(context, s_executionCallback, this);
}

So the context has already been tested for null prior to calling EC.Run and the test and throw would hopefully be branch eliminated if it inlines.

@stephentoub
Copy link
Member Author

I also see ExecutionContext.Run in the stack trace; is this at Tier0

Yes, this code was only executed once, so it wouldn't have been promoted. If I disable tiering, the stack becomes:

Unhandled exception. System.Threading.Tasks.TaskCanceledException: The operation was canceled.
   at System.Environment.get_StackTrace()
   at System.Net.Http.ConnectHelper.ConnectEventArgs.OnCompleted(SocketAsyncEventArgs _) in D:\repos\corefx\src\System.Net.Http\src\System\Net\Http\SocketsHttpHandler\ConnectHelper.cs:line 110
   at System.Net.Sockets.SocketAsyncEventArgs.ExecutionCallback(Object state) in D:\repos\corefx\src\System.Net.Sockets\src\System\Net\Sockets\SocketAsyncEventArgs.cs:line 423
   at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state)
   at System.Net.Sockets.SocketAsyncEventArgs.FinishConnectByNameAsyncFailure(Exception exception, Int32 bytesTransferred, SocketFlags flags) in D:\repos\corefx\src\System.Net.Sockets\src\System\Net\Sockets\SocketAsyncEventArgs.cs:line 648
   at System.Net.Sockets.MultipleConnectAsync.AsyncFail(Exception e) in D:\repos\corefx\src\System.Net.Sockets\src\System\Net\Sockets\MultipleConnectAsync.cs:line 338
   at System.Net.Sockets.MultipleConnectAsync.InternalConnectCallback(Object sender, SocketAsyncEventArgs args) in D:\repos\corefx\src\System.Net.Sockets\src\System\Net\Sockets\MultipleConnectAsync.cs:line 223
   at System.Net.Sockets.SocketAsyncEventArgs.OnCompleted(SocketAsyncEventArgs e) in D:\repos\corefx\src\System.Net.Sockets\src\System\Net\Sockets\SocketAsyncEventArgs.cs:line 203
   at System.Net.Sockets.SocketAsyncEventArgs.ExecutionCallback(Object state) in D:\repos\corefx\src\System.Net.Sockets\src\System\Net\Sockets\SocketAsyncEventArgs.cs:line 423
   at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state)
   at System.Net.Sockets.SocketAsyncEventArgs.FinishOperationAsyncFailure(SocketError socketError, Int32 bytesTransferred, SocketFlags flags) in D:\repos\corefx\src\System.Net.Sockets\src\System\Net\Sockets\SocketAsyncEventArgs.cs:line 632
   at System.Net.Sockets.SocketAsyncEventArgs.HandleCompletionPortCallbackError(UInt32 errorCode, UInt32 numBytes, NativeOverlapped* nativeOverlapped) in D:\repos\corefx\src\System.Net.Sockets\src\System\Net\Sockets\SocketAsyncEventArgs.Windows.cs:line 1305
   at System.Net.Sockets.SocketAsyncEventArgs.<>c.<.cctor>b__177_0(UInt32 errorCode, UInt32 numBytes, NativeOverlapped* nativeOverlapped) in D:\repos\corefx\src\System.Net.Sockets\src\System\Net\Sockets\SocketAsyncEventArgs.Windows.cs:line 1267
   at System.Threading._IOCompletionCallback.PerformIOCompletionCallback(UInt32 errorCode, UInt32 numBytes, NativeOverlapped* pNativeOverlapped)
--- End of stack trace from previous location ---
   at System.Net.Http.ConnectHelper.ConnectAsync(String host, Int32 port, CancellationToken cancellationToken) in D:\repos\corefx\src\System.Net.Http\src\System\Net\Http\SocketsHttpHandler\ConnectHelper.cs:line 55
   at System.Net.Http.HttpConnectionPool.ConnectAsync(HttpRequestMessage request, Boolean allowHttp2, CancellationToken cancellationToken) in D:\repos\corefx\src\System.Net.Http\src\System\Net\Http\SocketsHttpHandler\HttpConnectionPool.cs:line 625
   at System.Net.Http.HttpConnectionPool.CreateHttp11ConnectionAsync(HttpRequestMessage request, CancellationToken cancellationToken) in D:\repos\corefx\src\System.Net.Http\src\System\Net\Http\SocketsHttpHandler\HttpConnectionPool.cs:line 665
   at System.Net.Http.HttpConnectionPool.GetHttpConnectionAsync(HttpRequestMessage request, CancellationToken cancellationToken) in D:\repos\corefx\src\System.Net.Http\src\System\Net\Http\SocketsHttpHandler\HttpConnectionPool.cs:line 331
   at System.Net.Http.HttpConnectionPool.SendWithRetryAsync(HttpRequestMessage request, Boolean doRequestAuth, CancellationToken cancellationToken) in D:\repos\corefx\src\System.Net.Http\src\System\Net\Http\SocketsHttpHandler\HttpConnectionPool.cs:line 523
   at System.Net.Http.RedirectHandler.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) in D:\repos\corefx\src\System.Net.Http\src\System\Net\Http\SocketsHttpHandler\RedirectHandler.cs:line 33
   at System.Net.Http.HttpClient.FinishSendAsyncBuffered(Task`1 sendTask, HttpRequestMessage request, CancellationTokenSource cts, Boolean disposeCts) in D:\repos\corefx\src\System.Net.Http\src\System\Net\Http\HttpClient.cs:line 521
   at Program.Main() in d:\CoreClrTest\test.cs:line 25
   at Program.<Main>()

@stephentoub stephentoub force-pushed the setcurrentstacktrace branch 2 times, most recently from c7a54a5 to d4a9114 Compare October 3, 2019 14:57
/// true to throw if the exception already has a stack; false to nop if the exception already has a stack.
/// </param>
[StackTraceHidden]
internal void SetCurrentStackTrace(bool throwIfHasExistingStack) => SetCurrentStackTraceCore(throwIfHasExistingStack);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do not think we need this wrapper. internal void SetCurrentStackTrace(...) can be directly in Exception.CoreCLR.cs.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

partial methods have to be private. What would you recommend instead?

Copy link
Member

@jkotas jkotas Oct 3, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Delete SetCurrentStackTrace from here. Rename SetCurrentStackTraceCore to SetCurrentStackTrace in Exception.CoreCLR.cs.

Copy link
Member Author

@stephentoub stephentoub Oct 3, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I'm not following. SetCurrentStackTraceCore is partial; it has to be private. And as private, it can't be called from ExceptionDispatchInfo. If I make it not partial, then I can't specialize it for coreclr and would instead need another method in Exception.CoreCLR.cs it can call, along with an equivalent method in other partials in other runtimes. If I leave it in Exception.CoreCLR.cs as non-partial, then I'll similarly need to add that same method to other partial files in other runtimes. Am I misunderstanding? Or you're suggesting we do the latter, adding that same method?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll similarly need to add that same method to other partial files in other runtimes

Yes, I think other runtimes should implement this method as well (it is ok to have a empty impl with TODO).

@jkotas
Copy link
Member

jkotas commented Oct 3, 2019

Conflicts...

@stephentoub stephentoub merged commit 3bdc9f6 into dotnet:master Oct 4, 2019
@stephentoub stephentoub deleted the setcurrentstacktrace branch October 4, 2019 01:59
Dotnet-GitSync-Bot pushed a commit to Dotnet-GitSync-Bot/corefx that referenced this pull request Oct 4, 2019
* Add ExceptionDispatchInfo.SetCurrentStackTrace

* Address PR feedback

Signed-off-by: dotnet-bot <dotnet-bot@microsoft.com>
Dotnet-GitSync-Bot pushed a commit to Dotnet-GitSync-Bot/corert that referenced this pull request Oct 4, 2019
* Add ExceptionDispatchInfo.SetCurrentStackTrace

* Address PR feedback

Signed-off-by: dotnet-bot <dotnet-bot@microsoft.com>
Dotnet-GitSync-Bot pushed a commit to Dotnet-GitSync-Bot/mono that referenced this pull request Oct 4, 2019
* Add ExceptionDispatchInfo.SetCurrentStackTrace

* Address PR feedback

Signed-off-by: dotnet-bot <dotnet-bot@microsoft.com>
stephentoub added a commit to dotnet/corefx that referenced this pull request Oct 4, 2019
* Add ExceptionDispatchInfo.SetCurrentStackTrace

* Address PR feedback

Signed-off-by: dotnet-bot <dotnet-bot@microsoft.com>
jkotas pushed a commit to dotnet/corert that referenced this pull request Oct 5, 2019
* Add ExceptionDispatchInfo.SetCurrentStackTrace

* Address PR feedback

Signed-off-by: dotnet-bot <dotnet-bot@microsoft.com>
marek-safar pushed a commit to mono/mono that referenced this pull request Oct 5, 2019
* Add ExceptionDispatchInfo.SetCurrentStackTrace

* Address PR feedback

Signed-off-by: dotnet-bot <dotnet-bot@microsoft.com>
SrivastavaAnubhav pushed a commit to SrivastavaAnubhav/coreclr that referenced this pull request Oct 7, 2019
* Add ExceptionDispatchInfo.SetCurrentStackTrace

* Address PR feedback
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants