Remove Edi boundary for async#15781
Conversation
c8b30c3 to
02a5eed
Compare
|
@jkotas PTAL |
| } | ||
| } | ||
|
|
||
| public virtual MethodBase GetMethodBase(int i) |
There was a problem hiding this comment.
No point in virtuals on non-inherited internal class
|
It use the same method to detect async as the open PR #14655 which seems to be approved approach by C# team #14655 (comment)? |
|
@dotnet-bot test cross please |
|
OSX Failure |
|
@dotnet-bot test OSX10.12 x64 Checked Innerloop Build and Test |
|
|
||
| namespace System.Diagnostics | ||
| { | ||
| // There is no good reason for the methods of this class to be virtual. |
There was a problem hiding this comment.
I think we should split the cleanup off to its own PR to make sure it doesn't get squashed together with the other changes during checkin. It helps to avoid having a few important changes hidden amongst numerous refactorings.
There was a problem hiding this comment.
Simplified just to this change
| sb.Append(Environment.NewLine); | ||
| sb.Append(SR.Exception_EndStackTraceFromPreviousThrow); | ||
| if (sf.GetIsLastFrameFromForeignExceptionStackTrace() && | ||
| !(declaringType?.Assembly.FullName.StartsWith(CoreLib.Name, StringComparison.Ordinal) ?? false) && // Skip EDI boundary for mscorlib |
There was a problem hiding this comment.
Doing a direct comparison on the assembly object seems likely to be faster and more robust. Something like
static Assembly CorelibAssembly = typeof(object).Assembly?
| if (declaringType != null && declaringType.IsDefined(typeof(CompilerGeneratedAttribute)) && | ||
| (typeof(IAsyncStateMachine).IsAssignableFrom(declaringType))) | ||
| { | ||
| isAsync = true; |
There was a problem hiding this comment.
Could you add some perf numbers? I had a vague memory you already benchmarked and it was barely noticeable but just want to confirm. Some customers use stack traces on their hot path and get very sad when we slow them down (microsoft/dotnet#529)
In this case isAsync only needs to be calculated once we determine IsLastFrameFromForeignException is true, but I know if your other use case you need to check it for every frame anyways.
There was a problem hiding this comment.
Hmm... I tested against throwing the exception and it was insignificant - but I didn't look at just creating a StackTrace - however this is in the .ToString path so wouldn't impact the linked example.
Never the less, will check
02a5eed to
a1e1b8a
Compare
| // There is no good reason for the methods of this class to be virtual. | ||
| public class StackTrace | ||
| { | ||
| private static Assembly s_corelibAssembly = typeof(object).Assembly; |
| typeof(IAsyncStateMachine).IsAssignableFrom(declaringType)) | ||
| { | ||
| isAsync = true; | ||
| } |
There was a problem hiding this comment.
bool isAsync = declaringType != null && ...;
?
|
|
||
| if (sf.GetIsLastFrameFromForeignExceptionStackTrace()) | ||
| if (sf.GetIsLastFrameFromForeignExceptionStackTrace() && | ||
| declaringType?.Assembly != s_corelibAssembly && // Skip EDI boundary for mscorlib |
There was a problem hiding this comment.
Why is this disabled for all of corelib? There aren't many places in corelib where EDI.Capture is used, but there are a few, and I'm wondering if this may result in a worse experience for some of them.
For example, exceptions that occur in Lazy<T>.Value are propagated but also cached via EDI for subsequent access. Thus with a program like:
using System;
class Program
{
static void Main()
{
var l = new Lazy<int>(() => { throw new Exception("hello"); });
try { int i = l.Value; } catch (Exception e) { Console.WriteLine(e); }
Console.WriteLine();
try { int i = l.Value; } catch (Exception e) { Console.WriteLine(e); }
}
}currently this will output:
System.Exception: hello
at Program.<>c.<Main>b__0_0() in c:\Users\stoub\Desktop\CoreClrTest\test.cs:line 8
at System.Lazy`1.ViaFactory(LazyThreadSafetyMode mode) in E:\A\_work\1522\s\src\mscorlib\shared\System\Lazy.cs:line 330
at System.Lazy`1.ExecutionAndPublication(LazyHelper executionAndPublication, Boolean useDefaultConstructor) in E:\A\_work\1522\s\src\mscorlib\shared\System\Lazy.cs:line 348
at System.Lazy`1.CreateValue() in E:\A\_work\1522\s\src\mscorlib\shared\System\Lazy.cs:line 436
at Program.Main() in c:\Users\stoub\Desktop\CoreClrTest\test.cs:line 9
System.Exception: hello
at Program.<>c.<Main>b__0_0() in c:\Users\stoub\Desktop\CoreClrTest\test.cs:line 8
at System.Lazy`1.ViaFactory(LazyThreadSafetyMode mode) in E:\A\_work\1522\s\src\mscorlib\shared\System\Lazy.cs:line 324
--- End of stack trace from previous location where exception was thrown ---
at System.Lazy`1.CreateValue() in E:\A\_work\1522\s\src\mscorlib\shared\System\Lazy.cs:line 432
at Program.Main() in c:\Users\stoub\Desktop\CoreClrTest\test.cs:line 11
but with this change that --- End of stack trace... won't show, which might suggest to the caller that the exception is actually occurring as part of invoking the delegate on this call, rather than it coming from being cached.
Or consider:
using System;
using System.Threading.Tasks;
class Program
{
static void Main()
{
var t = Task.Run(() => { throw new Exception("hello"); });
t.GetAwaiter().GetResult();
}
}Today this will output:
Unhandled Exception: System.Exception: hello
at Program.<>c.<Main>b__0_0() in c:\Users\stoub\Desktop\CoreClrTest\test.cs:line 8
at System.Threading.Tasks.Task`1.InnerInvoke() in E:\A\_work\1522\s\src\mscorlib\src\System\Threading\Tasks\future.cs:line 610
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state) in E:\A\_work\1522\s\src\mscorlib\shared\System\Threading\ExecutionContext.cs:line 151
at System.Threading.Tasks.Task.ExecuteWithThreadLocal(Task& currentTaskSlot) in E:\A\_work\1522\s\src\mscorlib\src\System\Threading\Tasks\Task.cs:line 2440
--- End of stack trace from previous location where exception was thrown ---
at Program.Main() in c:\Users\stoub\Desktop\CoreClrTest\test.cs:line 10
That helps to highlight that Wait isn't actually calling ExecuteWithThreadLocal, but if you remove the --- End of stack trace... part, how does the developer know that?
There was a problem hiding this comment.
😞
Will have to narrow further
There was a problem hiding this comment.
Perhaps attribute on method is way forward?
There was a problem hiding this comment.
attribute seems reasonable, and matches the pattern we used for StackTraceHidden handling
There was a problem hiding this comment.
Just switched it off for async; can address other areas if/when they arise
a1e1b8a to
ded78a3
Compare
|
Changed to just off for async; can address other areas if/when they arise |
|
Tizen |
|
Ubuntu arm Cross Debug Innerloop Build |
|
@dotnet-bot test Tizen armel Cross Checked Innerloop Build and Test |
|
I'm happy if Stephen is. Thanks! |
|
@benaadams, can you share a before/after with this change? |
|
Pre Post |
|
Test class Program
{
static async Task Main(string[] args)
{
try
{
await MethodAsync();
}
catch (Exception e)
{
Console.WriteLine(e);
}
}
static async Task MethodAsync()
{
await RecursiveAsync(5);
}
static async Task RecursiveAsync(int count)
{
if (count == 0)
{
throw new Exception();
}
await RecursiveAsync(count - 1);
}
} |
|
Ok, thanks, looks ok to me. |
|
netfx-port-consider? |
Switch off EDI boundary for async
Removes:
Resolves https://github.com/dotnet/coreclr/issues/15779