feat(sched): cancel in-flight actions when a build fails

Only new spawns were held back after a failure, so every action already running
was allowed to finish even though its output would be discarded. The scheduler
now sends SIGTERM to the remaining children on the first failure, and reports
them separately rather than counting a process it killed as a failure of its
own. Partial outputs are removed and their cache entries dropped, so the next
run starts clean.

Under -k the existing behaviour is kept, since keeping going is the point.

On a graph with three six-second generators alongside one translation unit that
fails immediately, -j4 now returns in 0.12 s rather than 6.03 s.
This commit is contained in:
milner committed 2022-07-15 04:18:43 +00:00
1 parent b11c6e9f72
commit 7502fcd7dd
2 files changed
+36 -14

No files matched your search

+5 -3
View File
@@ -205,7 +205,7 @@ let execute p ~names =
end) end)
order; order;
Printf.printf "\n %s\n" (Style.dim (Printf.sprintf "%d actions, nothing run" !n)); Printf.printf "\n %s\n" (Style.dim (Printf.sprintf "%d actions, nothing run" !n));
(b, { Sched.built = 0; cached = 0; failed = 0 }) (b, { Sched.built = 0; cached = 0; failed = 0; aborted = 0 })
end end
else begin else begin
let cache = Cache.load (Filename.concat p.tc.builddir ".meowc-cache") in let cache = Cache.load (Filename.concat p.tc.builddir ".meowc-cache") in
@@ -223,12 +223,14 @@ let execute p ~names =
let summarise (r : Sched.result) elapsed = let summarise (r : Sched.result) elapsed =
if not fl.quiet && not fl.dry then begin if not fl.quiet && not fl.dry then begin
let text = let text =
if r.failed > 0 then Style.red (Style.plural r.failed "action" ^ " failed") if r.failed > 0 then
Style.red (Style.plural r.failed "action" ^ " failed")
^ (if r.aborted > 0 then Style.dim (Printf.sprintf ", %d cancelled" r.aborted) else "")
else if r.built = 0 then Style.dim "nothing to do" else if r.built = 0 then Style.dim "nothing to do"
else Printf.sprintf "%d built%s" r.built (Style.dim (Printf.sprintf ", %d cached" r.cached)) else Printf.sprintf "%d built%s" r.built (Style.dim (Printf.sprintf ", %d cached" r.cached))
in in
Printf.printf "%s %s%s\n" Printf.printf "%s %s%s\n"
(if r.built + r.failed = 0 then "" else "\n") (if r.built + r.failed + r.aborted = 0 then "" else "\n")
(Style.pad 48 text) (Style.dim (duration elapsed)) (Style.pad 48 text) (Style.dim (duration elapsed))
end; end;
if r.failed > 0 then exit 1 if r.failed > 0 then exit 1
+31 -11
View File
@@ -1,4 +1,4 @@
type result = { built : int; cached : int; failed : int } type result = { built : int; cached : int; failed : int; aborted : int }
type state = Blocked | Ready | Running | Done | Failed type state = Blocked | Ready | Running | Done | Failed
@@ -32,7 +32,8 @@ let run g ~selected ~jobs ~cache ~verbose ~keep_going ~on_start ~on_done =
let ready = Queue.create () in let ready = Queue.create () in
Array.iter (fun (v : Graph.node) -> if wanted v.id && pending.(v.id) = 0 then Queue.add v.id ready) nodes; Array.iter (fun (v : Graph.node) -> if wanted v.id && pending.(v.id) = 0 then Queue.add v.id ready) nodes;
let running = Hashtbl.create 8 in let running = Hashtbl.create 8 in
let built = ref 0 and cached = ref 0 and failed = ref 0 in let cancelled = Hashtbl.create 8 in
let built = ref 0 and cached = ref 0 and failed = ref 0 and aborted_count = ref 0 in
let stopping () = !failed > 0 && not keep_going in let stopping () = !failed > 0 && not keep_going in
let release i = let release i =
List.iter List.iter
@@ -66,12 +67,27 @@ let run g ~selected ~jobs ~cache ~verbose ~keep_going ~on_start ~on_done =
on_start v; on_start v;
Hashtbl.replace running pid (v, tmp) Hashtbl.replace running pid (v, tmp)
in in
let cancel_running () =
Hashtbl.iter
(fun pid _ ->
Hashtbl.replace cancelled pid ();
try Unix.kill pid Sys.sigterm with Unix.Unix_error _ -> ())
running
in
let discard (v : Graph.node) =
List.iter Cache.forget v.outs;
Cache.drop cache (List.hd v.outs);
List.iter (fun o -> try Sys.remove o with Sys_error _ -> ()) v.outs;
skip v.id
in
let reap () = let reap () =
let pid, status = Unix.wait () in let pid, status = Unix.wait () in
match Hashtbl.find_opt running pid with match Hashtbl.find_opt running pid with
| None -> () | None -> ()
| Some (v, tmp) -> | Some (v, tmp) ->
Hashtbl.remove running pid; Hashtbl.remove running pid;
let aborted = Hashtbl.mem cancelled pid in
Hashtbl.remove cancelled pid;
let ok = status = Unix.WEXITED 0 in let ok = status = Unix.WEXITED 0 in
let log = try Fs.read tmp with Sys_error _ -> "" in let log = try Fs.read tmp with Sys_error _ -> "" in
(try Sys.remove tmp with Sys_error _ -> ()); (try Sys.remove tmp with Sys_error _ -> ());
@@ -81,17 +97,21 @@ let run g ~selected ~jobs ~cache ~verbose ~keep_going ~on_start ~on_done =
List.iter Cache.forget v.outs; List.iter Cache.forget v.outs;
(match v.depfile with Some d -> Cache.forget d | None -> ()); (match v.depfile with Some d -> Cache.forget d | None -> ());
Cache.put cache (List.hd v.outs) (key_of v); Cache.put cache (List.hd v.outs) (key_of v);
release v.id release v.id;
on_done ~ok ~log v;
if verbose then print_endline (" " ^ Style.dim (Exec.show v.cmd))
end
else if aborted then begin
incr aborted_count;
discard v
end end
else begin else begin
incr failed; incr failed;
List.iter Cache.forget v.outs; discard v;
Cache.drop cache (List.hd v.outs); on_done ~ok ~log v;
List.iter (fun o -> try Sys.remove o with Sys_error _ -> ()) v.outs; if verbose then print_endline (" " ^ Style.dim (Exec.show v.cmd));
skip v.id if not keep_going then cancel_running ()
end; end
on_done ~ok ~log v;
if verbose then print_endline (" " ^ Style.dim (Exec.show v.cmd))
in in
while (not (Queue.is_empty ready) && not (stopping ())) || Hashtbl.length running > 0 do while (not (Queue.is_empty ready) && not (stopping ())) || Hashtbl.length running > 0 do
while (not (Queue.is_empty ready)) && Hashtbl.length running < jobs && not (stopping ()) do while (not (Queue.is_empty ready)) && Hashtbl.length running < jobs && not (stopping ()) do
@@ -107,4 +127,4 @@ let run g ~selected ~jobs ~cache ~verbose ~keep_going ~on_start ~on_done =
if Hashtbl.length running > 0 then reap () if Hashtbl.length running > 0 then reap ()
else if Queue.is_empty ready then () else if Queue.is_empty ready then ()
done; done;
{ built = !built; cached = !cached; failed = !failed } { built = !built; cached = !cached; failed = !failed; aborted = !aborted_count }