libxtc 0.4.0
Async concurrency for C: Tokio + Seastar + BEAM, in one library
Loading...
Searching...
No Matches
xtc_osproc.h
1/*-
2 * Copyright (c) 2026, The XTC Project
3 * Use of this source code is governed by the ISC License,
4 * a copy of which is in the file LICENSE in the top-level directory
5 * of this distribution.
6 *
7 * src/inc/xtc_osproc.h
8 * OS-process spawn + control socket + lifecycle (R3).
9 *
10 * This is the "default-to-fork extension tier" backbone: a way to
11 * run work in a separate OS process -- either an exec'd program or
12 * a forked callback -- with a control channel back to the spawning
13 * loop and non-blocking lifecycle management (signal, reap, wait).
14 * Distinct from xtc_proc_spawn, which spawns an in-process fiber.
15 * Use this tier for un-cooperative or untrusted work that must not
16 * run in a loop thread (no in-thread forcible unwind exists), and
17 * for the classic one-backend-per-connection isolation model.
18 *
19 * PLATFORM: POSIX only (fork/exec + pidfd). On Windows every entry
20 * point is present but returns XTC_E_NOSYS (CreateProcess + a
21 * control pipe is a future port); callers must handle the decline.
22 *
23 * Control socket
24 * When opts.ctrl_socket is set, spawn creates an AF_UNIX
25 * SOCK_STREAM socketpair. The PARENT end is returned by
26 * xtc_osproc_ctrl_fd(): it is O_NONBLOCK and O_CLOEXEC and is
27 * pollable with xtc_proc_wait_fd / wrappable with xtc_net, so the
28 * spawning fiber can exchange control messages (framed via
29 * xtc_net_send_frame/recv_frame) without blocking its loop. The
30 * CHILD end is NOT close-on-exec: a forked callback receives it as
31 * the fn(ctrl_fd, ...) argument; an exec'd program inherits it and
32 * finds its number in the environment variable XTC_CTRL_FD.
33 *
34 * Exit notification
35 * On Linux (>= 5.3) spawn opens a pidfd, so xtc_osproc_wait parks
36 * the caller on a readable fd and reaps in O(1) with no SIGCHLD
37 * handler. On platforms without pidfd, wait falls back to a
38 * cooperative waitpid(WNOHANG) poll (it yields/sleeps between
39 * probes, so it never blocks the loop) -- a kqueue EVFILT_PROC
40 * fast path is a future optimisation. Either way the library
41 * installs no process-wide SIGCHLD handler, so it never collides
42 * with an embedder's own signal handling.
43 *
44 * fork() in a threaded process
45 * An xtc executor has multiple loop + offload-pool threads. fork
46 * duplicates only the calling thread; locks held by other threads
47 * are frozen in the child. The exec path is therefore safe (the
48 * child only does async-signal-safe work, then execs a fresh
49 * image). The fn (fork-only) path runs the callback in that
50 * half-initialised child: the callback must restrict itself to
51 * async-signal-safe work until it re-initialises its own runtime.
52 */
53
54#ifndef XTC_OSPROC_H
55#define XTC_OSPROC_H
56
57#include "xtc.h"
58
59#include <stdint.h>
60
61#ifdef __cplusplus
62extern "C" {
63#endif
64
65typedef struct xtc_osproc xtc_osproc_t;
66
67typedef struct xtc_osproc_opts {
68 const char *name; /* label for logs/debug; may be NULL */
69
70 /* Exactly one of argv / fn must be set.
71 *
72 * argv != NULL: the child execvp(argv[0], argv)s a program.
73 * fn != NULL: the child runs fn(ctrl_fd, arg) and _exit()s with
74 * its return value (0..255). ctrl_fd is the child's
75 * control-socket end, or -1 if ctrl_socket == 0. */
76 char *const *argv;
77 int (*fn)(int ctrl_fd, void *arg);
78 void *arg;
79
80 int ctrl_socket; /* 1: create the control socketpair */
82
83/*
84 * Spawn an OS process. Callable from a loop fiber; never blocks the
85 * loop. Returns XTC_OK and *out on success; XTC_E_INVAL (bad opts:
86 * neither or both of argv/fn), XTC_E_NOMEM, or XTC_E_INTERNAL
87 * (fork/socketpair/exec setup failed).
88 *
89 * PUBLIC: int xtc_osproc_spawn __P((const xtc_osproc_opts_t *, xtc_osproc_t **));
90 */
91int xtc_osproc_spawn(const xtc_osproc_opts_t *opts, xtc_osproc_t **out);
92
93/* The child's OS process id, or -1.
94 * PUBLIC: long xtc_osproc_pid __P((const xtc_osproc_t *)); */
95long xtc_osproc_pid(const xtc_osproc_t *p);
96
97/* The parent end of the control socket (O_NONBLOCK, O_CLOEXEC), or -1
98 * if no control socket was requested. Owned by the handle; do not
99 * close it directly -- xtc_osproc_destroy does.
100 * PUBLIC: int xtc_osproc_ctrl_fd __P((const xtc_osproc_t *)); */
101int xtc_osproc_ctrl_fd(const xtc_osproc_t *p);
102
103/* Send signal `sig` to the child (e.g. SIGTERM graceful, SIGKILL hard,
104 * SIGINT cancel). Returns XTC_OK, XTC_E_INVAL, or XTC_E_INTERNAL.
105 * PUBLIC: int xtc_osproc_signal __P((const xtc_osproc_t *, int)); */
106int xtc_osproc_signal(const xtc_osproc_t *p, int sig);
107
108/* Non-blocking reap. If the child has exited, returns XTC_OK and (when
109 * status != NULL) stores the raw waitpid status; if still running,
110 * returns XTC_E_AGAIN. After XTC_OK the child is reaped (no zombie).
111 * PUBLIC: int xtc_osproc_try_wait __P((xtc_osproc_t *, int *)); */
112int xtc_osproc_try_wait(xtc_osproc_t *p, int *status);
113
114/* Cooperative wait: park the calling fiber until the child exits (or
115 * timeout_ns elapses; < 0 = forever), reap it, and store the raw
116 * waitpid status in *status. Never blocks the loop thread. Returns
117 * XTC_OK (exited), XTC_E_AGAIN (timeout), or XTC_E_*. Must be called
118 * from a loop fiber (it parks); off a fiber it polls-and-sleeps.
119 * PUBLIC: int xtc_osproc_wait __P((xtc_osproc_t *, int *, int64_t)); */
120int xtc_osproc_wait(xtc_osproc_t *p, int *status, int64_t timeout_ns);
121
122/* Close the control socket + pidfd and free the handle. Does NOT kill
123 * or reap a still-running child (signal + wait first for a clean
124 * shutdown); if the child already exited it is reaped here so it does
125 * not linger as a zombie.
126 * PUBLIC: void xtc_osproc_destroy __P((xtc_osproc_t *)); */
127void xtc_osproc_destroy(xtc_osproc_t *p);
128
129/* ---- isolated-worker ergonomics ------------------------------------
130 *
131 * The request/reply convenience over the control socket: real MMU
132 * isolation (a separate OS process) with a simple framed call. The
133 * parent spawns a worker, then xtc_osproc_call() ships a request frame
134 * and parks for the reply (never blocking the loop). The child runs
135 * xtc_osproc_serve(), which loops handling framed requests until the
136 * socket closes.
137 */
138
139/* Spawn a forked isolated worker running fn(ctrl_fd, arg), with a
140 * control socket always created. Equivalent to xtc_osproc_spawn with
141 * opts{.name, .fn, .arg, .ctrl_socket=1}.
142 *
143 * PUBLIC: int xtc_osproc_isolated_spawn __P((const char *, int (*)(int, void *), void *, xtc_osproc_t **));
144 */
145int xtc_osproc_isolated_spawn(const char *name,
146 int (*fn)(int ctrl_fd, void *arg), void *arg,
147 xtc_osproc_t **out);
148
149/* Parent side: send a request frame to the worker and park for its
150 * reply frame (allocated with the xtc allocator; free with xtc_free).
151 * max_reply caps the reply (0 = uncapped). Returns XTC_OK, XTC_E_AGAIN
152 * on timeout, or XTC_E_*. Requires a control socket.
153 *
154 * PUBLIC: int xtc_osproc_call __P((xtc_osproc_t *, const void *, size_t, void **, size_t *, size_t, int64_t));
155 */
156int xtc_osproc_call(xtc_osproc_t *p, const void *req, size_t req_len,
157 void **reply, size_t *reply_len, size_t max_reply,
158 int64_t timeout_ns);
159
160/* Child-side handler: given a request, produce a reply. Set *reply
161 * (malloc'd, or NULL for an empty reply) and *reply_len; return XTC_OK
162 * to send it and keep serving, or non-XTC_OK to stop. xtc_osproc_serve
163 * frees *reply after sending. */
164typedef int (*xtc_osproc_handler_fn)(const void *req, size_t req_len,
165 void **reply, size_t *reply_len,
166 void *arg);
167
168/* Child side: serve framed requests on ctrl_fd until the peer closes
169 * (or a handler stops it). Returns the handler's stop code, or XTC_OK
170 * on clean EOF.
171 *
172 * PUBLIC: int xtc_osproc_serve __P((int, xtc_osproc_handler_fn, void *));
173 */
174int xtc_osproc_serve(int ctrl_fd, xtc_osproc_handler_fn handler, void *arg);
175
176#ifdef __cplusplus
177}
178#endif
179
180#endif /* XTC_OSPROC_H */