libxtc 0.4.0
Async concurrency for C: Tokio + Seastar + BEAM, in one library
Loading...
Searching...
No Matches
xtc_app.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_app.h
7 * The L4 application container: a root supervisor + a process
8 * registry + lifecycle plumbing. Models OTP's `application`
9 * concept. An xtc_app owns a loop (creating one if not given
10 * or borrowing one passed in), starts a top-level supervisor
11 * with the configured children, and exposes the registry that
12 * those children can use to find each other by name.
13 *
14 * Typical usage:
15 *
16 * xtc_app_t *app;
17 * xtc_app_opts_t opts = XTC_APP_OPTS_DEFAULT;
18 * xtc_app_create(&opts, &app);
19 * xtc_app_start(app, child_specs, n_children);
20 * xtc_app_run(app); // blocks until app stops
21 * xtc_app_destroy(app);
22 */
23
24#ifndef XTC_APP_H
25#define XTC_APP_H
26
27#include "xtc_export.h"
28
29#include <stddef.h>
30#include <stdint.h>
31
32#include "xtc.h"
33#include "xtc_loop.h"
34#include "xtc_exec.h"
35#include "xtc_orc.h"
36#include "xtc_reg.h"
37
38typedef struct xtc_app xtc_app_t;
39
40typedef struct xtc_app_opts {
41 size_t size; /* sizeof(xtc_app_opts_t) */
42 const char *name; /* optional, for logs */
43 xtc_loop_t *loop; /* optional; NULL = create one */
44 int n_loops; /* >1 = own an N-loop executor and run
45 * the root supervisor on loop 0 (the
46 * other loops host children placed via
47 * xtc_child_spec.loop, and any work the
48 * children spawn via xtc_app_exec); 0/1
49 * = single loop. Ignored when `loop`
50 * is supplied. */
51 int no_tuning_check; /* 1 = skip the
52 * __os_tuning_check() power/kernel-
53 * tuning advisor probe xtc_app_start
54 * runs by default */
55 /* Root-supervisor settings, or NULL for XTC_SUP_OPTS_DEFAULT. A
56 * pointer, not an embedded struct: a sized struct embedded by value
57 * would move every field after it whenever it grew. Read (copied)
58 * by xtc_app_create; need not outlive the call. Its .exec is
59 * ignored -- the app owns its executor. */
60 const xtc_sup_opts_t *sup;
62
63#define XTC_APP_OPTS_DEFAULT { \
64 .size = sizeof(xtc_app_opts_t), \
65 .name = NULL, \
66 .loop = NULL, \
67 .n_loops = 1, \
68 .no_tuning_check = 0, \
69 .sup = NULL \
70}
71
72/*
73 * Graceful drain (PLAN.md 19.20 / 19.27.10).
74 *
75 * xtc_app_shutdown(app, drain_ns, force_ns, report):
76 * 1. sends XTC_APP_SHUTDOWN_MSG to every static child (the children
77 * passed to xtc_app_start) -- test for it with
78 * xtc_app_is_shutdown_msg;
79 * 2. waits up to drain_ns for each to finish on its own (return from
80 * its body, or exit);
81 * 3. stops the supervisor (no more restarts) and force-cancels every
82 * child still there with xtc_exit_pid(pid, XTC_APP_SHUTDOWN_REASON);
83 * 4. waits up to force_ns for each to LEAVE THE PROC TABLE -- the
84 * cleanup-complete signal (at-exit hooks and scope finalizers have
85 * returned), not the weaker "no longer alive";
86 * 5. stops the app's loop/executor so xtc_app_run returns, and fills
87 * *report.
88 * It ALWAYS returns within about drain_ns + force_ns (+ a few ms), even
89 * if a child is masked forever: such a child is reported, not waited
90 * for. Returns XTC_OK when every child left the table, XTC_E_AGAIN when
91 * at least one survived (report->survivor_mask), XTC_E_INVAL for a
92 * NULL app, an app not inside xtc_app_run (nothing could drain), a
93 * negative deadline, a report whose .size is too small, a second
94 * shutdown, a call from one of the app's own supervised children, or a
95 * call from ANY proc on a multi-loop app. Call it from a plain thread
96 * (single-loop: or a non-child proc). Dynamic children
97 * (xtc_sup_add_child) are not drained or reported: the supervisor stop
98 * in step 3 kills them.
99 *
100 * MULTI-LOOP LIMIT: the supervisor stops the executor the moment it
101 * exits (step 3), so a child force-cancelled on a multi-loop app may be
102 * frozen before its cleanup completes; it is then reported as a
103 * SURVIVOR (never falsely as forced) and the call still returns on time.
104 * Children that drain cooperatively in step 2 are unaffected.
105 */
106#define XTC_APP_SHUTDOWN_MSG "$xtc_shutdown"
107#define XTC_APP_SHUTDOWN_REASON 1 /* force-cancel exit reason */
108
109/*
110 * Size-versioned (PLAN.md 18.1): set .size = sizeof(xtc_app_drain_report_t)
111 * before the call (XTC_APP_DRAIN_REPORT_INIT does). The library writes
112 * only the first .size bytes, so fields appended in a later minor release
113 * are invisible to, and harmless for, a caller built against this one.
114 */
115typedef struct xtc_app_drain_report {
116 size_t size; /* IN: sizeof(xtc_app_drain_report_t) */
117 int n_children; /* static children at shutdown */
118 int n_drained; /* finished on their own within drain_ns */
119 int n_forced; /* force-cancelled, then left the table */
120 int n_survivors; /* still in the proc table at return */
121 uint64_t forced_mask; /* bit i = child i was forced (i < 64) */
122 uint64_t survivor_mask; /* bit i = child i survived (i < 64) */
123 int64_t elapsed_ns; /* time spent in the shutdown */
125
126#define XTC_APP_DRAIN_REPORT_INIT { .size = sizeof(xtc_app_drain_report_t) }
127
128/*
129 * PUBLIC: int xtc_app_create __P((const xtc_app_opts_t *, xtc_app_t **));
130 * PUBLIC: void xtc_app_destroy __P((xtc_app_t *));
131 * PUBLIC: int xtc_app_start __P((xtc_app_t *, const xtc_child_spec_t *, int));
132 * PUBLIC: int xtc_app_run __P((xtc_app_t *));
133 * PUBLIC: int xtc_app_stop __P((xtc_app_t *));
134 * PUBLIC: xtc_loop_t *xtc_app_loop __P((const xtc_app_t *));
135 * PUBLIC: xtc_exec_t *xtc_app_exec __P((const xtc_app_t *));
136 * PUBLIC: xtc_reg_t *xtc_app_registry __P((const xtc_app_t *));
137 * PUBLIC: int xtc_app_shutdown __P((xtc_app_t *, int64_t, int64_t, xtc_app_drain_report_t *));
138 * PUBLIC: int xtc_app_drain_on_signal __P((xtc_app_t *, int64_t, int64_t, xtc_app_drain_report_t *));
139 * PUBLIC: int xtc_app_is_shutdown_msg __P((const void *, size_t));
140 */
141
142XTC_API int xtc_app_create(const xtc_app_opts_t *opts, xtc_app_t **out);
143XTC_API void xtc_app_destroy(xtc_app_t *app);
144
145/* Start the root supervisor with `n_children` initial children. */
146XTC_API int xtc_app_start(xtc_app_t *app,
147 const xtc_child_spec_t *children,
148 int n_children);
149
150/* Run the loop until the supervisor exits or xtc_app_stop is called.
151 * On return, xtc_app_destroy may be called. */
152XTC_API int xtc_app_run(xtc_app_t *app);
153
154/* Asynchronously request the app to stop (kicks the root sup). NOT a
155 * drain: the supervisor immediately xtc_exit_pid()s every child and
156 * does not wait for their cleanup. Use xtc_app_shutdown for that. */
157XTC_API int xtc_app_stop(xtc_app_t *app);
158
159/* Bounded graceful drain; see the block comment above. */
160XTC_API int xtc_app_shutdown(xtc_app_t *app, int64_t drain_ns,
161 int64_t force_ns,
162 xtc_app_drain_report_t *report);
163
164/*
165 * OPT-IN: drain the app on SIGTERM or SIGINT. The library installs no
166 * signal handler unless this is called. The handler only write()s one
167 * byte to a non-blocking self-pipe (async-signal-safe, nothing else); a
168 * watcher proc on the app's loop waits on the pipe with xtc_proc_wait_fd
169 * and runs xtc_app_shutdown(app, drain_ns, force_ns, report) there.
170 * `report` (may be NULL) must stay valid until xtc_app_run returns; it
171 * is filled before that. Call after xtc_app_start, before xtc_app_run.
172 * xtc_app_destroy restores the previous dispositions. One app per
173 * process at a time: a second call while one is armed returns
174 * XTC_E_INVAL. XTC_E_NOSYS on Windows.
175 */
176XTC_API int xtc_app_drain_on_signal(xtc_app_t *app, int64_t drain_ns,
177 int64_t force_ns,
178 xtc_app_drain_report_t *report);
179
180/* 1 if (msg, len) is the XTC_APP_SHUTDOWN_MSG drain request, else 0. */
181XTC_API int xtc_app_is_shutdown_msg(const void *msg, size_t len);
182
183XTC_API xtc_loop_t *xtc_app_loop(const xtc_app_t *app);
184
185/* The executor backing a multi-loop app (n_loops > 1), or NULL for a
186 * single-loop app. Children use it to spawn work across loops -- e.g.
187 * a listener spawning a proc per connection round-robin. */
188XTC_API xtc_exec_t *xtc_app_exec(const xtc_app_t *app);
189
190XTC_API xtc_reg_t *xtc_app_registry(const xtc_app_t *app);
191
192#endif /* XTC_APP_H */