@@ -67,12 +67,16 @@ open System.Diagnostics
6767type ITrampolineInvocation =
6868 abstract member MoveNext: unit -> unit
6969 abstract IsCompleted: bool
70+ abstract ReplayExceptionIfStored: unit -> unit
7071
7172[<Struct; NoComparison; NoEquality>]
7273type 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
7781and CancellableStateMachine < 'TOverall > = ResumableStateMachine< CancellableStateMachineData< 'TOverall>>
7882and ICancellableStateMachine < 'TOverall > = IResumableStateMachine< CancellableStateMachineData< 'TOverall>>
@@ -82,38 +86,50 @@ and CancellableCode<'TOverall, 'T> = ResumableCode<CancellableStateMachineData<'
8286
8387[<Sealed>]
8488type 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>]
125141module 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>]
140154type 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>]
162194type Cancellable < 'T >( invokable : ICancellableInvokable < 'T >) =
163-
164- member _.GetInvocation () = invokable.Create()
165-
195+
196+ member _.GetInvocation ( delayed ) = invokable.Create( delayed )
197+
166198[<AutoOpen>]
167199module 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+
306342namespace Internal.Utilities.Library
307343
308344open 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 =
0 commit comments