Deferred.cancel propagation failure in Async nested deferreds
0 reputation · 28 Feb 2020, 12:01 UTC
0 reputation · 28 Feb 2020, 12:01 UTC
In OCaml's Async library, Deferred.cancel is used to signal the cancellation of a deferred operation. While this mechanism notifies the immediate deferred and any awaiting code, the behavior regarding nested operations remains inconsistent.
When a parent deferred spawns child deferreds to perform sub-tasks, it is unclear if the cancellation signal automatically propagates down the chain. If the parent is canceled while children are still pending, the children may continue to execute, potentially leading to resource leaks or unexpected state mutations.
Given the internal nature of Async's cancellation logic, what is the documented policy for propagation to nested deferreds? Does the library provide a native way to ensure child deferreds are canceled when the parent is terminated?
34775 reputation · 28 Feb 2020, 12:18 UTC
In OCaml's Async library, calling Deferred.cancel on a deferred only marks that deferred as cancelled; the cancellation signal does **not** automatically recurse into child deferreds that were created independently of the parent.
Deferred.cancel updates the internal state of the target deferred and raises Cancelled when the deferred is awaited.bind, map, both, all, or_error, etc.Deferred.create or spawned without linking to the parent remain unaffected and may continue execution.The library treats cancellation as a cooperative signal that travels along the same edges used for normal results. If a child is not linked to the parent through those edges, there is no path for the signal to follow, so the child runs to completion unless it checks the parent’s state manually.
let parent = Deferred.create ()
let child = parent >>= fun _ -> Deferred.of_unit (do_work ())
or use Deferred.both parent child / Deferred.all [parent; child] so that cancelling the parent forces the child to await a cancelled result.
Deferred.set_cancel_handler parent (fun () ->
List.iter Deferred.cancel children)
set_cancel_handler, manually propagate by linking each child before spawning, or keep a reference list and call Deferred.cancel on each when you decide to cancel the parent.
To give a precise recommendation, please confirm the version of Async (or base_async) you are using; the availability of Deferred.set_cancel_handler changes whether you need the handler approach or must rely solely on linking combinators.
Use comments to ask for clarification. Post a solution as an answer.
6,510 reputation · 28 Feb 2020, 22:59 UTC
When a parent deferred is cancelled, the Cancelled exception travels only along the data‑flow edges created by combinators that depend on the parent’s result. This includes >>= (bind), map, both, all, and or_error (provided the parent’s cancellation is not caught and re‑raised as a different exception). If a child deferred is created with Deferred.create and never linked via one of these combinators, it remains independent and will run to completion unless you attach an explicit cancel handler.
For Async versions ≥ v0.10.0 you can use Deferred.set_cancel_handler to register a cleanup that runs when the parent is cancelled; in earlier releases you must manually propagate cancellation by linking the child with Deferred.both or Deferred.all before spawning it.