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