Skip to content

Commit 11e632e

Browse files
committed
pass exceptions correctly across trampoline bounces
1 parent b6ee38c commit 11e632e

File tree

2 files changed

+101
-63
lines changed

2 files changed

+101
-63
lines changed

src/Compiler/Utilities/Cancellable.fs

Lines changed: 94 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -67,12 +67,16 @@ open System.Diagnostics
6767
type ITrampolineInvocation =
6868
abstract member MoveNext: unit -> unit
6969
abstract IsCompleted: bool
70+
abstract ReplayExceptionIfStored: unit -> unit
7071

7172
[<Struct; NoComparison; NoEquality>]
7273
type CancellableStateMachineData<'T> =
7374

7475
[<DefaultValue(false)>]
75-
val mutable Result: Result<'T, ExceptionDispatchInfo>
76+
val mutable Result: 'T
77+
78+
[<DefaultValue(false)>]
79+
val mutable NextInvocation: ITrampolineInvocation voption
7680

7781
and CancellableStateMachine<'TOverall> = ResumableStateMachine<CancellableStateMachineData<'TOverall>>
7882
and ICancellableStateMachine<'TOverall> = IResumableStateMachine<CancellableStateMachineData<'TOverall>>
@@ -82,38 +86,50 @@ and CancellableCode<'TOverall, 'T> = ResumableCode<CancellableStateMachineData<'
8286

8387
[<Sealed>]
8488
type Trampoline(cancellationToken: CancellationToken) =
89+
8590
let mutable bindDepth = 0
8691

87-
let stack = System.Collections.Generic.Stack<ITrampolineInvocation>()
92+
[<Literal>]
93+
static let bindDepthLimit = 1000
8894

8995
static let current = new ThreadLocal<Trampoline>()
9096

97+
let delayed = System.Collections.Generic.Stack<ITrampolineInvocation>()
98+
9199
member this.IsCancelled = cancellationToken.IsCancellationRequested
92100

93101
member this.ThrowIfCancellationRequested() =
94102
cancellationToken.ThrowIfCancellationRequested()
95103

96-
member this.ShoudBounce = bindDepth % 100 = 0
104+
member this.ShoudBounce =
105+
bindDepth % bindDepthLimit = 0
97106

98107
static member Install ct = current.Value <- Trampoline ct
99108

100-
member _.Set(invocation) = stack.Push(invocation)
109+
member val LastError: ExceptionDispatchInfo voption = ValueNone with get, set
101110

102-
member val ExceptionMap = ConditionalWeakTable<exn, ExceptionDispatchInfo>()
111+
member this.RunDelayed(continuation, invocation) =
112+
// The calling state machine is now suspended. We need to resume it next.
113+
delayed.Push continuation
114+
// Schedule the delayed invocation to run.
115+
delayed.Push invocation
103116

104-
[<DebuggerHidden>]
105-
member this.Execute(invocation) =
117+
member this.RunImmediate(invocation: ITrampolineInvocation) =
106118
bindDepth <- bindDepth + 1
119+
try
120+
// This can throw, which is fine. We want the exception to propagate to the calling machine.
121+
invocation.MoveNext()
107122

108-
stack.Push invocation
109-
110-
while not invocation.IsCompleted do
111-
stack.Peek().MoveNext()
112-
113-
if stack.Peek().IsCompleted then
114-
stack.Pop() |> ignore
123+
while not invocation.IsCompleted do
124+
if delayed.Peek().IsCompleted then
125+
delayed.Pop() |> ignore
126+
else
127+
delayed.Peek().MoveNext()
128+
// In case this was a delayed invocation, which captures the exception, we need to replay it.
129+
invocation.ReplayExceptionIfStored()
130+
finally
131+
bindDepth <- bindDepth - 1
115132

116-
bindDepth <- bindDepth - 1
117133

118134
static member Current = current.Value
119135

@@ -124,45 +140,61 @@ type ITrampolineInvocation<'T> =
124140
[<AutoOpen>]
125141
module ExceptionDispatchInfoHelpers =
126142
type ExceptionDispatchInfo with
127-
member edi.ThrowAny() =
128-
edi.Throw()
129-
Unchecked.defaultof<_>
143+
member edi.ThrowAny() = edi.Throw(); Unchecked.defaultof<_>
130144

131145
static member RestoreOrCapture(exn: exn) =
132-
match Trampoline.Current.ExceptionMap.TryGetValue exn with
133-
| true, edi -> edi
146+
match Trampoline.Current.LastError with
147+
| ValueSome edi when edi.SourceException = exn -> edi
134148
| _ ->
135149
let edi = ExceptionDispatchInfo.Capture exn
136-
Trampoline.Current.ExceptionMap.Add(exn, edi)
150+
Trampoline.Current.LastError <- ValueSome edi
137151
edi
138152

139153
[<NoEquality; NoComparison>]
140154
type ICancellableInvokable<'T> =
141-
abstract Create: unit -> ITrampolineInvocation<'T>
155+
abstract Create: bool -> ITrampolineInvocation<'T>
142156

143157
[<NoEquality; NoComparison>]
144-
type CancellableInvocation<'T, 'Machine when 'Machine :> IAsyncStateMachine and 'Machine :> ICancellableStateMachine<'T>>(machine: 'Machine)
158+
type CancellableInvocation<'T, 'Machine when 'Machine :> IAsyncStateMachine and 'Machine :> ICancellableStateMachine<'T>>(machine: 'Machine, delayed: bool)
145159
=
146160
let mutable machine = machine
161+
let mutable storedException = ValueNone
162+
let mutable finished = false
147163

148-
interface ITrampolineInvocation<'T> with
149-
member this.MoveNext() = machine.MoveNext()
164+
new (machine) = CancellableInvocation(machine, false)
150165

151-
member _.Result =
152-
match machine.Data.Result with
153-
| Ok value -> value
154-
| Error edi -> edi.ThrowAny()
166+
interface ITrampolineInvocation<'T> with
167+
member this.MoveNext() =
168+
let pushDelayed () =
169+
match machine.Data.NextInvocation with
170+
| ValueSome delayed ->
171+
Trampoline.Current.RunDelayed(this, delayed)
172+
| _ -> finished <- true
173+
174+
if delayed then
175+
// If the invocation is delayed, we need to store the exception.
176+
try
177+
machine.MoveNext()
178+
pushDelayed ()
179+
with exn ->
180+
finished <- true
181+
storedException <- ValueSome <| ExceptionDispatchInfo.RestoreOrCapture exn
182+
else
183+
machine.MoveNext()
184+
pushDelayed ()
155185

156-
member _.IsCompleted = machine.ResumptionPoint = -1
186+
member _.Result = machine.Data.Result
187+
member _.IsCompleted = finished
188+
member _.ReplayExceptionIfStored () = storedException |> ValueOption.iter _.Throw()
157189

158190
interface ICancellableInvokable<'T> with
159-
member _.Create() = CancellableInvocation<_, _>(machine)
191+
member _.Create(delayed) = CancellableInvocation<_, _>(machine, delayed)
160192

161-
[<Struct; NoComparison; NoEquality>]
193+
[<Struct; NoComparison>]
162194
type Cancellable<'T>(invokable: ICancellableInvokable<'T>) =
163-
164-
member _.GetInvocation() = invokable.Create()
165-
195+
196+
member _.GetInvocation(delayed) = invokable.Create(delayed)
197+
166198
[<AutoOpen>]
167199
module CancellableCode =
168200

@@ -188,7 +220,7 @@ type CancellableBuilder() =
188220

189221
member inline _.Return(value: 'T) : CancellableCode<'T, 'T> =
190222
CancellableCode<'T, _>(fun sm ->
191-
sm.Data.Result <- Ok value
223+
sm.Data.Result <- value
192224
true)
193225

194226
member inline _.Combine
@@ -231,33 +263,44 @@ type CancellableBuilder() =
231263
: CancellableCode<'Data, 'T> =
232264
CancellableCode(fun sm ->
233265
if __useResumableCode then
234-
let mutable invocation = code.GetInvocation()
266+
let mutable invocation =
267+
code.GetInvocation Trampoline.Current.ShoudBounce
235268

236269
if Trampoline.Current.ShoudBounce then
270+
// Suspend this state machine and schedule both parts to run on the trampoline.
237271
match __resumableEntry () with
272+
// Suspending
238273
| Some contID ->
239274
sm.ResumptionPoint <- contID
240-
Trampoline.Current.Set invocation
275+
sm.Data.NextInvocation <- ValueSome invocation
241276
false
242-
| None -> (continuation invocation.Result).Invoke(&sm)
277+
// Resuming
278+
| None ->
279+
sm.Data.NextInvocation <- ValueNone
280+
// At this point we either have a result or an exception.
281+
invocation.ReplayExceptionIfStored()
282+
(continuation invocation.Result).Invoke(&sm)
243283
else
244-
Trampoline.Current.Execute invocation
284+
Trampoline.Current.RunImmediate invocation
245285
(continuation invocation.Result).Invoke(&sm)
246286

247287
else
248288
// Dynamic Bind.
249289

250-
let mutable invocation = code.GetInvocation()
290+
let mutable invocation = code.GetInvocation Trampoline.Current.ShoudBounce
251291

252292
if Trampoline.Current.ShoudBounce then
253293
let cont =
254-
CancellableResumptionFunc<'Data>(fun sm -> (continuation invocation.Result).Invoke(&sm))
294+
CancellableResumptionFunc<'Data>(fun sm ->
295+
sm.Data.NextInvocation <- ValueNone
296+
invocation.ReplayExceptionIfStored()
297+
(continuation invocation.Result).Invoke(&sm))
255298

256-
Trampoline.Current.Set invocation
299+
sm.Data.NextInvocation <- ValueSome invocation
257300
sm.ResumptionDynamicInfo.ResumptionFunc <- cont
258301
false
259302
else
260-
Trampoline.Current.Execute invocation
303+
Trampoline.Current.RunImmediate invocation
261304
(continuation invocation.Result).Invoke(&sm))
262305

263306
member inline this.ReturnFrom(comp: Cancellable<'T>) : CancellableCode<'T, 'T> = this.Bind(comp, this.Return)
@@ -268,15 +311,11 @@ type CancellableBuilder() =
268311

269312
(MoveNextMethodImpl<_>(fun sm ->
270313
__resumeAt sm.ResumptionPoint
314+
let __stack_code_fin = code.Invoke(&sm)
271315

272-
try
273-
let __stack_code_fin = code.Invoke(&sm)
274-
275-
if __stack_code_fin then
276-
sm.ResumptionPoint <- -1
277-
with exn ->
316+
if __stack_code_fin then
278317
sm.ResumptionPoint <- -1
279-
sm.Data.Result <- Error <| ExceptionDispatchInfo.RestoreOrCapture exn))
318+
))
280319

281320
(SetStateMachineMethodImpl<_>(fun _ _ -> ()))
282321

@@ -289,12 +328,8 @@ type CancellableBuilder() =
289328
let resumptionInfo =
290329
{ new CancellableResumptionDynamicInfo<_>(initialResumptionFunc) with
291330
member info.MoveNext(sm) =
292-
try
293-
if info.ResumptionFunc.Invoke(&sm) then
294-
sm.ResumptionPoint <- -1
295-
with exn ->
331+
if info.ResumptionFunc.Invoke(&sm) then
296332
sm.ResumptionPoint <- -1
297-
sm.Data.Result <- Error <| ExceptionDispatchInfo.RestoreOrCapture exn
298333

299334
member _.SetStateMachine(_, _) = ()
300335
}
@@ -303,6 +338,7 @@ type CancellableBuilder() =
303338

304339
Cancellable(CancellableInvocation(sm))
305340

341+
306342
namespace Internal.Utilities.Library
307343

308344
open System
@@ -321,9 +357,9 @@ module Cancellable =
321357
let run ct (code: Cancellable<_>) =
322358
use _ = FSharp.Compiler.Cancellable.UsingToken ct
323359

324-
let invocation = code.GetInvocation()
360+
let invocation = code.GetInvocation(false)
325361
Trampoline.Install ct
326-
Trampoline.Current.Execute invocation
362+
Trampoline.Current.RunImmediate invocation
327363
invocation
328364

329365
let runWithoutCancellation code =

src/Compiler/Utilities/Cancellable.fsi

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,12 @@ open System.Runtime.ExceptionServices
2222
type internal ITrampolineInvocation =
2323
abstract MoveNext: unit -> unit
2424
abstract IsCompleted: bool
25+
abstract ReplayExceptionIfStored: unit -> unit
2526

2627
[<Struct; NoComparison; NoEquality>]
2728
type internal CancellableStateMachineData<'T> =
28-
val mutable Result: Result<'T, ExceptionDispatchInfo>
29+
val mutable Result: 'T
30+
val mutable NextInvocation: ITrampolineInvocation voption
2931

3032
and internal CancellableStateMachine<'TOverall> = ResumableStateMachine<CancellableStateMachineData<'TOverall>>
3133
and internal ICancellableStateMachine<'TOverall> = IResumableStateMachine<CancellableStateMachineData<'TOverall>>
@@ -38,12 +40,12 @@ type internal ITrampolineInvocation<'T> =
3840
abstract Result: 'T
3941

4042
type internal ICancellableInvokable<'T> =
41-
abstract Create: unit -> ITrampolineInvocation<'T>
43+
abstract Create: bool -> ITrampolineInvocation<'T>
4244

4345
[<Sealed>]
4446
type internal Trampoline =
45-
member Set: ITrampolineInvocation -> unit
46-
member Execute: ITrampolineInvocation -> unit
47+
member RunDelayed: ITrampolineInvocation * ITrampolineInvocation -> unit
48+
member RunImmediate: ITrampolineInvocation -> unit
4749
static member Current: Trampoline
4850
member IsCancelled: bool
4951
member ThrowIfCancellationRequested: unit -> unit
@@ -59,7 +61,7 @@ type internal CancellableInvocation<'T, 'Machine
5961
[<Struct; NoComparison; NoEquality>]
6062
type internal Cancellable<'T> =
6163
new: invokable: ICancellableInvokable<'T> -> Cancellable<'T>
62-
member GetInvocation: unit -> ITrampolineInvocation<'T>
64+
member GetInvocation: bool -> ITrampolineInvocation<'T>
6365

6466
[<AutoOpen>]
6567
module internal ExceptionDispatchInfoHelpers =

0 commit comments

Comments
 (0)