package affect
Source file affect__trace.ml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136(*--------------------------------------------------------------------------- Copyright (c) 2026 The affect programmers. All rights reserved. SPDX-License-Identifier: ISC ---------------------------------------------------------------------------*) open Affect__base open Affect__action.Private open Affect__async_fun.Private (* Execution context identifiers *) type thread_id = int let current_thread_id () = Thread.id (Thread.self ()) type domain_id = int let current_domain_id () = (Domain.self () :> int) type exec_id = { thread_id : thread_id; sup : domain_id; index : int } let current_exec_id () = { thread_id = current_thread_id (); sup = current_domain_id (); index = -1; } (* Traces *) type payload = | Action_block of Async_fun.Call.id * Action.Meta.t | Action_unblock of Async_fun.Call.id * Action.Meta.t | Async_fun_call of { parent : Async_fun.Call.id; id : Async_fun.Call.id } | Async_fun_continue of Async_fun.Call.id | Async_fun_yield of Async_fun.Call.id | Async_fun_return of Async_fun.Call.id | Async_fun_user of Async_fun.Call.id * string | Domain_start | Domain_sleep | Domain_unblocker of { block : bool } | Domain_resume | Domain_panic of string | Domain_stop type t = exec_id * payload let is_domain (_, payload) = match payload with | Domain_start | Domain_sleep | Domain_unblocker _ | Domain_resume | Domain_stop -> true | _ -> false let is_fun t = not (is_domain t) let is_user (_, p) = match p with Async_fun_user _ -> true | _ -> false (* Formatting *) let call_none_id = Async_fun.Call.none_id let pf = Format.fprintf let pp_lines ppf msg = let lines = (String.split_on_char '\n' msg) in Format.(pp_print_list Format.pp_print_string) ppf lines let pp_call_id = Async_fun.Call.pp_id let pp_exec_id ppf eid = (* The special case of [eid.sup = 0] is to have less noise if you just Fun.Async.main in the program and you are happy with that. *) if eid.index = -1 then pf ppf "%d.τ%02x" eid.sup eid.thread_id else if eid.sup = 0 then pf ppf "%02d" eid.index else pf ppf "%d.%02d" eid.sup eid.index let pp_exec_trace ppf eid = pf ppf "[%a ]" pp_exec_id eid let pp_fun_trace ppf (eid, id) = pf ppf "[%a %a]" pp_exec_id eid pp_call_id id let pp_block_meta ppf m = Action.Meta.pp ppf m let pp ppf (eid, trace) = match trace with | Action_block (id, meta) -> pf ppf "%a @<0>\x1B[1mblock@<0>\x1B[0m on @[%a@]" pp_fun_trace (eid, id) pp_block_meta meta | Action_unblock (id, meta) -> pf ppf "%a @<0>\x1B[1munblock@<0>\x1B[0m @[%a@] from @[%a@]" pp_exec_trace eid pp_call_id id pp_block_meta meta | Async_fun_call { parent; id } -> if parent = call_none_id then pf ppf "%a root function" pp_fun_trace (eid, id) else pf ppf "%a calls %a" pp_fun_trace (eid, parent) pp_call_id id | Async_fun_continue id -> pf ppf "%a continue" pp_fun_trace (eid, id) | Async_fun_yield id -> pf ppf "%a yield" pp_fun_trace (eid, id) | Async_fun_return id -> pf ppf "%a @<0>\x1B[1mreturn@<0>\x1B[0m" pp_fun_trace (eid, id) | Async_fun_user (id, msg) -> pf ppf "%a %s" pp_fun_trace (eid, id) msg | Domain_start -> pf ppf "%a start" pp_exec_trace eid | Domain_sleep -> pf ppf "%a sleep" pp_exec_trace eid | Domain_unblocker { block } -> if block then pf ppf "%a @<0>\x1B[1mblock@<0>\x1B[0m on unblocker" pp_exec_trace eid else pf ppf "%a poll unblocker" pp_exec_trace eid | Domain_resume -> pf ppf "%a resume" pp_exec_trace eid | Domain_panic msg -> pf ppf "@[<v>%a panic@,%a@]" pp_exec_trace eid pp_lines msg | Domain_stop -> pf ppf "%a stop" pp_exec_trace eid (* Reporters *) type reporter = t -> unit let default_reporter = ignore let format_reporter ppf tr = pf (ppf ()) "@[%a@]@." pp tr let stderr_reporter = format_reporter (Format.get_err_formatter) let keep sat report = fun t -> if sat t then report t let only_fun report = keep is_fun report let only_user report = keep is_user report let reporter' : reporter Atomic.t = Atomic.make default_reporter let reporter () = Atomic.get reporter' let set_reporter report = Atomic.set reporter' report let exchange_reporter report = Atomic.exchange reporter' report let with_reporter report f = let old = exchange_reporter report in Fun.protect ~finally:(fun () -> set_reporter old) f module Delayed = struct type nonrec t = t list Atomic.t let make () = Atomic.make [] let reporter ts = fun t -> Atomic.update (fun ts -> t :: ts) ts let clear ts = Atomic.update (fun _ -> []) ts let traces ts = List.rev (Atomic.get ts) let pp ppf ts = Format.fprintf ppf "@[<v>%a@]" (Format.pp_print_list pp) (traces ts) let prerr ts = Format.fprintf (Format.get_err_formatter ()) "@[%a@]@." pp ts end (* Reporting *) let report t = try (Atomic.get reporter') t with | exn when not (Exn.is_runtime_system exn) -> let bt = Printexc.get_raw_backtrace () in Exn.trap exn bt