@@ -505,6 +505,7 @@ type ResultOrException<'TResult> =
505505module ResultOrException =
506506
507507let success a = Result a
508+
508509let raze ( b : exn ) = Exception b
509510
510511// map
@@ -523,6 +524,124 @@ module ResultOrException =
523524| Result x-> success x
524525| Exception_ err-> f()
525526
527+ [<RequireQualifiedAccess>]
528+ type ValueOrCancelled < 'TResult > =
529+ | Valueof 'TResult
530+ | Cancelledof OperationCanceledException
531+
532+ /// Represents a cancellable computation with explicit representation of a cancelled result.
533+ ///
534+ /// A cancellable computation is passed may be cancelled via a CancellationToken, which is propagated implicitly.
535+ /// If cancellation occurs, it is propagated as data rather than by raising an OperationCancelledException.
536+ type Cancellable < 'TResult > = Cancellableof ( System.Threading.CancellationToken -> ValueOrCancelled < 'TResult >)
537+
538+ [<CompilationRepresentation( CompilationRepresentationFlags.ModuleSuffix) >]
539+ module Cancellable =
540+
541+ /// Bind the result of a cancellable computation
542+ let bind f ( Cancellable f1 ) =
543+ Cancellable( fun ct ->
544+ if ct.IsCancellationRequestedthen
545+ ValueOrCancelled.Cancelled( OperationCanceledException())
546+ else
547+ match f1 ctwith
548+ | ValueOrCancelled.Value res1->
549+ if ct.IsCancellationRequestedthen
550+ ValueOrCancelled.Cancelled( OperationCanceledException())
551+ else
552+ let ( Cancellable f2 ) = f res1
553+ f2 ct
554+ | ValueOrCancelled.Cancelled err1->
555+ ValueOrCancelled.Cancelled err1)
556+
557+ /// Map the result of a cancellable computation
558+ let map f ( Cancellable f1 ) =
559+ Cancellable( fun ct ->
560+ match f1 ctwith
561+ | ValueOrCancelled.Value res1-> ValueOrCancelled.Value( f res1)
562+ | ValueOrCancelled.Cancelled err1-> ValueOrCancelled.Cancelled err1)
563+
564+ /// Return a simple value as the result of a cancellable computation
565+ let ret x = Cancellable( fun _ -> ValueOrCancelled.Value x)
566+
567+ /// Fold a cancellable computation along a sequence of inputs
568+ let fold f acc seq =
569+ ( ret acc, seq) ||> Seq.fold( fun acc x -> acc|> bind( fun acc -> f acc x))
570+
571+ /// Iterate a cancellable computation over a collection
572+ let each f seq =
573+ ([], seq) ||> fold( fun acc x -> f x|> map( fun x2 -> x2:: acc)) |> map List.rev
574+
575+ /// Delay a cancellable computation
576+ let delay ( f : unit -> Cancellable < 'T >) = Cancellable( fun ct -> let ( Cancellable g ) = f() in g ct)
577+
578+ /// Run a cancellable computation using the given cancellation token
579+ let run ct ( Cancellable f ) = f ct
580+
581+ /// Run the computation in a mode where it may not be cancelled. The computation never results in a
582+ /// ValueOrCancelled.Cancelled.
583+ let runWithoutCancellation comp =
584+ let res = run System.Threading.CancellationToken.None comp
585+ match reswith
586+ | ValueOrCancelled.Cancelled_ -> failwith" unexpected cancellation"
587+ | ValueOrCancelled.Value r-> r
588+
589+ /// Bind the cancellation token associated with the computation
590+ let token () = Cancellable( fun ct -> ValueOrCancelled.Value ct)
591+
592+ /// Represents a canceled computation
593+ let canceled () = Cancellable( fun _ -> ValueOrCancelled.Cancelled( new OperationCanceledException()))
594+
595+ /// Catch exceptions in a computation
596+ let private catch ( Cancellable e ) =
597+ Cancellable( fun ct ->
598+ try
599+ match e ctwith
600+ | ValueOrCancelled.Value r-> ValueOrCancelled.Value( Choice1Of2 r)
601+ | ValueOrCancelled.Cancelled e-> ValueOrCancelled.Cancelled e
602+ with err->
603+ ValueOrCancelled.Value( Choice2Of2 err))
604+
605+ /// Implement try/finally for a cancellable computation
606+ let tryFinally e compensation =
607+ catch e|> bind( fun res ->
608+ compensation();
609+ match reswith Choice1Of2 r-> ret r| Choice2Of2 err-> raise err)
610+
611+ /// Implement try/with for a cancellable computation
612+ let tryWith e handler =
613+ catch e|> bind( fun res ->
614+ match reswith Choice1Of2 r-> ret r| Choice2Of2 err-> handler err)
615+
616+ // /// Run the cancellable computation within an Async computation. This isn't actaully used in the codebase, but left
617+ // here in case we need it in the future
618+ //
619+ // let toAsync e =
620+ // async {
621+ // let! ct = Async.CancellationToken
622+ // return!
623+ // Async.FromContinuations(fun (cont, econt, ccont) ->
624+ // // Run the computation synchronously using the given cancellation token
625+ // let res = try Choice1Of2 (run ct e) with err -> Choice2Of2 err
626+ // match res with
627+ // | Choice1Of2 (ValueOrCancelled.Value v) -> cont v
628+ // | Choice1Of2 (ValueOrCancelled.Cancelled err) -> ccont err
629+ // | Choice2Of2 err -> econt err)
630+ // }
631+
632+ type CancellableBuilder () =
633+ member x.Bind ( e , k ) = Cancellable.bind k e
634+ member x.Return ( v ) = Cancellable.ret v
635+ member x.ReturnFrom ( v ) = v
636+ member x.Combine ( e1 , e2 ) = e1|> Cancellable.bind( fun () -> e2)
637+ member x.TryWith ( e , handler ) = Cancellable.tryWith e handler
638+ member x.Using ( resource , e ) = Cancellable.tryFinally( e resource) ( fun () -> ( resource:> System.IDisposable) .Dispose())
639+ member x.TryFinally ( e , compensation ) = Cancellable.tryFinally e compensation
640+ member x.Delay ( f ) = Cancellable.delay f
641+ member x.Zero () = Cancellable.ret()
642+
643+ let cancellable = CancellableBuilder()
644+
526645/// Computations that can cooperatively yield by returning a continuation
527646///
528647/// - Any yield of a NotYetDone should typically be "abandonable" without adverse consequences. No resource release
@@ -533,6 +652,8 @@ module ResultOrException =
533652///
534653/// - The key thing is that you can take an Eventually value and run it with
535654/// Eventually.repeatedlyProgressUntilDoneOrTimeShareOverOrCanceled
655+ ///
656+ /// - Cancellation results in a suspended computation rather than complete abandonment
536657type Eventually < 'T > =
537658| Doneof 'T
538659| NotYetDoneof ( CompilationThreadToken -> Eventually < 'T >)
@@ -559,6 +680,8 @@ module Eventually =
559680
560681/// Keep running the computation bit by bit until a time limit is reached.
561682/// The runner gets called each time the computation is restarted
683+ ///
684+ /// If cancellation happens, the operation is left half-complete, ready to resume.
562685let repeatedlyProgressUntilDoneOrTimeShareOverOrCanceled timeShareInMilliseconds ( ct : CancellationToken ) runner e =
563686let sw = new System.Diagnostics.Stopwatch()
564687let rec runTimeShare ctok e =
@@ -578,7 +701,7 @@ module Eventually =
578701 NotYetDone( fun ctok -> runTimeShare ctok e)
579702
580703/// Keep running the asynchronous computation bit by bit. The runner gets called each time the computation is restarted.
581- /// Can be cancelled in the normal way.
704+ /// Can be cancelledas an Async in the normal way.
582705let forceAsync ( runner : ( CompilationThreadToken -> Eventually < 'T >) -> Async < Eventually < 'T >>) ( e : Eventually < 'T >) : Async < 'T option > =
583706let rec loop ( e : Eventually < 'T >) =
584707async {