libxtc 0.4.0
Async concurrency for C: Tokio + Seastar + BEAM, in one library
Loading...
Searching...
No Matches
xtc_inspect.h
1/*-
2 * Copyright (c) 2026, The XTC Project
3 * @stability stable
4 * Use of this source code is governed by the ISC License.
5 *
6 * src/inc/xtc_inspect.h
7 * Live process introspection -- the programmatic form of the
8 * debugger's xtc-procs / xtc-loops, callable from a running program
9 * (an admin command, a metrics scraper, a TUI). Modeled on
10 * Erlang's erlang:process_info/2 + the data observer renders.
11 *
12 * Each call takes a best-effort snapshot under the per-loop slot
13 * locks: the proc set is consistent for the duration of the call,
14 * the per-proc counters are read under the mailbox lock, and the
15 * scheduler-owned run state is sampled (it may change the instant
16 * after). Callbacks run AFTER all internal locks are released, so
17 * they may freely call back into the proc/loop APIs.
18 *
19 * See docs/guide/debugging.md.
20 */
21
22#ifndef XTC_INSPECT_H
23#define XTC_INSPECT_H
24
25#include "xtc_export.h"
26
27#include <stddef.h>
28#include <stdint.h>
29
30#include "xtc.h"
31#include "xtc_proc.h"
32
33/* Run state of a proc (mirrors the scheduler task state). */
34enum xtc_proc_run_state {
35 XTC_PROC_SCHEDULED = 0,
36 XTC_PROC_RUNNING = 1,
37 XTC_PROC_PARKED = 2,
38 XTC_PROC_DONE = 3
39};
40
41/* Why a PARKED proc is parked (XTC_PARK_NONE otherwise). */
42enum xtc_proc_park {
43 XTC_PARK_NONE = 0,
44 XTC_PARK_FD = 1,
45 XTC_PARK_TIMER = 2,
46 XTC_PARK_MAILBOX = 3
47};
48
49/*
50 * One proc's snapshot. mbox_len is the live mailbox depth -- the
51 * single most useful health signal in a message-passing system; a
52 * large or growing value is the most common pathology.
53 */
54typedef struct xtc_proc_info {
55 size_t size; /* sizeof(xtc_proc_info_t): XTC_PROC_INFO_INIT */
56 xtc_pid_t pid;
57 int run_state; /* enum xtc_proc_run_state */
58 int park_reason; /* enum xtc_proc_park (valid if PARKED) */
59 int alive;
60 int kill_pending;
61 /*
62 * A2 cancellation-mask state -- the "is it killable RIGHT NOW?"
63 * signal a supervisor needs to choose between wait, kill, and
64 * escalate. kill_pending alone cannot distinguish "the fiber is
65 * unwinding" from "the fiber is wedged inside a critical section
66 * that defers the kill indefinitely"; these two do.
67 *
68 * mask_depth > 0 the proc is inside xtc_uncancelable(), so an
69 * async kill will be DEFERRED, not acted on.
70 * mask_deferred != 0 a kill was delivered and is latched, waiting
71 * for the mask to drop to 0.
72 *
73 * mask_depth > 0 with mask_deferred != 0 and no forward progress is
74 * the wedged-critical-section signature: xtc_exit_pid has already
75 * been observed and cannot fire. A supervisor that sees it should
76 * escalate rather than wait, and MUST NOT assume a further
77 * xtc_exit_pid will help. See xtc_exit_pid_deadline, which reports
78 * this as XTC_KILL_DEFERRED, and the "Killing a fiber that mutates
79 * shared state" section of xtc_proc(3).
80 *
81 * Both are sampled without synchronizing (like run_state): a
82 * best-effort snapshot, exact for a fiber that is not running.
83 */
84 unsigned mask_depth;
85 int mask_deferred;
86 size_t mbox_len; /* current depth */
87 size_t mbox_peak; /* high-water mark */
88 size_t mbox_cap;
89 size_t mbox_saved; /* selective-receive save queue */
90 uint64_t mbox_recv_total; /* messages ever accepted */
91 uint64_t mbox_drop_total; /* messages ever rejected (full/dead) */
93
94/* Set before xtc_proc_info(); the library writes at most .size bytes. An
95 * info handed to an xtc_inspect_proc_fn carries the LIBRARY's size: check
96 * XTC_FIELD_OK(info, field) before reading a field newer than your oldest
97 * supported library. */
98#define XTC_PROC_INFO_INIT { .size = sizeof(xtc_proc_info_t) }
99
100/*
101 * Link/monitor topology is intentionally NOT in the live snapshot: a
102 * proc mutates its own link/monitor lists without a lock, so walking
103 * them from another thread would race. Use the debugger (xtc-proc,
104 * which runs against a stopped program) to inspect link/monitor
105 * topology; the live API reports only fields that are safe to read
106 * concurrently (mailbox counters under the mailbox lock, plus the
107 * sampled run state).
108 */
109
110/* One loop's scheduler snapshot. */
111typedef struct xtc_loop_info {
112 size_t size; /* the LIBRARY's sizeof: see XTC_FIELD_OK */
113 int loop_id; /* 0 standalone, exec slot + 1 otherwise */
114 int n_procs; /* live procs homed on this loop */
115 int n_alive; /* live tasks (procs + plain tasks) */
116 uint64_t tasks_run;
117 uint64_t steals;
119
120/* Enumeration callbacks: return 0 to continue, nonzero to stop early. */
121typedef int (*xtc_inspect_proc_fn)(const xtc_proc_info_t *info, void *user);
122typedef int (*xtc_inspect_loop_fn)(const xtc_loop_info_t *info, void *user);
123
124/*
125 * PUBLIC: int xtc_inspect_procs __P((xtc_inspect_proc_fn, void *));
126 * PUBLIC: int xtc_inspect_loops __P((xtc_inspect_loop_fn, void *));
127 * PUBLIC: int xtc_proc_info __P((xtc_pid_t, xtc_proc_info_t *));
128 */
129
130/* Invoke `cb` once per live proc (across all loops). Returns the
131 * number of procs visited, or a negative XTC_E_* on error. */
132XTC_API int xtc_inspect_procs(xtc_inspect_proc_fn cb, void *user);
133
134/* Invoke `cb` once per registered loop. Returns the loop count or a
135 * negative XTC_E_*. */
136XTC_API int xtc_inspect_loops(xtc_inspect_loop_fn cb, void *user);
137
138/* Snapshot one proc by pid into *out, writing at most out->size bytes.
139 * XTC_OK on success, XTC_E_NOTFOUND if no live proc has that pid,
140 * XTC_E_INVAL on a NULL out-pointer or an out->size of 0. */
141XTC_API int xtc_proc_info(xtc_pid_t pid, xtc_proc_info_t *out);
142
143#endif /* XTC_INSPECT_H */