Skip to content

Commit eae21db

Browse files
committed
Massively improve stacktraces after a yield
1 parent d59bc29 commit eae21db

File tree

2 files changed

+24
-9
lines changed

2 files changed

+24
-9
lines changed

Ply.fs

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -56,9 +56,12 @@ module TplPrimitives =
5656

5757
let forceThrowEx = "|PlyForceThrowEx|"
5858

59-
type [<Struct>]ContinuationStateMachine<'u> =
60-
#if NETSTANDARD2_0
61-
val Builder : AsyncTaskMethodBuilder<'u>
59+
type TplResult<'t> = Result<'t, ExceptionDispatchInfo>
60+
61+
// https://github.com/dotnet/coreclr/pull/15781/files
62+
type [<Struct;CompilerGenerated>]ContinuationStateMachine<'u> =
63+
#if NETSTANDARD2_0
64+
val Builder : AsyncTaskMethodBuilder<'u>
6265
#else
6366
val Builder : AsyncValueTaskMethodBuilder<'u>
6467
#endif
@@ -127,7 +130,7 @@ module TplPrimitives =
127130

128131
val private awaiterMethods: 'methods
129132
val mutable private awaiter: 'awt
130-
val private continuation: 't -> Ply<'u>
133+
val private continuation: TplResult<'t> -> Ply<'u>
131134

132135
new(awaiterMethods, awaiter, continuation) = {
133136
awaiterMethods = awaiterMethods
@@ -144,9 +147,16 @@ module TplPrimitives =
144147

145148
override this.GetNext() =
146149
Debug.Assert(this.awaiterMethods.IsCompleted &this.awaiter || (typeof<'awt> = typeof<YieldAwaitable.YieldAwaiter>), "Forcing an async here")
147-
this.continuation (this.awaiterMethods.GetResult &this.awaiter)
148150

149-
and [<Sealed>] PlyAwaitable<'t, 'u> (awaitable: Awaitable<'t>, continuation: 't -> Ply<'u>) =
151+
let result =
152+
try
153+
Ok(this.awaiterMethods.GetResult &this.awaiter)
154+
with ex ->
155+
Error(ExceptionDispatchInfo.Capture(ex))
156+
157+
this.continuation result
158+
159+
and [<Sealed>] PlyAwaitable<'t, 'u> (awaitable: Awaitable<'t>, continuation: 't -> Ply<'u>) =
150160
inherit Awaitable<'u>()
151161
let mutable awaitable = awaitable
152162

@@ -404,8 +414,12 @@ module TplPrimitives =
404414
// Secondly, for every GetResult — because all calls to bind overloads are wrapped by TaskBuilder.Run — we are
405415
// already running within our own Excecution context bubble. No need to be careful calling GetResult.
406416

417+
// Await exists for binary compatibility.
418+
static member Await<'methods, 'awt, 't when 'methods :> IAwaiterMethods<'awt, 't>>(awt: byref<'awt>, cont: 't -> Ply<'u>) =
419+
Ply(await = TplAwaitable(defaultof<'methods>, awt, fun r -> match r with Ok t -> cont t | Error e -> e.Throw(); defaultof<_>))
420+
407421
// We keep Await non inline to protect internals to maximize binary compatibility.
408-
static member Await<'methods, 'awt, 't when 'methods :> IAwaiterMethods<'awt, 't>>(awt: byref<'awt>, cont: 't -> Ply<'u>) =
422+
static member AwaitResult<'methods, 'awt, 't when 'methods :> IAwaiterMethods<'awt, 't>>(awt: byref<'awt>, cont: TplResult<'t> -> Ply<'u>) =
409423
Ply(await = TplAwaitable(defaultof<'methods>, awt, cont))
410424

411425
static member inline Specialized<'methods, ^awt, 't
@@ -418,7 +432,8 @@ module TplPrimitives =
418432
cont (^awt : (member GetResult: unit -> 't) (awt))
419433
else
420434
let mutable mutAwt = awt
421-
Binder<'u>.Await<'methods,_,_>(&mutAwt, (fun x -> cont x))
435+
// Having the edi.Throw here means user stack frames will get captured, as this code will get inlined into cont.
436+
Binder<'u>.AwaitResult<'methods,_,_>(&mutAwt, (fun r -> match r with Ok t -> cont t | Error e -> e.Throw(); defaultof<_>))
422437

423438
// We have special treatment for unknown taskLike types where we wrap the continuation in a unit func
424439
// This allows us to use a single GenericAwaiterMethods type (zero alloc, small drop in perf) instead of an object expression.

global.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"sdk": {
3-
"rollForward": "minor",
3+
"rollForward": "feature",
44
"version": "3.1.100"
55
}
66
}

0 commit comments

Comments
 (0)