libxtc 0.4.0
Async concurrency for C: Tokio + Seastar + BEAM, in one library
Loading...
Searching...
No Matches
xtc_proc.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_proc.h
7 * Lightweight processes with mailboxes, selective receive,
8 * links, monitors, and explicit exit. M8 ships the core; the
9 * `xtc_orc` supervisor (M10) sits on top of these primitives.
10 *
11 * A process is a coroutine with identity (xtc_pid_t) plus a
12 * mailbox. Send is fire-and-forget; the message is copied into
13 * an envelope owned by the mailbox. Receive is selective: the
14 * caller supplies a match function, and envelopes that don't
15 * match are kept in arrival order in a save queue and re-tested
16 * on the next receive.
17 */
18
19#ifndef XTC_PROC_H
20#define XTC_PROC_H
21
22#include "xtc_export.h"
23
24#include <stddef.h>
25#include <stdint.h>
26
27#include "xtc.h"
28#include "xtc_loop.h"
29#include "xtc_async.h"
30#include "xtc_exec.h" /* xtc_exec_class_t for xtc_proc_opts_t.sched_class */
31
32/*
33 * Process identifier (16 bytes; 2.0). Encodes the loop ID, a per-loop
34 * slot index, and the slot's generation, so a stale pid (after a
35 * process exits and its slot is reused) is recognisably stale on
36 * lookup. The slot index is 32 bits (a loop holds as many live procs as
37 * memory and the xtc_res caps allow), and the generation is 64 bits per
38 * slot, so it never wraps: a pid held across any number of reuses of
39 * its slot never names the new occupant. Compare with xtc_pid_eq, not
40 * memcmp (padding-free today, but that is not part of the contract).
41 */
42typedef struct xtc_pid {
43 uint32_t loop_id; /* 0 standalone loop, else xtc_exec slot + 1 */
44 uint32_t local_id; /* slot in the loop's proc table */
45 uint64_t gen; /* slot generation, 1.. (0 only in XTC_PID_NONE) */
46} xtc_pid_t;
47
48#define XTC_PID_NONE ((xtc_pid_t){0, 0, 0})
49
50static inline int
51xtc_pid_eq(xtc_pid_t a, xtc_pid_t b)
52{
53 return a.loop_id == b.loop_id &&
54 a.local_id == b.local_id &&
55 a.gen == b.gen;
56}
57
58static inline int
59xtc_pid_is_none(xtc_pid_t p)
60{
61 return p.loop_id == 0 && p.local_id == 0 && p.gen == 0;
62}
63
64/*
65 * A pid published from one thread and read on others. A 16-byte pid
66 * does not fit one lock-free atomic word (C11 _Atomic of 16 bytes is a
67 * libatomic call, and MSVC C has none), so this is a seqlock: a reader
68 * never sees a torn pid. Initialize with XTC_ATOMIC_PID_INIT (or zero
69 * memory: it then holds XTC_PID_NONE). Stores from several threads are
70 * safe (they serialize on the sequence word); a load spins only while a
71 * store is in progress. The fields are private.
72 */
73typedef struct xtc_atomic_pid {
74 uint64_t seq_;
75 uint64_t w_[2];
77#define XTC_ATOMIC_PID_INIT { 0, { 0, 0 } }
78
79/*
80 * Match callback for selective receive. Inspects an envelope's
81 * data + size and returns:
82 * 1 -> consume this envelope (the receive call returns it)
83 * 0 -> skip; envelope stays in the save queue for the next receive
84 */
85typedef int (*xtc_match_fn)(const void *data, size_t size, void *user_data);
86
87/*
88 * Process entry function. The proc runs as a coroutine and the
89 * function returns nothing; exit happens by returning from the entry,
90 * by calling xtc_exit, or by being killed via a link.
91 */
92typedef void (*xtc_proc_fn)(void *arg);
93
94typedef struct xtc_proc_opts {
95 size_t size; /* sizeof(xtc_proc_opts_t): XTC_PROC_OPTS_INIT */
96 const char *name; /* optional, for debug */
97 size_t mailbox_cap; /* 0 = default */
98 /* Mailbox watermark: when an accepted message brings the depth to
99 * this percent of mailbox_cap (1..100; 0 = disabled), the callback
100 * fires once on the rising edge, so the app can shed load before
101 * the hard cap rejects with XTC_E_AGAIN. The callback runs on the
102 * sender's thread, outside the mailbox lock; keep it cheap and do
103 * not block. */
104 int mailbox_watermark_pct;
105 /*
106 * Work-stealing: 0 (default) pins the proc's coroutine to its
107 * spawn loop. 1 makes the coroutine work-stealable: an idle loop
108 * may steal it (only while parked/scheduled, never mid-instruction)
109 * to rebalance runnable work across a multi-loop executor. The
110 * proc's identity (pid), supervision (links/monitors/DOWN), recovery
111 * frame, and mailbox are unaffected -- only the carrier loop can
112 * change, and only at a yield point. See xtc_proc(3) MIGRATION.
113 */
114 int migratable;
115 void (*mailbox_watermark_fn)(xtc_pid_t self, size_t depth,
116 size_t cap, void *user);
117 void *mailbox_watermark_user;
118 /*
119 * L1 proportional-share scheduler: place this proc's task in a
120 * scheduling CLASS created with xtc_exec_class_create. NULL (the
121 * default) is the implicit plain-FIFO class -- zero overhead. A
122 * non-NULL handle weights the proc's CPU share against the other
123 * classes on its loop. INSPIRED BY Glommio.
124 */
125 xtc_exec_class_t sched_class;
127
128/* All defaults. Add fields with a designated initializer that sets .size
129 * too: { .size = sizeof(xtc_proc_opts_t), .name = "w" }. A NULL opts
130 * pointer also means all defaults. */
131#define XTC_PROC_OPTS_INIT { .size = sizeof(xtc_proc_opts_t) }
132
133/* Mailbox statistics snapshot (see xtc_proc_mailbox_stats). */
134typedef struct xtc_mailbox_stats {
135 size_t size; /* sizeof(xtc_mailbox_stats_t): XTC_MAILBOX_STATS_INIT */
136 size_t depth; /* messages currently in the mailbox */
137 size_t saved; /* messages held in the selective-receive
138 * save queue (inspected, not yet matched) */
139 size_t peak; /* high-water mailbox depth ever reached */
140 size_t cap; /* capacity bound on depth + saved (0 = none) */
141 uint64_t recv_total; /* messages accepted over the proc's life */
142 uint64_t drop_total; /* messages rejected (full / dead) */
144
145#define XTC_MAILBOX_STATS_INIT { .size = sizeof(xtc_mailbox_stats_t) }
146
147/*
148 * PUBLIC: int xtc_proc_spawn __P((xtc_loop_t *, xtc_proc_fn, void *, const xtc_proc_opts_t *, xtc_pid_t *));
149 * PUBLIC: int xtc_proc_spawn_link __P((xtc_loop_t *, xtc_proc_fn, void *, const xtc_proc_opts_t *, xtc_pid_t *));
150 * PUBLIC: int xtc_proc_spawn_monitor __P((xtc_loop_t *, xtc_proc_fn, void *, const xtc_proc_opts_t *, xtc_pid_t *, uint64_t *));
151 * PUBLIC: xtc_pid_t xtc_self __P((void));
152 * PUBLIC: xtc_pid_t xtc_atomic_pid_load __P((const xtc_atomic_pid_t *));
153 * PUBLIC: void xtc_atomic_pid_store __P((xtc_atomic_pid_t *, xtc_pid_t));
154 * PUBLIC: int xtc_proc_set_userdata __P((void *));
155 * PUBLIC: void *xtc_proc_userdata __P((void));
156 * PUBLIC: int xtc_proc_set_class __P((xtc_exec_class_t));
157 * PUBLIC: int xtc_send __P((xtc_pid_t, const void *, size_t));
158 * PUBLIC: int xtc_send_owned __P((xtc_pid_t, void *, size_t));
159 * PUBLIC: int xtc_recv __P((void **, size_t *, int64_t));
160 * PUBLIC: int xtc_recv_match __P((xtc_match_fn, void *, void **, size_t *, int64_t));
161 * PUBLIC: int xtc_recv_correlate __P((const void *, size_t, int, xtc_msg_t *, int *, int64_t));
162 * PUBLIC: int xtc_proc_wait_fd __P((int, uint32_t, int64_t, uint32_t *));
163 * PUBLIC: int xtc_proc_sleep __P((int64_t));
164 * PUBLIC: int xtc_exit_self __P((int));
165 * PUBLIC: int xtc_exit_pid __P((xtc_pid_t, int));
166 * PUBLIC: int xtc_exit_pid_deadline __P((xtc_pid_t, int, int64_t, int *));
167 * PUBLIC: int xtc_proc_wake __P((xtc_pid_t));
168 * PUBLIC: int xtc_link __P((xtc_pid_t));
169 * PUBLIC: int xtc_unlink __P((xtc_pid_t));
170 * PUBLIC: int xtc_proc_trap_exit __P((int));
171 * PUBLIC: int xtc_monitor __P((xtc_pid_t, uint64_t *));
172 */
173
174XTC_API int xtc_proc_spawn(xtc_loop_t *loop, xtc_proc_fn fn, void *arg,
175 const xtc_proc_opts_t *opts, xtc_pid_t *out_pid);
176
177/*
178 * Atomic spawn + link / spawn + monitor (Erlang spawn_link /
179 * spawn_monitor). Identical to xtc_proc_spawn, but the parent<->child
180 * relationship is established BEFORE the child is made runnable, so
181 * there is no window in which the child exists but is not yet
182 * linked/monitored -- even if the child runs and exits immediately,
183 * its EXIT/DOWN is delivered (no XTC_DOWN_NOPROC race that a
184 * spawn-then-link/monitor idiom can hit). The CALLER MUST be a
185 * process (xtc_self() != NONE); returns XTC_E_INVAL otherwise.
186 *
187 * _link: bidirectional fate, exactly like calling xtc_link(child)
188 * the instant the child is born -- an abnormal exit on either
189 * side kills the other, or delivers it an EXIT message if it
190 * traps exits (see xtc_link / xtc_proc_trap_exit).
191 * _monitor: unidirectional; the caller receives a DOWN (with *out_ref
192 * as the monitor reference) when the child exits, exactly
193 * like xtc_monitor(child).
194 */
195XTC_API int xtc_proc_spawn_link(xtc_loop_t *loop, xtc_proc_fn fn, void *arg,
196 const xtc_proc_opts_t *opts, xtc_pid_t *out_pid);
197XTC_API int xtc_proc_spawn_monitor(xtc_loop_t *loop, xtc_proc_fn fn,
198 void *arg, const xtc_proc_opts_t *opts,
199 xtc_pid_t *out_pid, uint64_t *out_ref);
200
201/* From inside a process, return its pid; from outside, returns NONE. */
202/*
203 * Asynchronous cross-process exit signal. Sets a kill flag on the
204 * target proc; the target raises the exit at its next yield/recv
205 * point with the supplied reason. Idempotent (first call wins).
206 * Returns XTC_E_INVAL if the target is unknown or already dead.
207 */
208XTC_API int xtc_exit_pid(xtc_pid_t target, int reason);
209
210/*
211 * Kill-delivery outcome, reported by xtc_exit_pid_deadline.
212 *
213 * xtc_exit_pid is FIRE-AND-FORGET: it returns XTC_OK once the flag is
214 * set, which says nothing about whether the target ever acted on it.
215 * That is fine for a cooperative sibling, but it leaves a supervisor
216 * unable to distinguish the two cases it most needs to tell apart --
217 * "the fiber is unwinding" from "the fiber is wedged inside an
218 * xtc_uncancelable() region, where the kill is deferred with no bound
219 * and will never fire." Guessing after a fixed timeout is the only
220 * option today; these statuses replace the guess with an answer.
221 */
222enum xtc_kill_status {
223 /* The target is no longer ALIVE: it observed the kill and left its
224 * body, or it was already dead (a pid that was never issued is
225 * XTC_E_INVAL, not DELIVERED). NOT proof that at-exit
226 * hooks finished, that the stack was reclaimed, or even that THIS
227 * request's reason was the one delivered -- see the long note on
228 * xtc_exit_pid_deadline below before using this to release a
229 * shared resource or start a replacement. */
230 XTC_KILL_DELIVERED = 0,
231 /* Still masked when the deadline expired: the kill is LATCHED and
232 * will fire if the mask ever drops -- but the fiber is inside a
233 * critical section and is not obliged to leave it. A supervisor
234 * should treat this as "wedged, escalate", NOT "try again": a
235 * second xtc_exit_pid is a no-op (first call wins). Escalation
236 * means taking down a larger unit -- see the "Killing a fiber that
237 * mutates shared state" section of xtc_proc(3). */
238 XTC_KILL_DEFERRED = 1,
239 /* Reported as soon as a nonzero mask depth with a latched kill is
240 * OBSERVED, which can be well before the supplied deadline -- so
241 * DEFERRED is not evidence that the full timeout elapsed, and a
242 * brief mask (any xtc_uncancelable / xtc_mask_enter region) is not
243 * by itself evidence of a wedge. Sample mask_depth /
244 * mask_deferred over time (xtc_proc_info) to tell a transient mask
245 * from a stuck one. */
246 /* Not masked, but still alive at the deadline: it simply has not
247 * reached a yield/recv point yet (a CPU-bound loop with no yield,
248 * or a park that outlives the deadline). Usually means "wait
249 * longer"; a fiber that never yields will never be killable.
250 * NOT proof the target is unmasked for the whole interval: a fiber
251 * that is masked but has not yet OBSERVED the kill can land here
252 * too. And when the caller is itself a fiber on a starved loop,
253 * this deadline is an observation budget, not a wall-clock bound --
254 * call from a plain thread if you need the latter. */
255 XTC_KILL_TIMEOUT = 2
256};
257
258/*
259 * xtc_exit_pid with a bounded wait and a REPORT of what happened.
260 *
261 * Identical to xtc_exit_pid (same idempotent flag, same reason
262 * encoding), but waits up to timeout_ns for the target to act, then
263 * writes one of the enum xtc_kill_status values to *out_status
264 * (out_status may be NULL if only the wait is wanted).
265 *
266 * A zero or negative timeout polls once and reports immediately.
267 * The CALLER MUST NOT be the target (killing yourself is
268 * xtc_exit_self); returns XTC_E_INVAL for that, for XTC_PID_NONE, and
269 * for an unknown pid. A target that is already dead reports
270 * XTC_KILL_DELIVERED with XTC_OK.
271 *
272 * Callable from a fiber or a plain thread. On a fiber the wait yields
273 * (xtc_proc_sleep); off one it sleeps the OS thread, so a supervisor
274 * thread can use it directly. Returns XTC_OK whenever a status was
275 * determined -- the STATUS, not the return code, says whether the kill
276 * landed.
277 *
278 * IMPORTANT -- WHAT XTC_KILL_DELIVERED DOES *NOT* PROVE. It means the
279 * target is no longer ALIVE, i.e. it observed the kill and left its
280 * body. It is NOT proof that the proc's at-exit hooks and scope
281 * finalizers have COMPLETED, and it is NOT proof that the runtime has
282 * reclaimed the proc's task/fiber stack.
283 *
284 * Why: the exit path clears `alive` BEFORE it runs the at-exit list
285 * (see proc_exit in src/ptc/proc.c), and this call reports DELIVERED as
286 * soon as the pid no longer resolves to a live proc. A hook that parks
287 * -- on a lock, an fd, a mailbox -- therefore leaves cleanup IN
288 * PROGRESS while this function already returned DELIVERED. An earlier
289 * version of this comment said DELIVERED meant the hooks had run; that
290 * was stronger than the implementation and is corrected here.
291 *
292 * So do NOT use DELIVERED as the signal to release, reset, or hand a
293 * shared resource to a replacement -- that is exactly the overlap it
294 * cannot rule out. The library itself does not rely on `!alive` for
295 * that purpose: arena-group discard waits for the pid to leave the proc
296 * TABLE (see src/ptc/mctx.c), which happens only after the at-exit list
297 * has returned. Use that same stronger condition when you need
298 * cleanup-complete, not merely kill-accepted.
299 *
300 * DELIVERED is also returned for a target that was already dead (still
301 * unwinding, or long gone), so it cannot distinguish "this request's
302 * reason was delivered" from "it had already died of something else".
303 * Treat it as "not running any more", nothing more. A pid this
304 * runtime never issued is XTC_E_INVAL, exactly as for xtc_exit_pid;
305 * releases before 1.50 reported XTC_OK + DELIVERED for it, and
306 * XTC_E_INVAL for a target that was still running its at-exit hooks.
307 * A pid whose loop has since been finalized cannot be told from one
308 * never issued, and is XTC_E_INVAL too.
309 *
310 * Nor does it mean shared state the fiber was mutating is consistent:
311 * releasing a lock does not undo a half-finished mutation. If a fiber
312 * can be killed mid-mutation of state other fibers keep using, an async
313 * kill is the wrong tool -- see xtc_proc(3).
314 */
315XTC_API int xtc_exit_pid_deadline(xtc_pid_t target, int reason,
316 int64_t timeout_ns, int *out_status);
317
318/*
319 * Resume a process parked in xtc_proc_wait_fd / xtc_recv, from ANY OS
320 * thread (including a thread libxtc does not manage). The explicit
321 * "poke the target loop" primitive: after a foreign thread makes a
322 * watched condition true (writes a self-pipe an fd-park watches, sets
323 * an embedder latch, completes an async read), xtc_proc_wake(pid)
324 * nudges the target loop out of its I/O wait so the parked proc
325 * re-checks its condition. It delivers no message; the woken proc just
326 * re-evaluates, so a spurious wake is always safe. Returns XTC_OK
327 * (including when the target is not parked, already runnable, or gone),
328 * XTC_E_INVAL for XTC_PID_NONE.
329 */
330XTC_API int xtc_proc_wake(xtc_pid_t target);
331
332XTC_API xtc_pid_t xtc_self(void);
333
334/* Load / store an xtc_atomic_pid_t (see above). At least acquire /
335 * release (what the storing thread wrote before the store is visible to
336 * a thread whose load returned that pid), and both take part in the
337 * single seq_cst total order, like an atomic_store / atomic_load. */
338XTC_API xtc_pid_t xtc_atomic_pid_load(const xtc_atomic_pid_t *a);
339XTC_API void xtc_atomic_pid_store(xtc_atomic_pid_t *a, xtc_pid_t p);
340
341/*
342 * Per-proc opaque consumer pointer, on the CALLING proc.
343 *
344 * xtc_proc_set_userdata stores an opaque `void *` on the calling proc
345 * (NULL by default); xtc_proc_userdata reads it back. The pointer
346 * belongs to the proc, not the loop or OS thread, so it SURVIVES
347 * work-stealing migration -- a migratable proc
348 * (xtc_proc_opts_t.migratable) that resumes on a different loop still
349 * reads its own value. This is the supported way to associate
350 * per-proc consumer state that must track the running proc across a
351 * carrier change (keyed implicitly by xtc_self()).
352 *
353 * O(1), allocation-free, lock-free: a plain field access on the
354 * calling proc, which is running on this thread by definition. The
355 * runtime never dereferences or frees the pointer -- the consumer owns
356 * whatever it points to (the same lifetime contract as
357 * xtc_proc_at_exit's arg); it is simply dropped when the proc exits.
358 *
359 * xtc_proc_set_userdata returns XTC_OK, or XTC_E_INVAL when called off
360 * a proc (xtc_self() == XTC_PID_NONE). xtc_proc_userdata returns NULL
361 * off a proc.
362 */
363XTC_API int xtc_proc_set_userdata(void *ud);
364XTC_API void *xtc_proc_userdata(void);
365
366/*
367 * L1 proportional-share scheduler: place the CALLING proc's task in the
368 * scheduling class `cls` (created with xtc_exec_class_create on the
369 * proc's loop). The runtime-side equivalent of setting
370 * xtc_proc_opts_t.sched_class at spawn, but callable from inside a
371 * running proc so a proc can reclassify itself. Returns XTC_OK, or
372 * XTC_E_INVAL called off a proc or with a handle that is not on the
373 * calling proc's loop. INSPIRED BY Glommio's task-queue placement.
374 */
375XTC_API int xtc_proc_set_class(xtc_exec_class_t cls);
376
377/*
378 * Send a message. Copies `size` bytes from `data` (once: into a pooled
379 * envelope up to 256 bytes, else into a buffer the receiver is then handed
380 * without a second copy); the caller retains ownership of `data`. To send
381 * without any copy, build the message in an xtc_malloc'd buffer and use
382 * xtc_send_owned below. Returns:
383 * XTC_OK queued successfully
384 * XTC_E_INVAL NULL data with non-zero size, or stale/unknown pid
385 * XTC_E_AGAIN target mailbox at capacity
386 * XTC_E_RESOURCE global slot cap (XTC_RES_CHAN_SLOTS) hit
387 *
388 * BACKPRESSURE CONTRACT -- read this. Mailboxes are bounded (the
389 * cap is xtc_proc_opts_t.mailbox_cap, default 4096). This is
390 * deliberate: an unbounded mailbox is how an actor system OOMs when
391 * a fast sender outruns a slow receiver (the classic unbounded-mailbox failure).
392 * The price is that send can fail with XTC_E_AGAIN when the target
393 * is full, and a dropped XTC_E_AGAIN is a SILENT MESSAGE LOSS.
394 *
395 * Senders MUST check the return value and decide a policy:
396 * - retry later (re-arm on a timer, or yield and resend),
397 * - shed load (drop the message and account it),
398 * - apply end-to-end flow control (e.g. stop reading the upstream
399 * socket until the target drains -- see examples/07_kaka for a
400 * credit-based scheme), or
401 * - treat it as fatal for a must-deliver path.
402 * Ignoring the return is a bug, not a shortcut.
403 *
404 * WAKE GUARANTEE. A successful send WAKES a receiver parked in
405 * xtc_recv / xtc_recv_match / xtc_proc_wait_fd, and this holds from ANY
406 * thread -- fiber-to-fiber, cross-loop, and from a plain OS thread that
407 * libxtc knows nothing about (no loop, no proc identity, so the
408 * envelope's `from` is XTC_PID_NONE). There is no weaker cross-thread
409 * mode: the receiver arms its waker under the same mailbox lock that
410 * observed the mailbox empty, so a delivery either finds the waker armed
411 * and fires it, or lands before the receiver commits to parking and the
412 * receiver sees the message on its own re-check. A cross-thread wake
413 * posts to the target loop's inbox and pings its I/O backend, so a loop
414 * blocked in epoll_wait / io_uring / kqueue / IOCP returns and
415 * re-polls. If a send returns XTC_OK and the target then stays parked
416 * with a non-empty mailbox, that is a libxtc bug -- report it.
417 *
418 * A parked receiver is therefore never left holding an undelivered
419 * message, but note what the guarantee does NOT promise: it says the
420 * receiver is made runnable, not that it runs before the sender
421 * continues. Send is asynchronous and never blocks waiting for a
422 * receive.
423 */
424XTC_API int xtc_send(xtc_pid_t to, const void *data, size_t size);
425
426/*
427 * Send a message WITHOUT copying it: move `buf` itself into the target's
428 * mailbox. `buf` must come from libxtc's allocator (xtc_malloc /
429 * xtc_calloc / xtc_realloc, or a buffer libxtc handed you to free with
430 * xtc_free, e.g. one from xtc_recv); the receiver gets the same pointer
431 * from xtc_recv / xtc_recv_match / xtc_recv_correlate and frees it with
432 * xtc_free. Zero copies end to end, at any size, same-loop or
433 * cross-thread.
434 *
435 * OWNERSHIP: on XTC_OK the buffer belongs to the library (and then to
436 * the receiver); the caller must not touch it again. On ANY error the
437 * caller still owns it, unchanged, and must free or retry it -- the same
438 * XTC_E_AGAIN backpressure contract as xtc_send. buf == NULL is allowed
439 * only with size == 0 (an empty message).
440 *
441 * Returns the same codes as xtc_send. Unlike xtc_send it never returns
442 * XTC_E_NOMEM for the payload (there is nothing to allocate but the
443 * fixed-size envelope header, which may still fail).
444 */
445XTC_API int xtc_send_owned(xtc_pid_t to, void *buf, size_t size);
446
447/*
448 * Receive the next envelope from this process's mailbox. The caller
449 * owns the returned buffer and frees it with xtc_free. A message from
450 * xtc_send_owned, or an xtc_send larger than 256 bytes, is handed over
451 * in the buffer it already travels in (no copy, no allocation here); a
452 * small xtc_send payload is copied out of its pooled envelope. Blocks
453 * (yields the coroutine) up to
454 * timeout_ns; -1 is
455 * indefinite, 0 is non-blocking.
456 *
457 * Returns:
458 * XTC_OK *out / *size set
459 * XTC_E_AGAIN timeout fired with no message
460 * XTC_E_INVAL called outside a process
461 */
462XTC_API int xtc_recv(void **out, size_t *out_size, int64_t timeout_ns);
463
464/*
465 * Selective receive. match_fn is called for each envelope in
466 * arrival order; the first one for which match_fn returns 1 is
467 * delivered. Non-matching envelopes are kept in the save queue.
468 */
469XTC_API int xtc_recv_match(xtc_match_fn match_fn, void *user_data,
470 void **out, size_t *out_size,
471 int64_t timeout_ns);
472
473/*
474 * Receive `n_expected` messages whose leading `corr_size` bytes
475 * match `corr_value`. The first `n_expected` matching messages
476 * are delivered as an array via `out_msgs[]`; non-matching
477 * messages stay in the mailbox / save queue for subsequent
478 * receives. Returns XTC_OK on full collection; XTC_E_AGAIN if
479 * the timeout fires before n_expected matches arrive (in which
480 * case `*out_n` is the number actually collected; out_msgs[0..*out_n]
481 * are still owned by the caller and must be freed).
482 *
483 * This is the canonical helper for fork-join and request-reply
484 * patterns: pick a correlation id, send N children a request
485 * containing that id, wait for N replies whose first corr_size
486 * bytes equal the id. Avoids manual save-queue management.
487 *
488 * Each delivered message conforms to the same ownership contract
489 * as xtc_recv: the caller owns the buffer and must free() it.
490 */
491typedef struct xtc_msg {
492 void *data;
493 size_t size;
494} xtc_msg_t;
495
496XTC_API int xtc_recv_correlate(const void *corr_value, size_t corr_size,
497 int n_expected,
498 xtc_msg_t *out_msgs,
499 int *out_n,
500 int64_t timeout_ns);
501
502/*
503 * Wait until ANY of the following becomes true:
504 * - the given fd has any of `interest` bits set
505 * (XTC_IO_READABLE / WRITABLE / ERR / HUP),
506 * - a message arrives in the calling proc's mailbox,
507 * - the timeout elapses (only if `timeout_ns >= 0`),
508 * - the proc is killed (xtc_exit_pid raises the exit as usual).
509 *
510 * Returns:
511 * XTC_OK on a non-timeout wakeup. *out_revents has the
512 * XTC_IO_* bits that fired plus XTC_WAIT_MAILBOX if
513 * a message is queued. Multiple bits can be set if
514 * more than one source raced to wake.
515 * XTC_E_AGAIN timeout fired with nothing else. *out_revents has
516 * XTC_WAIT_TIMEOUT.
517 * XTC_E_INVAL bad args (NULL out_revents, fd<0, interest == 0, an
518 * fd that is not open -- POSIX), called from
519 * outside a process, OR the fd could not be registered
520 * because ANOTHER waiter already holds a live
521 * registration for it on this loop's io. One fd, one
522 * waiter: two procs cannot wait on the same fd on the
523 * same loop at the same time. A closed fd was reported
524 * as XTC_E_INTERNAL (epoll) or as a timeout / XTC_IO_ERR
525 * wake (io_uring) before 1.50.
526 * XTC_E_NOMEM / XTC_E_RESOURCE / XTC_E_NOSYS
527 * propagated unchanged from the fd registration.
528 * XTC_E_INTERNAL
529 * a libxtc invariant broke. This is now reserved for
530 * genuinely unexpected failures: it previously ALSO
531 * covered every registration error above, which left a
532 * consumer unable to tell a bad argument from resource
533 * pressure, and led one to classify the opaque code as a
534 * spurious wake and retry it forever.
535 *
536 * EVERY non-XTC_OK return above means the proc did NOT park. None of
537 * them is a wake, and none should be retried in a tight loop: treat an
538 * error as an error. (A spurious wake IS possible, but it arrives as
539 * XTC_OK -- see the note on re-checking your condition.)
540 *
541 * The fd is auto-unregistered before return -- including on the
542 * cancellation path, and including when the fiber MIGRATED while parked
543 * (the unregister is routed to the io that owns the registration, and
544 * deferred to its owning thread when that is not this one). A
545 * subsequent wait on the SAME fd is therefore expected to succeed, and a
546 * pending deferred unregister no longer makes it fail: the re-register
547 * consumes it. The caller never needs to drive a poll or drain step to
548 * make that true.
549 *
550 * The mailbox is left untouched (caller still calls xtc_recv to
551 * actually drain).
552 */
553#define XTC_WAIT_MAILBOX 0x10000u /* in out_revents only */
554#define XTC_WAIT_TIMEOUT 0x20000u /* in out_revents only */
555
556XTC_API int xtc_proc_wait_fd(int fd, uint32_t interest, int64_t timeout_ns,
557 uint32_t *out_revents);
558
559/* Sleep the calling process for at least ns nanoseconds by parking it
560 * on a timer (the loop runs other work meanwhile -- it does not block
561 * the thread). Unlike a timed xtc_recv it does not touch the mailbox.
562 * Returns XTC_E_INVAL if not called from a process. */
563XTC_API int xtc_proc_sleep(int64_t ns);
564
565/* Explicit exit from inside a process; reason is delivered via
566 * EXIT/DOWN signals to linked / monitoring procs. */
567XTC_API int xtc_exit_self(int reason);
568
569/*
570 * Link / unlink: bidirectional fate (BEAM semantics, since 1.55.1).
571 * When a linked proc exits with a NON-normal reason (!= 0), each linked
572 * peer that does not trap exits is killed with the same reason (as if by
573 * xtc_exit_pid). A normal exit (reason 0) is silent to a non-trapping
574 * peer. A peer that traps exits (xtc_proc_trap_exit(1)) is never killed
575 * by a link; it receives an 'E' message for every linked exit, normal or
576 * not (decode with xtc_down_decode_ex; ref is 0).
577 *
578 * Before 1.55.1 a link never killed anything: every peer received an 'E'
579 * message for every exit, normal included. Code that links and then
580 * waits for that 'E' must now call xtc_proc_trap_exit(1) first.
581 */
582XTC_API int xtc_link(xtc_pid_t other);
583XTC_API int xtc_unlink(xtc_pid_t other);
584
585/*
586 * Set whether the CALLING proc traps exits (default off). Returns the
587 * previous setting (0 or 1), or XTC_E_INVAL off a proc.
588 */
589XTC_API int xtc_proc_trap_exit(int on);
590#define XTC_HAVE_PROC_TRAP_EXIT 1
591
592/* ---- Monitor DOWN reasons ------------------------------------------
593 *
594 * A monitor's DOWN carries an int `reason` describing how the target
595 * ended. The reason space is:
596 *
597 * 0 -- clean exit (the target returned, or called
598 * xtc_exit_self(0)).
599 * > 0 -- an application exit code the target passed to
600 * xtc_exit_self(code) (including a contained fault
601 * that ran xtc_exit_self(sig), which passes the
602 * POSITIVE signal number, e.g. 11 for SIGSEGV).
603 * XTC_DOWN_NOPROC -- the monitor was registered on a target that had
604 * ALREADY exited (the monitor raced the target's
605 * exit), so its real exit reason was already reaped
606 * and is unknown. This is NOT a crash: a short-lived
607 * target that a supervisor monitors just after it
608 * finished delivers this, and it is expected. It is a
609 * distinct value (not XTC_E_NOTFOUND, and outside the
610 * 1..255 signal-number range) precisely so a
611 * supervisor can tell "already gone" apart from a real
612 * fault exit -- a DOWN reason of, say, 11 is a
613 * contained SIGSEGV, whereas XTC_DOWN_NOPROC is not.
614 *
615 * xtc_down_is_noproc(reason) is the readable test for the last case.
616 */
617#define XTC_DOWN_NOPROC (-100000)
618
619static inline int
620xtc_down_is_noproc(int reason)
621{
622 return reason == XTC_DOWN_NOPROC;
623}
624
625/* Monitor: unidirectional notification. out_ref is filled with the
626 * monitor reference; the watcher receives a DOWN message of shape
627 * { uint8_t kind = 'D'; uint64_t ref; xtc_pid_t pid; int reason; }
628 * when the monitored process exits (reason per the DOWN-reason space
629 * above). Registering a monitor on an ALREADY-dead target is not an
630 * error: it delivers an immediate DOWN with reason XTC_DOWN_NOPROC. */
631XTC_API int xtc_monitor(xtc_pid_t target, uint64_t *out_ref);
632
633/* Snapshot a process's mailbox statistics into *out. Returns XTC_OK,
634 * or XTC_E_INVAL if the pid is dead / unknown. Safe to call from any
635 * thread. */
636int xtc_proc_mailbox_stats(xtc_pid_t pid, xtc_mailbox_stats_t *out);
637
638/* ---- R1: per-fiber fault containment ----
639 *
640 * Turns a real synchronous fault (SIGSEGV / SIGBUS / SIGFPE / SIGILL
641 * on POSIX; the equivalent EXCEPTION_* on Windows) inside one
642 * coroutine into an unwind of only that process, leaving siblings on
643 * the same loop untouched -- the runtime support PG's "let it crash"
644 * session containment needs.
645 *
646 * POSIX: sigaltstack + sigaction + siglongjmp. Windows: a Vectored
647 * Exception Handler restores the CONTEXT captured at
648 * xtc_proc_recovery_arm() (no stack unwind). Both paths are
649 * runtime-verified on their hosts: a contained fault outside a
650 * critical section unwinds the one proc and delivers DOWN to its
651 * monitors; a fault inside a critical section escalates to process
652 * abort (POSIX re-raise; Windows EXCEPTION_CONTINUE_SEARCH ->
653 * 0xC0000005), preserving PG's critical-section PANIC semantics.
654 */
655#if (defined(__sun) || defined(__illumos__)) && !defined(__EXTENSIONS__)
656/* illumos/Solaris gate sigjmp_buf / sigsetjmp / siglongjmp behind a
657 * feature-test macro; under -std=c11 (strict ISO C) <setjmp.h> hides
658 * them. A consumer that includes this PUBLIC header need not know to
659 * set the macro itself, so expose the POSIX setjmp surface here before
660 * the include. No effect on other platforms. */
661#define __EXTENSIONS__ 1
662#endif
663#include <setjmp.h>
664
665#if defined(_WIN32)
666#include <windows.h>
667/*
668 * Windows recovery uses CONTEXT capture/restore rather than
669 * setjmp/longjmp. The fault is caught by a Vectored Exception Handler
670 * that restores this saved CONTEXT via EXCEPTION_CONTINUE_EXECUTION --
671 * the OS reloads the thread's registers and resumes at the capture
672 * point. longjmp out of (or via) a VEH is unsafe: on a fiber stack it
673 * walks unwind tables that no longer match and corrupts the CRT; a
674 * context restore does no unwinding at all.
675 */
676typedef struct xtc_recovery_buf { CONTEXT ctx; } xtc_recovery_buf_t;
677#else
678typedef sigjmp_buf xtc_recovery_buf_t;
679#endif
680
681/* Install the process-wide fault handler. On POSIX it registers a
682 * SIGSEGV/SIGBUS/SIGFPE/SIGILL handler on an alternate signal stack
683 * (call once per loop thread for the alt stack; the handler is
684 * installed once). On Windows it registers a Vectored Exception
685 * Handler. Returns XTC_OK on success. */
686int xtc_fault_guard_install(void);
687
688/* Internal arm-slot for the xtc_proc_recovery_arm() macro (POSIX). */
689xtc_recovery_buf_t *__xtc_proc_recovery_slot(void);
690
691#if defined(_WIN32)
692/* Windows recovery-arm helpers (used by the macro below).
693 * __xtc_recovery_prep arms the frame and clears the fired flag;
694 * __xtc_recovery_ctx returns the CONTEXT to capture into;
695 * __xtc_recovery_result returns 0 on the arming pass and the fault
696 * code when the VEH has restored the context. */
697void __xtc_recovery_prep(void);
698CONTEXT *__xtc_recovery_ctx(void);
699int __xtc_recovery_result(void);
700#endif
701
702/*
703 * Arm a recovery frame for the calling process, exactly like
704 * sigsetjmp: returns 0 on the normal path and the fault signal number
705 * (POSIX) or exception code (Windows) when control returns here via a
706 * contained fault. Use it as:
707 *
708 * int sig = xtc_proc_recovery_arm();
709 * if (sig != 0) { // recovered from a contained fault
710 * ... release locks, reset the memory context, close fds ...
711 * xtc_exit_self(reason); // delivers DOWN to the supervisor
712 * }
713 * ... session work ...
714 *
715 * The frame is disarmed automatically when a fault fires it (so a
716 * fault during recovery escalates to process abort); re-arm or
717 * xtc_proc_recovery_disarm() as needed.
718 *
719 * IMPORTANT: containment only unwinds the fiber's CALL STACK. Any
720 * resources the proc held at fault time -- locks, fds, allocations,
721 * buffer pins -- are the recovery block's responsibility to release:
722 * abort any in-progress transaction and release every held lock and
723 * resource before returning. Hold those under an xtc_mctx you
724 * can reset, and release lock-manager locks with the lock manager's
725 * release-all; otherwise a contained fault leaks or, worse, leaves a
726 * lock held and wedges peers.
727 */
728#if defined(_WIN32)
729/* Capture the proc fn's own frame inline (like setjmp), so the VEH can
730 * restore it; the comma expression returns 0 while arming and the
731 * fault code after a contained fault resumes execution here. */
732#define xtc_proc_recovery_arm() \
733 (__xtc_recovery_prep(), \
734 RtlCaptureContext(__xtc_recovery_ctx()), \
735 __xtc_recovery_result())
736#else
737#define xtc_proc_recovery_arm() (sigsetjmp(*__xtc_proc_recovery_slot(), 1))
738#endif
739
740/* Disarm the calling process's recovery frame. */
741void xtc_proc_recovery_disarm(void);
742
743/* Critical section: while crit_depth > 0, a fault is NOT contained --
744 * it escalates to process abort, because shared state may be torn.
745 * Nestable. Mirrors PG's START_CRIT_SECTION / END_CRIT_SECTION. */
746void xtc_proc_critical_enter(void);
747void xtc_proc_critical_leave(void);
748
749/* Register a callback to run when the calling process exits -- on a
750 * normal return OR a contained-fault recovery (after
751 * xtc_proc_recovery_arm -> xtc_exit_self). Callbacks run LIFO,
752 * outside signal context, with the proc still current, BEFORE its
753 * monitors observe DOWN. This is where an embedder guarantees a
754 * faulted session releases what it held -- e.g. register
755 * xtc_lock_release_all so no lock-manager lock outlives the proc, or
756 * a memory-context reset. Up to a small fixed number per proc;
757 * returns XTC_E_RESOURCE past the limit, XTC_E_INVAL off a proc. */
758int xtc_proc_at_exit(void (*fn)(void *), void *arg);
759
760/* A memory context scoped to the calling process: created lazily on
761 * first call, destroyed automatically on proc exit (a backstop so a
762 * faulted session's allocations are reclaimed even if its recovery
763 * block itself faults). Returns NULL off a proc. See xtc_mctx.h. */
764struct xtc_mctx *xtc_proc_mctx(void);
765
766/*
767 * Recovery resource registry.
768 *
769 * A contained fault unwinds the faulting fiber's call stack but frees
770 * NONE of the resources the proc held -- locks stay locked, fds stay
771 * open, memory arenas stay live -- and a leaked lock can wedge every
772 * peer. Register the resources a proc acquires so the runtime can
773 * release them automatically:
774 *
775 * xtc_proc_recovery_track_fd(fd); // close(fd) on cleanup
776 * xtc_proc_recovery_track_mctx(mctx); // xtc_mctx_reset(mctx)
777 * xtc_proc_recovery_track_locks(mgr, locker, release_all);
778 * xtc_proc_recovery_track(fn, arg); // generic fn(arg)
779 *
780 * xtc_proc_recovery_cleanup() releases everything registered (LIFO).
781 * It is the DEFAULT recovery action and is ALSO callable from a custom
782 * recovery block to finish the standard bits after the block's own
783 * application-specific unwinding:
784 *
785 * int sig = xtc_proc_recovery_arm();
786 * if (sig != 0) { // recovered from a contained fault
787 * my_app_abort_txn(); // custom unwinding first
788 * xtc_proc_recovery_cleanup(); // then the registered standard bits
789 * xtc_exit_self(sig);
790 * }
791 *
792 * Or use xtc_proc_recovery_arm_clean(), which performs the cleanup and
793 * xtc_exit_self automatically on the recovered branch (no custom block).
794 *
795 * Registered resources are ALSO released automatically on a NORMAL
796 * proc exit (before the at-exit hooks), so a proc that simply returns
797 * without explicitly releasing still cleans up. Use
798 * xtc_proc_recovery_untrack_fd() when the proc releases an fd itself,
799 * to avoid a double close on a later recovery.
800 *
801 * Each registration returns XTC_OK, XTC_E_RESOURCE past the per-proc
802 * limit, or XTC_E_INVAL off a proc / on a bad argument.
803 */
804int xtc_proc_recovery_track_fd(int fd);
805int xtc_proc_recovery_track_mctx(struct xtc_mctx *mctx);
806int xtc_proc_recovery_track_locks(void *mgr, uint64_t locker,
807 void (*release_all)(void *, uint64_t));
808int xtc_proc_recovery_track(void (*fn)(void *), void *arg);
809int xtc_proc_recovery_untrack_fd(int fd);
810void xtc_proc_recovery_cleanup(void);
811
812/*
813 * Arm a recovery frame whose default recovered action is to release
814 * all tracked resources and exit the proc with the fault code. On the
815 * normal (arming) pass it returns 0 and execution continues into the
816 * session work; on a contained fault it does NOT return -- it runs
817 * xtc_proc_recovery_cleanup() then xtc_exit_self(sig). Use this when
818 * the registered resources are the whole cleanup story; use the bare
819 * xtc_proc_recovery_arm() + a custom block when they are not.
820 */
821#define xtc_proc_recovery_arm_clean() \
822 do { \
823 int __xtc_rsig = xtc_proc_recovery_arm(); \
824 if (__xtc_rsig != 0) { \
825 xtc_proc_recovery_cleanup(); \
826 (void)xtc_exit_self(__xtc_rsig); \
827 } \
828 } while (0)
829
830/* ---- A2: cancellation masking (uncancelable / poll) ----
831 *
832 * MonadCancel's masking discipline, for C. Structured cancellation via
833 * xtc_abort / xtc_exit_pid is cooperative -- a running fiber observes it
834 * only at a yield/recv point. Masking makes it COMPOSABLE: inside an
835 * xtc_uncancelable() region an asynchronous kill delivered at a park
836 * point is DEFERRED, not acted on, and is observed only when the region
837 * returns (mask depth back to 0). This is what lets a resource acquired
838 * in the region always register its release before cancellation unwinds
839 * the fiber -- the guarantee xtc_scope / xtc_bracket rely on.
840 *
841 * Run `body(ud)` with cancellation masked; returns body's return value
842 * (or XTC_E_INVAL if body is NULL). Nests: each call bumps a per-proc
843 * mask counter. Off a proc it simply runs body.
844 *
845 * PUBLIC: int xtc_uncancelable __P((int (*)(void *), void *));
846 */
847XTC_API int xtc_uncancelable(int (*body)(void *), void *ud);
848
849/*
850 * Cats Effect's poll: inside an xtc_uncancelable() body, run `body(ud)`
851 * with the mask temporarily lifted so cancellation IS observed for that
852 * sub-region. A kill already deferred by the enclosing mask fires at
853 * the poll site (before body runs); the caller's mask depth is restored
854 * on return. Outside a masked region it just runs body. Returns body's
855 * value (or XTC_E_INVAL if body is NULL).
856 *
857 * PUBLIC: int xtc_cancel_poll __P((int (*)(void *), void *));
858 */
859XTC_API int xtc_cancel_poll(int (*body)(void *), void *ud);
860
861/*
862 * True iff cancellation is pending for the calling proc -- either an
863 * async kill has been requested (xtc_exit_pid) or one was deferred by a
864 * mask. A cheap, allocation-free probe for a masked region that wants
865 * to unwind early and cleanly rather than wait for the park point.
866 * Returns 0 off a proc.
867 *
868 * PUBLIC: int xtc_cancel_requested __P((void));
869 */
870XTC_API int xtc_cancel_requested(void);
871
872/*
873 * Paired form of the cancellation mask, for straight-line code that
874 * cannot be expressed as a callback body.
875 *
876 * xtc_uncancelable() brackets a `body(ud)` lambda; that is the right
877 * shape for a self-contained region, but not for a macro pair wrapped
878 * around arbitrary code that goto/break/returns or longjmps out
879 * (PostgreSQL's START_CRIT_SECTION() / END_CRIT_SECTION() over WAL
880 * insertion, buffer writes, page splits -- straight-line code that
881 * cannot be hoisted into a callback without a wholesale control-flow
882 * rewrite). These expose the same per-proc mask counter directly:
883 *
884 * xtc_mask_enter() bumps the mask depth; a kill delivered at a
885 * later park point is DEFERRED, not acted on.
886 * xtc_mask_leave() drops the depth (saturating at 0) and, once the
887 * depth is back to 0, honors any kill latched
888 * while masked -- which, exactly like the end of
889 * an xtc_uncancelable() body, may not return
890 * (it unwinds the fiber via xtc_exit_self).
891 *
892 * They NEST: N enters need N leaves, and only the outermost leave can
893 * unwind. xtc_uncancelable() and the pair share the one counter and
894 * compose freely.
895 *
896 * Both return XTC_OK on a proc and XTC_E_INVAL off one (mirroring how
897 * xtc_uncancelable() just runs body off a proc -- there is nothing to
898 * mask, so a bridge macro can ignore the return). UNLIKE
899 * xtc_uncancelable(), which brackets a synchronous body, enter and
900 * leave are separated in time and a yield may occur between them, so
901 * each independently re-anchors onto the running fiber -- a bridge that
902 * enters, yields (any park), and later leaves stays correct even if the
903 * fiber migrated in between.
904 *
905 * A leave with no matching enter is a no-op, not an error: the
906 * saturating decrement means an unbalanced END_CRIT_SECTION() cannot
907 * drive the counter negative and mask cancellation forever.
908 *
909 * PUBLIC: int xtc_mask_enter __P((void));
910 * PUBLIC: int xtc_mask_leave __P((void));
911 */
912XTC_API int xtc_mask_enter(void);
913XTC_API int xtc_mask_leave(void);
914
915/* ---- A1: resource scope / bracket ----
916 *
917 * A blessed, runtime-ENFORCED resource scope. Cats Effect's
918 * Resource/bracket was, for years, a "paper door": a convention the API
919 * carroted you toward but nothing stopped you walking past, leaking a
920 * socket on the cancellation path. xtc_scope makes
921 * "this WILL be released on EVERY exit path" a MECHANISM, not a manner.
922 *
923 * Open a scope on the calling proc, defer finalizers into it, and close
924 * it. Finalizers run LIFO -- on the normal xtc_scope_close, AND on an
925 * error return, xtc_exit_self, an async kill (xtc_exit_pid), or a
926 * fault-guard-contained crash, because the scope is pushed as a marker
927 * on the same recovery registry that releases fds and locks on unwind.
928 * Scopes nest; an outer unwind closes inner-then-outer LIFO.
929 *
930 * xtc_scope_open returns NULL off a proc or on resource exhaustion.
931 */
932typedef struct xtc_scope xtc_scope_t;
933typedef void (*xtc_finalizer_fn)(void *arg);
934
935/*
936 * PUBLIC: xtc_scope_t *xtc_scope_open __P((void));
937 * PUBLIC: int xtc_scope_defer __P((xtc_scope_t *, xtc_finalizer_fn, void *));
938 * PUBLIC: void xtc_scope_close __P((xtc_scope_t *));
939 */
940XTC_API xtc_scope_t *xtc_scope_open(void);
941XTC_API int xtc_scope_defer(xtc_scope_t *s, xtc_finalizer_fn fn, void *arg);
942XTC_API void xtc_scope_close(xtc_scope_t *s);
943
944/*
945 * bracket sugar (acquire -> use -> guaranteed release), the load-bearing
946 * correctness core. `acquire` runs abort-MASKED so `release` is always
947 * registered before cancellation can be observed; `release` then runs on
948 * EVERY exit path of `use` (normal, error, kill, crash). acquire writes
949 * the resource through *res and returns XTC_OK (else bracket returns its
950 * code and nothing else runs); use returns a code that bracket returns;
951 * release is void. `ud` threads a caller context through all three.
952 *
953 * PUBLIC: int xtc_bracket __P((int (*)(void **, void *), int (*)(void *, void *), void (*)(void *, void *), void *));
954 */
955XTC_API int xtc_bracket(int (*acquire)(void **res, void *ud),
956 int (*use)(void *res, void *ud),
957 void (*release)(void *res, void *ud),
958 void *ud);
959
960/* Decode a DOWN signal (delivered to a monitor when its target exits)
961 * into the target pid and exit reason, without hand-rolling the
962 * on-wire layout. The DOWN/EXIT signals are sent packed; a mismatched
963 * (unpacked) mirror struct misreads `reason`. Returns XTC_OK if msg
964 * is a DOWN, XTC_E_INVAL otherwise. out_pid / out_reason may be NULL. */
965int xtc_down_decode(const void *msg, size_t len,
966 xtc_pid_t *out_pid, int *out_reason);
967
968/*
969 * Self-describing DOWN classification (requested by embedders whose
970 * app exit codes and signal numbers would otherwise share the single
971 * `reason` integer: a bare xtc_exit_self(1) was indistinguishable from
972 * a signal-1 (SIGHUP) contained fault). xtc_down_decode_ex fills an
973 * xtc_down_info_t whose `kind` says HOW the target ended, with the
974 * signal number and the app exit code in SEPARATE fields that never
975 * collide. The legacy single-integer xtc_down_decode still works and
976 * returns the same `reason` it always did.
977 */
978typedef enum {
979 XTC_DOWN_KIND_CLEAN = 0, /* target returned or xtc_exit_self(0) */
980 XTC_DOWN_KIND_EXIT = 1, /* xtc_exit_self(code), code in .exit_code */
981 XTC_DOWN_KIND_SIGNAL = 2, /* R1 contained fault, signal in .signal */
982 XTC_DOWN_KIND_NOPROC = 3, /* monitor raced a dead target (benign) */
983 XTC_DOWN_KIND_NOCONNECTION = 4 /* the cross-process channel died
984 * before a clean exit was seen
985 * (xtc_xproc: the fork'd child's
986 * control socket closed) */
987} xtc_down_kind_t;
988
989typedef struct xtc_down_info {
990 size_t size; /* sizeof(xtc_down_info_t): XTC_DOWN_INFO_INIT */
991 xtc_pid_t pid; /* the target that went DOWN */
992 xtc_down_kind_t kind; /* how it ended (never ambiguous) */
993 int signal; /* signal number iff kind == SIGNAL, else 0 */
994 int exit_code; /* app code iff kind == EXIT, else 0 */
995 int reason; /* the legacy xtc_down_decode reason value */
996 uint64_t ref; /* the monitor reference (0 for a link EXIT) */
998
999#define XTC_DOWN_INFO_INIT { .size = sizeof(xtc_down_info_t) }
1000
1001/*
1002 * Decode a DOWN or EXIT signal into a fully-classified xtc_down_info_t.
1003 * Accepts both the monitor DOWN ('D') and the link EXIT ('E') signal
1004 * shapes. Returns XTC_OK on either, XTC_E_INVAL for any other message.
1005 * A monitor need no longer know the producer's encoding convention:
1006 * kind + signal + exit_code are unambiguous by construction.
1007 *
1008 * PUBLIC: int xtc_down_decode_ex __P((const void *, size_t, xtc_down_info_t *));
1009 */
1010XTC_API int xtc_down_decode_ex(const void *msg, size_t len,
1011 xtc_down_info_t *out);
1012
1013/* Predicate + accessor helpers over a legacy `reason` integer, for
1014 * callers that keep using xtc_down_decode. A signal-N fault and a
1015 * bare xtc_exit_self(N) are STILL not distinguishable from `reason`
1016 * alone (that is the whole reason to prefer xtc_down_decode_ex); these
1017 * helpers only classify the two unambiguous sentinels. */
1018static inline int xtc_down_is_signal_reason(int reason)
1019{
1020 return reason >= 1 && reason <= 255;
1021}
1022static inline int xtc_down_is_exit_reason(int reason)
1023{
1024 return reason == 0 || reason >= 256;
1025}
1026
1027/* The internal current-proc context save/restore across a yield
1028 * (__xtc_proc_ctx_save / __xtc_proc_ctx_restore) is library-internal
1029 * (the __ prefix) and lives in "proc_int.h", not in this installed
1030 * public header. */
1031
1032#endif /* XTC_PROC_H */