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