Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cancellable: hide UsingToken and simplify inlined run #18309

Merged
merged 4 commits into from
Feb 12, 2025
Merged
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
54 changes: 29 additions & 25 deletions src/Compiler/Utilities/Cancellable.fs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,16 @@ namespace FSharp.Compiler
open System
open System.Threading

// This code provides two methods for handling cancellation in synchronous code:
// 1. Explicitly, by calling Cancellable.CheckAndThrow().
// 2. Implicitly, by wrapping the code in a cancellable computation.
// The cancellable computation propagates the CancellationToken and checks for cancellation implicitly.
// When it is impractical to use the cancellable computation, such as in deeply nested functions, Cancellable.CheckAndThrow() can be used.
// It checks a CancellationToken local to the current async execution context, held in AsyncLocal.
// Before calling Cancellable.CheckAndThrow(), this token must be set.
// The token is guaranteed to be set during execution of cancellable computation.
// Otherwise, it can be passed explicitly from the ambient async computation using Cancellable.UseToken().

[<Sealed>]
type Cancellable =
static let tokenHolder = AsyncLocal<CancellationToken voption>()
Expand Down Expand Up @@ -47,9 +57,7 @@ open System
open System.Threading
open FSharp.Compiler

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

[<RequireQualifiedAccess; Struct>]
type ValueOrCancelled<'TResult> =
Expand All @@ -65,12 +73,7 @@ module Cancellable =
if ct.IsCancellationRequested then
ValueOrCancelled.Cancelled(OperationCanceledException ct)
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
oper ct

let fold f acc seq =
Cancellable(fun ct ->
Expand All @@ -84,6 +87,7 @@ module Cancellable =
acc)

let runWithoutCancellation comp =
use _ = Cancellable.UsingToken CancellationToken.None
let res = run CancellationToken.None comp

match res with
Expand All @@ -92,14 +96,19 @@ module Cancellable =

let toAsync c =
async {
use! _holder = Cancellable.UseToken()

let! ct = Async.CancellationToken
let res = run ct c

return!
Async.FromContinuations(fun (cont, _econt, ccont) ->
match res with
| ValueOrCancelled.Value v -> cont v
| ValueOrCancelled.Cancelled ce -> ccont ce)
Async.FromContinuations(fun (cont, econt, ccont) ->
try
match run ct c with
| ValueOrCancelled.Value v -> cont v
| ValueOrCancelled.Cancelled ce -> ccont ce
with
| :? OperationCanceledException as ce when ct.IsCancellationRequested -> ccont ce
| :? OperationCanceledException as e -> InvalidOperationException("Wrong cancellation token", e) |> econt)
}

let token () = Cancellable(ValueOrCancelled.Value)
Expand All @@ -113,39 +122,35 @@ type CancellableBuilder() =

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

__debugPoint ""
#endif

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

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

__debugPoint ""
#endif

match Cancellable.run ct 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

__debugPoint ""
#endif

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

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

__debugPoint ""
#endif

let compRes =
try
Expand All @@ -164,9 +169,9 @@ type CancellableBuilder() =

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

__debugPoint ""
#endif

let body = comp resource

let compRes =
Expand All @@ -188,9 +193,8 @@ type CancellableBuilder() =

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

__debugPoint ""
#endif

let compRes =
try
Expand Down
3 changes: 0 additions & 3 deletions src/Compiler/Utilities/Cancellable.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,6 @@ open System.Threading
type Cancellable =
static member internal UseToken: unit -> Async<IDisposable>

/// For use in testing only. Cancellable.token should be set only by the cancellable computation.
static member internal UsingToken: CancellationToken -> IDisposable

static member HasCancellationToken: bool
static member Token: CancellationToken

Expand Down