Keep an explicit tree of cancellation contexts

This is slightly more efficient, and might also be useful to allow
dumping out the tree for debugging.
This commit is contained in:
Thomas Leonard 2021-11-17 14:49:08 +00:00
parent 021330b258
commit 9c41d9fdf2
8 changed files with 69 additions and 85 deletions

View File

@ -17,14 +17,25 @@ type state =
type t = { type t = {
mutable state : state; mutable state : state;
parent : t;
children : t Lwt_dllist.t;
protected : bool;
}
type fibre_context = {
tid : Ctf.id;
mutable cancel : t;
} }
(* A dummy value for bootstrapping *) (* A dummy value for bootstrapping *)
let boot = { let rec boot = {
state = Finished; state = Finished;
parent = boot;
children = Lwt_dllist.create ();
protected = true;
} }
type _ eff += Set_cancel : t -> t eff type _ eff += Get_context : fibre_context eff
let cancelled t = let cancelled t =
match t.state with match t.state with
@ -49,70 +60,68 @@ let is_finished t =
| Finished -> true | Finished -> true
| On _ | Cancelling _ -> false | On _ | Cancelling _ -> false
(* Runs [fn] with a fresh cancellation context value (but does not install it). *) (* Runs [fn] with a fresh cancellation context. *)
let with_cc fn = let with_cc ~ctx ?parent ~protected fn =
let q = Lwt_dllist.create () in let q = Lwt_dllist.create () in
let t = { state = On q } in let parent = Option.value parent ~default:ctx.cancel in
Fun.protect (fun () -> fn t) let children = Lwt_dllist.create () in
~finally:(fun () -> t.state <- Finished) let t = { state = On q; parent; children; protected } in
let node = Lwt_dllist.add_r t parent.children in
ctx.cancel <- t;
match fn t with
| x -> ctx.cancel <- t.parent; t.state <- Finished; Lwt_dllist.remove node; x
| exception ex -> ctx.cancel <- t.parent; t.state <- Finished; Lwt_dllist.remove node; raise ex
let protect_full fn = let protect fn =
with_cc @@ fun t -> let ctx = perform Get_context in
let x = with_cc ~ctx ?parent:None ~protected:true @@ fun t ->
let old = perform (Set_cancel t) in let x = fn () in
Fun.protect (fun () -> fn t)
~finally:(fun () -> ignore (perform (Set_cancel old)))
in
check t; check t;
x x
let protect fn = protect_full (fun (_ : t) -> fn ()) let add_hook t hook =
let add_hook_unwrapped t hook =
match t.state with match t.state with
| Finished -> invalid_arg "Cancellation context finished!" | Finished -> invalid_arg "Cancellation context finished!"
| Cancelling (ex, _) -> protect (fun () -> hook ex); Hook.null | Cancelling (ex, _) -> protect (fun () -> hook (Cancelled ex)); Hook.null
| On q -> | On q ->
let node = Lwt_dllist.add_r hook q in let node = Lwt_dllist.add_r hook q in
(fun () -> Lwt_dllist.remove node) (fun () -> Lwt_dllist.remove node)
let add_hook t hook = add_hook_unwrapped t (fun ex -> hook (Cancelled ex)) let rec cancel t ex =
let cancel t ex =
match t.state with match t.state with
| Finished -> invalid_arg "Cancellation context finished!" | Finished -> invalid_arg "Cancellation context finished!"
| Cancelling _ -> () | Cancelling _ -> ()
| On q -> | On q ->
let bt = Printexc.get_raw_backtrace () in let bt = Printexc.get_raw_backtrace () in
t.state <- Cancelling (ex, bt); t.state <- Cancelling (ex, bt);
let cex = Cancelled ex in
let rec aux () = let rec aux () =
match Lwt_dllist.take_opt_r q with match Lwt_dllist.take_opt_r q with
| None -> [] | None -> Lwt_dllist.fold_r (cancel_child ex) t.children []
| Some f -> | Some f ->
match f ex with match f cex with
| () -> aux () | () -> aux ()
| exception ex2 -> ex2 :: aux () | exception ex2 -> ex2 :: aux ()
in in
match protect aux with match protect aux with
| [] -> () | [] -> ()
| exns -> raise (Cancel_hook_failed exns) | exns -> raise (Cancel_hook_failed exns)
and cancel_child ex t acc =
if t.protected then acc
else match cancel t ex with
| () -> acc
| exception ex -> ex :: acc
let sub fn = let sub fn =
with_cc @@ fun t -> let ctx = perform Get_context in
with_cc ~ctx ?parent:None ~protected:false @@ fun t ->
let x = let x =
(* Can't use Fun.protect here because of [Fun.Finally_raised]. *) match fn t with
let old = perform (Set_cancel t) in
match
let unhook = add_hook_unwrapped old (cancel t) in
Fun.protect (fun () -> fn t) ~finally:unhook
with
| x -> | x ->
ignore (perform (Set_cancel old)); check t.parent;
check old;
x x
| exception ex -> | exception ex ->
ignore (perform (Set_cancel old)); check t.parent;
check old;
raise ex raise ex
in in
match t.state with match t.state with
@ -123,11 +132,7 @@ let sub fn =
(* Like [sub], but it's OK if the new context is cancelled. (* Like [sub], but it's OK if the new context is cancelled.
(instead, return the parent context on exit so the caller can check that) *) (instead, return the parent context on exit so the caller can check that) *)
let sub_unchecked fn = let sub_unchecked fn =
with_cc @@ fun t -> let ctx = perform Get_context in
let old = perform (Set_cancel t) in with_cc ~ctx ?parent:None ~protected:false @@ fun t ->
Fun.protect (fun () -> fn t;
let unhook = add_hook_unwrapped old (cancel t) in t.parent
Fun.protect (fun () -> fn t) ~finally:unhook
)
~finally:(fun () -> ignore (perform (Set_cancel old)));
old

View File

@ -257,7 +257,7 @@ module Stdenv = struct
end end
module Private = struct module Private = struct
type context = Suspend.context = { type context = Cancel.fibre_context = {
tid : Ctf.id; tid : Ctf.id;
mutable cancel : Cancel.t; mutable cancel : Cancel.t;
} }
@ -268,8 +268,8 @@ module Private = struct
| Suspend = Suspend.Suspend | Suspend = Suspend.Suspend
| Fork = Fibre.Fork | Fork = Fibre.Fork
| Fork_ignore = Fibre.Fork_ignore | Fork_ignore = Fibre.Fork_ignore
| Get_context = Cancel.Get_context
| Trace = Std.Trace | Trace = Std.Trace
| Set_cancel = Cancel.Set_cancel
end end
let boot_cancel = Cancel.boot let boot_cancel = Cancel.boot
end end

View File

@ -283,9 +283,6 @@ module Cancel : sig
This can be used to clean up resources on cancellation. This can be used to clean up resources on cancellation.
However, it is usually better to use {!Switch.on_release} (which calls this for you). *) However, it is usually better to use {!Switch.on_release} (which calls this for you). *)
val protect_full : (t -> 'a) -> 'a
(** [protect_full fn] is like {!protect}, but also gives access to the new context. *)
val check : t -> unit val check : t -> unit
(** [check t] checks that [t] hasn't been cancelled. (** [check t] checks that [t] hasn't been cancelled.
@raise Cancelled If the context has been cancelled. *) @raise Cancelled If the context has been cancelled. *)
@ -619,7 +616,7 @@ module Private : sig
| Fork : (unit -> 'a) -> 'a Promise.t eff | Fork : (unit -> 'a) -> 'a Promise.t eff
(** See {!Fibre.fork} *) (** See {!Fibre.fork} *)
| Fork_ignore : (unit -> unit) -> unit eff | Fork_ignore : (context -> unit) -> unit eff
(** See {!Fibre.fork_ignore} *) (** See {!Fibre.fork_ignore} *)
| Trace : (?__POS__:(string * int * int * int) -> ('a, Format.formatter, unit, unit) format4 -> 'a) eff | Trace : (?__POS__:(string * int * int * int) -> ('a, Format.formatter, unit, unit) format4 -> 'a) eff
@ -628,8 +625,7 @@ module Private : sig
If the system is not ready to receive the trace output, If the system is not ready to receive the trace output,
the whole domain must block until it is. *) the whole domain must block until it is. *)
| Set_cancel : Cancel.t -> Cancel.t eff | Get_context : context eff
(** [Set_cancel c] sets the running fibre's cancel context to [c] and returns the previous context. *)
end end
val boot_cancel : Cancel.t val boot_cancel : Cancel.t

View File

@ -12,16 +12,14 @@ let fork ~sw ~exn_turn_off f =
in in
perform (Fork f) perform (Fork f)
type _ eff += Fork_ignore : (unit -> unit) -> unit eff type _ eff += Fork_ignore : (Cancel.fibre_context -> unit) -> unit eff
let fork_ignore ~sw f = let fork_ignore ~sw f =
let f () = let f child =
Switch.with_op sw @@ fun () -> Switch.with_op sw @@ fun () ->
try try
Cancel.protect_full @@ fun c -> Cancel.with_cc ~ctx:child ~parent:sw.cancel ~protected:false @@ fun _t ->
let hook = Switch.add_cancel_hook_unwrapped sw (Cancel.cancel c) in f ()
Fun.protect f
~finally:(fun () -> Hook.remove hook)
with ex -> with ex ->
Switch.turn_off sw ex Switch.turn_off sw ex
in in

View File

@ -1,12 +1,7 @@
open EffectHandlers open EffectHandlers
type context = {
tid : Ctf.id;
mutable cancel : Cancel.t;
}
type 'a enqueue = ('a, exn) result -> unit type 'a enqueue = ('a, exn) result -> unit
type _ eff += Suspend : (context -> 'a enqueue -> unit) -> 'a eff type _ eff += Suspend : (Cancel.fibre_context -> 'a enqueue -> unit) -> 'a eff
let enter_unchecked fn = perform (Suspend fn) let enter_unchecked fn = perform (Suspend fn)

View File

@ -31,7 +31,6 @@ let rec turn_off t ex =
Cancel.cancel t.cancel ex Cancel.cancel t.cancel ex
let add_cancel_hook t hook = Cancel.add_hook t.cancel hook let add_cancel_hook t hook = Cancel.add_hook t.cancel hook
let add_cancel_hook_unwrapped t hook = Cancel.add_hook_unwrapped t.cancel hook
let with_op t fn = let with_op t fn =
check t; check t;
@ -43,7 +42,7 @@ let with_op t fn =
Waiters.wake_all t.waiter (Ok ()) Waiters.wake_all t.waiter (Ok ())
) )
let await_internal waiters id (ctx:Suspend.context) enqueue = let await_internal waiters id (ctx:Cancel.fibre_context) enqueue =
let cleanup_hooks = Queue.create () in let cleanup_hooks = Queue.create () in
let when_resolved r = let when_resolved r =
Queue.iter Waiters.remove_waiter cleanup_hooks; Queue.iter Waiters.remove_waiter cleanup_hooks;

View File

@ -927,7 +927,7 @@ let run ?(queue_depth=64) ?(block_size=4096) main =
let rec fork ~tid ~cancel:initial_cancel fn = let rec fork ~tid ~cancel:initial_cancel fn =
Ctf.note_switch tid; Ctf.note_switch tid;
let fibre = { Eio.Private.tid; cancel = initial_cancel } in let fibre = { Eio.Private.tid; cancel = initial_cancel } in
match_with fn () match_with fn fibre
{ retc = (fun () -> schedule st); { retc = (fun () -> schedule st);
exnc = raise; exnc = raise;
effc = fun (type a) (e : a eff) -> effc = fun (type a) (e : a eff) ->
@ -972,11 +972,7 @@ let run ?(queue_depth=64) ?(block_size=4096) main =
); );
schedule st schedule st
) )
| Eio.Private.Effects.Set_cancel cancel -> Some (fun k -> | Eio.Private.Effects.Get_context -> Some (fun k -> continue k fibre)
let old = fibre.cancel in
fibre.cancel <- cancel;
continue k old
)
| Eio.Private.Effects.Suspend f -> Some (fun k -> | Eio.Private.Effects.Suspend f -> Some (fun k ->
let k = { Suspended.k; fibre } in let k = { Suspended.k; fibre } in
f fibre (function f fibre (function
@ -994,7 +990,7 @@ let run ?(queue_depth=64) ?(block_size=4096) main =
fork fork
~tid:id ~tid:id
~cancel:fibre.cancel ~cancel:fibre.cancel
(fun () -> (fun _fibre ->
match f () with match f () with
| x -> Promise.fulfill resolver x | x -> Promise.fulfill resolver x
| exception ex -> | exception ex ->
@ -1007,8 +1003,8 @@ let run ?(queue_depth=64) ?(block_size=4096) main =
enqueue_thread st k (); enqueue_thread st k ();
let child = Ctf.note_fork () in let child = Ctf.note_fork () in
Ctf.note_switch child; Ctf.note_switch child;
fork ~tid:child ~cancel:fibre.cancel (fun () -> fork ~tid:child ~cancel:fibre.cancel (fun new_fibre ->
match f () with match f new_fibre with
| () -> | () ->
Ctf.note_resolved child ~ex:None Ctf.note_resolved child ~ex:None
| exception ex -> | exception ex ->
@ -1029,7 +1025,7 @@ let run ?(queue_depth=64) ?(block_size=4096) main =
in in
let main_done = ref false in let main_done = ref false in
let `Exit_scheduler = let `Exit_scheduler =
fork ~tid:(Ctf.mint_id ()) ~cancel:Eio.Private.boot_cancel (fun () -> fork ~tid:(Ctf.mint_id ()) ~cancel:Eio.Private.boot_cancel (fun _fibre ->
Fun.protect (fun () -> Eio.Cancel.protect (fun () -> main stdenv)) Fun.protect (fun () -> Eio.Cancel.protect (fun () -> main stdenv))
~finally:(fun () -> main_done := true) ~finally:(fun () -> main_done := true)
) in ) in

View File

@ -586,7 +586,7 @@ let run main =
let rec fork ~tid ~cancel:initial_cancel fn = let rec fork ~tid ~cancel:initial_cancel fn =
Ctf.note_switch tid; Ctf.note_switch tid;
let fibre = { Eio.Private.tid; cancel = initial_cancel } in let fibre = { Eio.Private.tid; cancel = initial_cancel } in
match_with fn () match_with fn fibre
{ retc = (fun () -> ()); { retc = (fun () -> ());
exnc = (fun e -> raise e); exnc = (fun e -> raise e);
effc = fun (type a) (e : a eff) -> effc = fun (type a) (e : a eff) ->
@ -607,7 +607,7 @@ let run main =
fork fork
~tid:id ~tid:id
~cancel:fibre.cancel ~cancel:fibre.cancel
(fun () -> (fun _new_fibre ->
match f () with match f () with
| x -> Promise.fulfill resolver x | x -> Promise.fulfill resolver x
| exception ex -> | exception ex ->
@ -620,19 +620,14 @@ let run main =
enqueue_thread k (); enqueue_thread k ();
let child = Ctf.note_fork () in let child = Ctf.note_fork () in
Ctf.note_switch child; Ctf.note_switch child;
fork ~tid:child ~cancel:fibre.cancel (fun () -> fork ~tid:child ~cancel:fibre.cancel (fun new_fibre ->
match f () with match f new_fibre with
| () -> | () ->
Ctf.note_resolved child ~ex:None Ctf.note_resolved child ~ex:None
| exception ex -> | exception ex ->
Ctf.note_resolved child ~ex:(Some ex) Ctf.note_resolved child ~ex:(Some ex)
)) ))
| Eio.Private.Effects.Set_cancel cancel -> | Eio.Private.Effects.Get_context -> Some (fun k -> continue k fibre)
Some (fun k ->
let old = fibre.cancel in
fibre.cancel <- cancel;
continue k old
)
| Enter_unchecked fn -> Some (fun k -> | Enter_unchecked fn -> Some (fun k ->
fn { Suspended.k; fibre } fn { Suspended.k; fibre }
) )
@ -650,7 +645,7 @@ let run main =
} }
in in
let main_status = ref `Running in let main_status = ref `Running in
fork ~tid:(Ctf.mint_id ()) ~cancel:Eio.Private.boot_cancel (fun () -> fork ~tid:(Ctf.mint_id ()) ~cancel:Eio.Private.boot_cancel (fun _new_fibre ->
match Eio.Cancel.protect (fun () -> main stdenv) with match Eio.Cancel.protect (fun () -> main stdenv) with
| () -> main_status := `Done | () -> main_status := `Done
| exception ex -> main_status := `Ex (ex, Printexc.get_raw_backtrace ()) | exception ex -> main_status := `Ex (ex, Printexc.get_raw_backtrace ())