Continuing a discussion offshoot from How to "block" in an agnostic way?, I have some naive questions about cancellation, in Eio and I guess possibly in general. (I don’t have much concurrent programming experience.)
Discontinuing Multicore continuations
With effect handlers, a nice realization of the Multicore OCaml folks is that the operation of “dropping” a fiber (delimited continuation), what is sometimes written discontinue k
, in fact corresponds to resuming the continuation with an exception instead of a normal value: you write, say, discontinue k Exit
, providing an explicit exception. This is nice (much better than just invalidating the continuation and moving on) because it gives a fighting chance to resource-cleanup code:
let input = open_in path in
let@ () = Fun.protect ~finally:(fun _ -> close_in input) in
let v = perform Some_effect in
...
If perform Some_effect
discontinues the continuation with some exception exn
, the code behaves as if perform Some_effect
was turned into raise exn
, and in particular the input channel will be closed.
(Aside: I previously thought of exceptions as a special case of input handler whose continuations are never resumed. This idea from the Multicore folk was surprising to me because it gives a special role to exceptions, in a way that does not feel like a hack but rather a graceful interaction between the two features. I’m still not fully sure whether my initial intuition was wrong, there is a deep reason to give a special status to 0-ary effects, or I’m missing something else – probably the latter.)
Cancellation?
Could we use the same approach for cancellation? Do we still need the set_cancel_fn
business?
The Eio README explains that if an Eio fibre runs Fibre.yield ()
, and gets cancelled while it is waiting, this Fibre.yield ()
expression will raise a Cancelled
exception. This sounds like the same idea as discontinuation above, great!
But then @talex5 explains that there is an imperative protocol (set_cancel_fn
, clear_cancel_fn
) to register cancellation-time logic into a “fibre context”. Why do we need this additional complexity? Naively I would expect
Fibre.set_cancel_fn my_context (fun exn -> <cleanup logic>);
<rest of the code>
to be expressible as
try <rest of the code>
with exn ->
<cleanup logic>;
reraise exn
What am I missing?