libxtc 0.4.0
Async concurrency for C: Tokio + Seastar + BEAM, in one library
Loading...
Searching...
No Matches
xtc_xproc.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_xproc.h
7 * Cross-fork spawn / send / link / monitor: extend the Erlang-style
8 * process relations across a fork() boundary, not just between fibers
9 * inside one OS process. This is the SINGLE-HOST subset of the
10 * distributed design -- a stepping stone toward --enable-dist, built
11 * over a local socketpair rather than TCP.
12 *
13 * A parent xtc_xspawn()s a child that runs its own xtc runtime; the
14 * two are joined by the xtc_osproc control socketpair. The parent
15 * gets an xtc_xpid_t handle for the child. Over that handle it can:
16 * - xtc_xsend a message that the child's runtime delivers to its
17 * root proc (a byte payload, copied);
18 * - xtc_xmonitor / xtc_xlink to the child so a child CRASH or EXIT
19 * surfaces as a normal xtc DOWN in the monitoring fiber, with the
20 * signal / exit code decoded from the child's waitpid status;
21 * - observe XTC_DOWN_KIND_NOCONNECTION if the control channel dies
22 * before a clean exit is seen (the local analog of node-down).
23 *
24 * The child side runs xtc_xproc_child_main(ctrl_fd, root_fn, arg): it
25 * stands up a relay that receives xtc_xsend payloads and delivers
26 * them to root_fn's proc, and it exits when the parent closes the
27 * channel or the child's own runtime finishes.
28 *
29 * PLATFORM: POSIX only (fork + socketpair + waitpid); the Windows
30 * xtc_osproc entry points decline, and so do these.
31 */
32
33#ifndef XTC_XPROC_H
34#define XTC_XPROC_H
35
36#include "xtc_export.h"
37
38#include <stddef.h>
39#include <stdint.h>
40
41#include "xtc.h"
42#include "xtc_loop.h"
43#include "xtc_proc.h"
44
45typedef struct xtc_xproc xtc_xproc_t;
46
47/* A cross-process pid: the child handle plus (reserved) a node-local pid
48 * within that child. For the MVP a message addresses the child's root
49 * proc; local_pid is reserved for the future per-child pid routing the
50 * distributed design specifies. */
51typedef struct xtc_xpid {
52 xtc_xproc_t *child; /* the fork'd child this pid lives in */
53 xtc_pid_t local_pid; /* reserved: pid within the child (0 = root) */
55
56/* The child's entry: stand up the relay on `ctrl_fd`, spawn root_fn as
57 * the child's root proc, and pump the relay + loop until the parent
58 * closes the channel. Call this from the fn passed to xtc_xspawn (it is
59 * the child-side counterpart). Returns the child's exit code. */
60typedef void (*xtc_xproc_root_fn)(void *arg);
61
62/*
63 * PUBLIC: int xtc_xspawn __P((xtc_loop_t *, const char *, xtc_xproc_root_fn, const void *, size_t, xtc_xproc_t **));
64 * PUBLIC: int xtc_xproc_register_entry __P((const char *, xtc_xproc_root_fn));
65 * PUBLIC: int xtc_xspawn_entry __P((xtc_loop_t *, const char *, const char *, const void *, size_t, xtc_xproc_t **));
66 * PUBLIC: void xtc_xproc_destroy __P((xtc_xproc_t *));
67 * PUBLIC: long xtc_xproc_os_pid __P((const xtc_xproc_t *));
68 * PUBLIC: int xtc_xsend __P((xtc_xproc_t *, const void *, size_t));
69 * PUBLIC: int xtc_xmonitor __P((xtc_xproc_t *, uint64_t *));
70 * PUBLIC: int xtc_xlink __P((xtc_xproc_t *));
71 * PUBLIC: int xtc_xproc_child_main __P((int, xtc_xproc_root_fn, void *));
72 * PUBLIC: int xtc_xproc_win_child_maybe __P((int, char **));
73 */
74
75/*
76 * Fork a child that runs `root_fn`. The child's runtime is stood up by
77 * the library; root_fn receives, as its arg, a pointer to a copy of the
78 * `arg`/`arg_len` bytes the parent passed (or NULL if arg_len == 0).
79 * The parent gets an xtc_xproc handle in *out. Must be called from a
80 * fiber on `loop` (it registers a monitor-relay proc there).
81 *
82 * POSIX ONLY (fork preserves the address space, so a raw function
83 * pointer is meaningful in the child). Returns XTC_E_NOSYS on Windows,
84 * where a child is a fresh process image and a function pointer does not
85 * survive -- use xtc_xspawn_entry there (and portably).
86 */
87XTC_API int xtc_xspawn(xtc_loop_t *loop, const char *name,
88 xtc_xproc_root_fn root_fn,
89 const void *arg, size_t arg_len,
90 xtc_xproc_t **out);
91
92/*
93 * Register a child root function under a name in a process-global table.
94 * The SAME binary (parent and any re-exec'd child) resolves the name to
95 * the same function, so this is the portable bridge that works where a
96 * raw function pointer cannot survive process creation (Windows). Call
97 * it once, early, in code that runs in BOTH the parent and the child
98 * image (e.g. before parsing argv in main, or a constructor). Names are
99 * short strings; re-registering a name replaces the binding. Returns
100 * XTC_OK, XTC_E_INVAL, or XTC_E_RESOURCE if the table is full.
101 */
102XTC_API int xtc_xproc_register_entry(const char *name, xtc_xproc_root_fn fn);
103
104/*
105 * Spawn a child that runs the root function registered under `entry`
106 * (see xtc_xproc_register_entry). It RE-EXECS this binary, which looks
107 * the name up in its own copy of the registry: CreateProcess on Windows,
108 * fork+execve on POSIX (1.50+; before that POSIX forked straight into a
109 * child runtime, which could deadlock the child of a multithreaded
110 * parent). The child side is xtc_xproc_win_child_maybe, which the
111 * binary must call first thing in main() on EVERY platform. On POSIX,
112 * if this image never called it, the entry path falls back to a plain
113 * fork (re-exec would re-run main() in the child) with the async-
114 * signal-safety caveat of xtc_xspawn.
115 * `arg`/`arg_len` are copied and delivered to the root function as its
116 * arg. Otherwise identical to xtc_xspawn (monitor, send, destroy all
117 * work the same). Returns XTC_E_NOTFOUND if `entry` is not registered.
118 */
119XTC_API int xtc_xspawn_entry(xtc_loop_t *loop, const char *name, const char *entry,
120 const void *arg, size_t arg_len, xtc_xproc_t **out);
121
122/* Tear down the handle: terminate (SIGTERM, 2s, SIGKILL) and reap the
123 * child if still running, close the channel, free the handle. NULL is a
124 * no-op; destroying a handle twice is undefined. Safe while monitored. */
125XTC_API void xtc_xproc_destroy(xtc_xproc_t *p);
126
127/* The child's OS pid (for logging), or -1. */
128XTC_API long xtc_xproc_os_pid(const xtc_xproc_t *p);
129
130/* Send a byte payload (copied) to the child's root proc. It arrives in
131 * the child root's mailbox as an ordinary xtc_recv message. Returns
132 * XTC_E_INVAL if the channel is closed. */
133XTC_API int xtc_xsend(xtc_xproc_t *p, const void *msg, size_t len);
134
135/*
136 * Monitor the child from the calling fiber: when the child exits or
137 * crashes -- or its control channel dies -- the caller receives a normal
138 * xtc DOWN message (decode with xtc_down_decode_ex). The DOWN's kind is
139 * XTC_DOWN_KIND_EXIT / _SIGNAL decoded from the child's waitpid status,
140 * or XTC_DOWN_KIND_NOCONNECTION if the channel died first. *out_ref (if
141 * non-NULL) receives the monitor reference. Must be called from a fiber.
142 */
143XTC_API int xtc_xmonitor(xtc_xproc_t *p, uint64_t *out_ref);
144
145/*
146 * Bidirectionally LINK the calling fiber to the child (like xtc_link,
147 * across the fork boundary), and if the linking fiber / parent process
148 * dies the child observes its control channel close and shuts down.
149 * Link semantics are xtc_link's (BEAM, since 1.55.1): an abnormal child
150 * exit (nonzero code, signal) KILLS the linking fiber unless it traps
151 * exits; a clean exit is silent. Call xtc_proc_trap_exit(1) first to
152 * receive an EXIT message instead, for every child exit (decode with
153 * xtc_down_decode_ex). Must be called from a fiber. Use xtc_xmonitor
154 * instead when you want a one-way DOWN without binding the caller's own
155 * fate to the child.
156 */
157XTC_API int xtc_xlink(xtc_xproc_t *p);
158
159/*
160 * Child-side entry. Call this from the child body (the process the fork
161 * lands in) with the inherited control fd, the root proc function, and
162 * its arg. It stands up a child runtime, delivers parent xtc_xsend
163 * payloads into root_fn's mailbox, and returns the child's exit code
164 * when the channel closes or root_fn's proc finishes. Most embedders do
165 * not call this directly -- xtc_xspawn arranges the child side -- but it
166 * is public so a re-exec'd child (a future extension) can re-enter.
167 */
168XTC_API int xtc_xproc_child_main(int ctrl_fd, xtc_xproc_root_fn root_fn, void *arg);
169
170/*
171 * The re-exec'd child image's entry point, on every platform (the name
172 * predates POSIX re-exec). Call it as the first statement of main() --
173 * after registering entries -- in a binary that hosts xtc_xspawn_entry
174 * children: if the cross-process-child sentinel argv is present it
175 * connects the control channel, receives its arg, runs the registered
176 * entry, and _exit()s -- never returning. Otherwise it returns 0 and
177 * normal startup continues; on POSIX it also records that this binary
178 * has the hook, which is what enables re-exec for xtc_xspawn_entry.
179 */
180XTC_API int xtc_xproc_win_child_maybe(int argc, char **argv);
181
182#endif /* XTC_XPROC_H */