Cancellable: built-in stack guard by majocha · Pull Request #18285 · dotnet/fsharp · GitHub
Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 15 additions & 32 deletions src/Compiler/Checking/CheckDeclarations.fs
3 changes: 2 additions & 1 deletion src/Compiler/Driver/CompilerImports.fs
Original file line number Diff line number Diff line change
Expand Up @@ -2255,7 +2255,7 @@ and [<Sealed>] TcImports
r: AssemblyResolution
) : Async<(_ * (unit -> AvailableImportedAssembly list)) option> =
async {
do! Cancellable.UseToken()
use! _holder = Cancellable.UseToken()
CheckDisposed()
let m = r.originalReference.Range
let fileName = r.resolvedPath
Expand Down Expand Up @@ -2327,6 +2327,7 @@ and [<Sealed>] TcImports
async {
CheckDisposed()

use! _holder = Cancellable.UseToken()

let tcConfig = tcConfigP.Get ctok

Expand Down
5 changes: 0 additions & 5 deletions src/Compiler/Facilities/DiagnosticsLogger.fs
Original file line number Diff line number Diff line change
Expand Up @@ -891,7 +891,6 @@ type StackGuard(maxDepth: int, name: string) =

try
if depth % maxDepth = 0 then

async {
do! Async.SwitchToNewThread()
Thread.CurrentThread.Name <- $"F# Extra Compilation Thread for {name} (depth {depth})"
Expand All @@ -903,10 +902,6 @@ type StackGuard(maxDepth: int, name: string) =
finally
depth <- depth - 1

[<DebuggerHidden; DebuggerStepThrough>]
member x.GuardCancellable(original: Cancellable<'T>) =
Cancellable(fun ct -> x.Guard(fun () -> Cancellable.run ct original))

static member val DefaultDepth =
#if DEBUG
GetEnvInteger "FSHARP_DefaultStackGuardDepth" 50
Expand Down
2 changes: 0 additions & 2 deletions src/Compiler/Facilities/DiagnosticsLogger.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -462,8 +462,6 @@ type StackGuard =
[<CallerLineNumber; Optional; DefaultParameterValue(0)>] line: int ->
'T

member GuardCancellable: Internal.Utilities.Library.Cancellable<'T> -> Internal.Utilities.Library.Cancellable<'T>

static member GetDepthOption: string -> int

/// This represents the global state established as each task function runs as part of the build.
Expand Down
99 changes: 53 additions & 46 deletions src/Compiler/Utilities/Cancellable.fs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ type Cancellable =
static member UseToken() =
async {
let! ct = Async.CancellationToken
tokenHolder.Value <- ValueSome ct
return Cancellable.UsingToken ct
}

static member UsingToken(ct) =
Expand All @@ -47,44 +47,53 @@ open System
open System.Threading
open FSharp.Compiler

#if !FSHARPCORE_USE_PACKAGE
open FSharp.Core.CompilerServices.StateMachineHelpers
#endif

[<RequireQualifiedAccess; Struct>]
type ValueOrCancelled<'TResult> =
| Value of result: 'TResult
| Cancelled of ``exception``: OperationCanceledException

[<Struct>]
type Cancellable<'T> = Cancellable of (CancellationToken -> ValueOrCancelled<'T>)
type Cancellable<'T> = Cancellable of (CancellationToken * int -> ValueOrCancelled<'T>)

module Cancellable =

let inline run (ct: CancellationToken) (Cancellable oper) =
let maxDepth = 100

let handleCheckAndThrow (ct: CancellationToken, depth) oper =
try
oper (ct, depth)
with
| :? OperationCanceledException as e when ct.IsCancellationRequested -> ValueOrCancelled.Cancelled e
| :? OperationCanceledException as e -> InvalidOperationException("Wrong cancellation token", e) |> raise

let run (ct: CancellationToken, depth: int) (Cancellable oper) =
if ct.IsCancellationRequested then
ValueOrCancelled.Cancelled(OperationCanceledException ct)
else if depth % maxDepth = 0 then
async {
do! Async.SwitchToNewThread()
return handleCheckAndThrow (ct, depth + 1) oper
}
|> Async.RunSynchronously
else
try
use _ = Cancellable.UsingToken(ct)
oper ct
with
| :? OperationCanceledException as e when ct.IsCancellationRequested -> ValueOrCancelled.Cancelled e
| :? OperationCanceledException as e -> InvalidOperationException("Wrong cancellation token", e) |> raise
handleCheckAndThrow (ct, depth + 1) oper

let fold f acc seq =
Cancellable(fun ct ->
Cancellable(fun state ->
let mutable acc = ValueOrCancelled.Value acc

for x in seq do
match acc with
| ValueOrCancelled.Value accv -> acc <- run ct (f accv x)
| ValueOrCancelled.Value accv -> acc <- run state (f accv x)
| ValueOrCancelled.Cancelled _ -> ()

acc)

let runWithoutCancellation comp =
let res = run CancellationToken.None comp
use _ = Cancellable.UsingToken CancellationToken.None
let res = run (CancellationToken.None, 1) comp

match res with
| ValueOrCancelled.Cancelled _ -> failwith "unexpected cancellation"
Expand All @@ -93,7 +102,8 @@ module Cancellable =
let toAsync c =
async {
let! ct = Async.CancellationToken
let res = run ct c
use! _holder = Cancellable.UseToken()
let res = run (ct, 1) c

return!
Async.FromContinuations(fun (cont, _econt, ccont) ->
Expand All @@ -102,54 +112,51 @@ module Cancellable =
| ValueOrCancelled.Cancelled ce -> ccont ce)
}

let token () = Cancellable(ValueOrCancelled.Value)
let token () =
Cancellable(fun (ct, _) -> ValueOrCancelled.Value ct)

type CancellableBuilder() =

member inline _.Delay([<InlineIfLambda>] f) =
Cancellable(fun ct ->
Cancellable(fun state ->
let (Cancellable g) = f ()
g ct)
g state)

member inline _.Bind(comp, [<InlineIfLambda>] k) =
Cancellable(fun ct ->
#if !FSHARPCORE_USE_PACKAGE
Cancellable(fun state ->

__debugPoint ""
#endif

match Cancellable.run ct comp with
| ValueOrCancelled.Value v1 -> Cancellable.run ct (k v1)
match Cancellable.run state comp with
| ValueOrCancelled.Value v1 -> Cancellable.run state (k v1)
| ValueOrCancelled.Cancelled err1 -> ValueOrCancelled.Cancelled err1)

member inline _.BindReturn(comp, [<InlineIfLambda>] k) =
Cancellable(fun ct ->
#if !FSHARPCORE_USE_PACKAGE
Cancellable(fun state ->

__debugPoint ""
#endif

match Cancellable.run ct comp with
match Cancellable.run state comp with
| ValueOrCancelled.Value v1 -> ValueOrCancelled.Value(k v1)
| ValueOrCancelled.Cancelled err1 -> ValueOrCancelled.Cancelled err1)

member inline _.Combine(comp1, comp2) =
Cancellable(fun ct ->
#if !FSHARPCORE_USE_PACKAGE
Cancellable(fun state ->

__debugPoint ""
#endif

match Cancellable.run ct comp1 with
| ValueOrCancelled.Value() -> Cancellable.run ct comp2
match Cancellable.run state comp1 with
| ValueOrCancelled.Value() -> Cancellable.run state comp2
| ValueOrCancelled.Cancelled err1 -> ValueOrCancelled.Cancelled err1)

member inline _.TryWith(comp, [<InlineIfLambda>] handler) =
Cancellable(fun ct ->
#if !FSHARPCORE_USE_PACKAGE
Cancellable(fun state ->

__debugPoint ""
#endif

let compRes =
try
match Cancellable.run ct comp with
match Cancellable.run state comp with
| ValueOrCancelled.Value res -> ValueOrCancelled.Value(Choice1Of2 res)
| ValueOrCancelled.Cancelled exn -> ValueOrCancelled.Cancelled exn
with err ->
Expand All @@ -159,19 +166,19 @@ type CancellableBuilder() =
| ValueOrCancelled.Value res ->
match res with
| Choice1Of2 r -> ValueOrCancelled.Value r
| Choice2Of2 err -> Cancellable.run ct (handler err)
| Choice2Of2 err -> Cancellable.run state (handler err)
| ValueOrCancelled.Cancelled err1 -> ValueOrCancelled.Cancelled err1)

member inline _.Using(resource, [<InlineIfLambda>] comp) =
Cancellable(fun ct ->
#if !FSHARPCORE_USE_PACKAGE
Cancellable(fun state ->

__debugPoint ""
#endif

let body = comp resource

let compRes =
try
match Cancellable.run ct body with
match Cancellable.run state body with
| ValueOrCancelled.Value res -> ValueOrCancelled.Value(Choice1Of2 res)
| ValueOrCancelled.Cancelled exn -> ValueOrCancelled.Cancelled exn
with err ->
Expand All @@ -187,14 +194,13 @@ type CancellableBuilder() =
| ValueOrCancelled.Cancelled err1 -> ValueOrCancelled.Cancelled err1)

member inline _.TryFinally(comp, [<InlineIfLambda>] compensation) =
Cancellable(fun ct ->
#if !FSHARPCORE_USE_PACKAGE
Cancellable(fun state ->

__debugPoint ""
#endif

let compRes =
try
match Cancellable.run ct comp with
match Cancellable.run state comp with
| ValueOrCancelled.Value res -> ValueOrCancelled.Value(Choice1Of2 res)
| ValueOrCancelled.Cancelled exn -> ValueOrCancelled.Cancelled exn
with err ->
Expand All @@ -212,7 +218,8 @@ type CancellableBuilder() =
member inline _.Return v =
Cancellable(fun _ -> ValueOrCancelled.Value v)

member inline _.ReturnFrom(v: Cancellable<'T>) = v
member inline _.ReturnFrom(v: Cancellable<'T>) =
Cancellable(fun state -> Cancellable.run state v)

member inline _.Zero() =
Cancellable(fun _ -> ValueOrCancelled.Value())
Expand Down
10 changes: 4 additions & 6 deletions src/Compiler/Utilities/Cancellable.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,8 @@ open System.Threading

[<Sealed>]
type Cancellable =
static member internal UseToken: unit -> Async<unit>

/// For use in testing only. Cancellable.token should be set only by the cancellable computation.
static member internal UsingToken: CancellationToken -> IDisposable
/// Capture the current cancellation token for use in CheckAndThrow.
static member internal UseToken: unit -> Async<IDisposable>

static member HasCancellationToken: bool
static member Token: CancellationToken
Expand All @@ -31,12 +29,12 @@ type internal ValueOrCancelled<'TResult> =
/// A cancellable computation may be cancelled via a CancellationToken, which is propagated implicitly.
/// If cancellation occurs, it is propagated as data rather than by raising an OperationCanceledException.
[<Struct>]
type internal Cancellable<'T> = Cancellable of (CancellationToken -> ValueOrCancelled<'T>)
type internal Cancellable<'T> = Cancellable of (CancellationToken * int -> ValueOrCancelled<'T>)

module internal Cancellable =

/// Run a cancellable computation using the given cancellation token
val inline run: ct: CancellationToken -> Cancellable<'T> -> ValueOrCancelled<'T>
val run: CancellationToken * int -> Cancellable<'T> -> ValueOrCancelled<'T>

val fold: f: ('State -> 'T -> Cancellable<'State>) -> acc: 'State -> seq: seq<'T> -> Cancellable<'State>

Expand Down