xtc_app(3)
---xtc_app(3)
OTP-style application container
| XTC_APP(3) | Library Functions Manual | XTC_APP(3) |
NAME
xtc_app_create,
xtc_app_destroy,
xtc_app_start, xtc_app_run,
xtc_app_stop,
xtc_app_shutdown,
xtc_app_drain_on_signal,
xtc_app_is_shutdown_msg,
xtc_app_loop, xtc_app_exec,
xtc_app_registry — OTP-style
application container
SYNOPSIS
#include <xtc.h>
#include <xtc_app.h>
int
xtc_app_create(const
xtc_app_opts_t *opts,
xtc_app_t **out);
void
xtc_app_destroy(xtc_app_t
*app);
int
xtc_app_start(xtc_app_t
*app, const
xtc_child_spec_t *children,
int n_children);
int
xtc_app_run(xtc_app_t
*app);
int
xtc_app_stop(xtc_app_t
*app);
int
xtc_app_shutdown(xtc_app_t
*app, int64_t
drain_ns, int64_t
force_ns,
xtc_app_drain_report_t
*report);
int
xtc_app_drain_on_signal(xtc_app_t
*app, int64_t
drain_ns, int64_t
force_ns,
xtc_app_drain_report_t
*report);
int
xtc_app_is_shutdown_msg(const
void *msg, size_t
len);
xtc_loop_t *
xtc_app_loop(const
xtc_app_t *app);
xtc_exec_t *
xtc_app_exec(const
xtc_app_t *app);
xtc_reg_t *
xtc_app_registry(const
xtc_app_t *app);
DESCRIPTION
xtc_app is the L4 application container --
a root supervisor plus a process registry plus lifecycle plumbing. Models
OTP's ‘application’ concept.
An app owns:
- A loop (created if not supplied).
- A root supervisor with the configured children.
- A process registry where children can find each other by name (see xtc_reg(3)).
Typical usage:
xtc_app_opts_t opts = XTC_APP_OPTS_DEFAULT;
opts.name = "my_service";
opts.sup.strategy = XTC_SUP_ONE_FOR_ONE;
xtc_app_t *app;
xtc_app_create(&opts, &app);
xtc_child_spec_t kids[] = {
{ .name = "listener", .fn = listener_proc, .arg = NULL,
.policy = XTC_RESTART_PERMANENT },
{ .name = "metrics", .fn = metrics_proc, .arg = NULL,
.policy = XTC_RESTART_TRANSIENT },
};
xtc_app_start(app, kids, 2);
xtc_app_run(app); /* blocks until app stops */
xtc_app_destroy(app);
xtc_app_loop()
returns the application's event loop and
xtc_app_registry()
its process registry.
xtc_app_exec()
returns the underlying xtc_exec(3) executor the
application runs its loop on.
Setting opts.n_loops above 1 makes the app own an N-loop executor; the root supervisor runs on loop 0 and children are placed by xtc_child_spec_t.loop.
Stopping
xtc_app_stop()
is NOT a drain. It asks the root supervisor to stop and returns at once; the
supervisor then
xtc_exit_pid()s
every child with reason 1 and does not wait for their cleanup. A child
masked with
xtc_mask_enter()
never observes that kill, and
xtc_app_run()
then never returns.
Graceful drain
xtc_app_shutdown() is the bounded,
reported drain:
- It sends the message
XTC_APP_SHUTDOWN_MSG("$xtc_shutdown"),sizeof XTC_APP_SHUTDOWN_MSGbytes with the NUL, to every static child (the children given toxtc_app_start()). A child recognizes it withxtc_app_is_shutdown_msg() and finishes by returning from its body. - It waits up to drain_ns for every child to return or exit. A PERMANENT child that returns during the drain is parked, not restarted.
- It stops the supervisor (no more restarts) and force-cancels each
remaining child with
xtc_exit_pid(pid, XTC_APP_SHUTDOWN_REASON). - It waits up to force_ns for every
child to leave the proc table
(
xtc_proc_info() returnsXTC_E_NOTFOUND). That is the cleanup-complete signal: the at-exit hooks and scope finalizers have returned. Neither “not alive” norXTC_KILL_DELIVEREDmeans that. - It stops the app's loop or executor, so
xtc_app_run() returns, and fills *report.
It always returns within about drain_ns + force_ns plus a few milliseconds, even when a child is masked forever: that child is reported as a survivor instead of being waited for. A survivor on an owned loop is freed with the app; on a borrowed loop (opts.loop) it stays parked there.
The report is size-versioned: set
report->size to
sizeof(xtc_app_drain_report_t) first
(XTC_APP_DRAIN_REPORT_INIT does that). The library
writes only the first size bytes, so fields added in a
later minor release do not break an older caller.
typedef struct xtc_app_drain_report {
size_t size; /* IN: sizeof the struct */
int n_children; /* static children */
int n_drained; /* finished within drain_ns */
int n_forced; /* force-cancelled, then left the table */
int n_survivors; /* still in the proc table */
uint64_t forced_mask; /* bit i = child i forced (i < 64) */
uint64_t survivor_mask; /* bit i = child i survived (i < 64) */
int64_t elapsed_ns;
} xtc_app_drain_report_t;
Call
xtc_app_shutdown()
from a plain thread, or, on a single-loop app, from a proc that is not one
of the app's supervised children.
Multi-loop limit: the supervisor stops the
executor the moment it exits (step 3), so a child force-cancelled on an app
with n_loops above 1 can be frozen before its cleanup
completes. It is then reported as a survivor, never falsely as forced, and
the call still returns on time. Children that drain cooperatively in step 2
are not affected. Children added later with
xtc_sup_add_child()
are not drained or reported; the supervisor stop in step 3 kills them.
Signals
The library installs no signal handler by default.
xtc_app_drain_on_signal()
is the opt-in: it installs a SIGTERM and SIGINT handler that only writes one
byte to a non-blocking self-pipe, and spawns a watcher proc on the app's
loop that waits on the pipe with
xtc_proc_wait_fd()
and starts a thread that runs
xtc_app_shutdown(app,
drain_ns, force_ns,
report); xtc_app_run() joins
that thread before it returns. No work runs in the handler.
report (may be NULL) must stay valid until
xtc_app_run() returns. Call it after
xtc_app_start() and before
xtc_app_run(), on an app that owns its loop.
xtc_app_destroy()
restores the previous dispositions. One app per process may be armed at a
time.
RETURN VALUES
xtc_app_shutdown() returns
XTC_OK when every child left the proc table,
XTC_E_AGAIN when at least one survived, and
XTC_E_INVAL for a NULL app, an app that is not
inside xtc_app_run() (nothing could drain), a
negative deadline, a report whose
size is too small, a second call, a call from a
supervised child, or a call from any proc on a multi-loop app.
xtc_app_drain_on_signal() returns
XTC_OK, XTC_E_INVAL (bad
arguments, a borrowed loop, or another app already armed),
XTC_E_INTERNAL if the pipe could not be created, or
XTC_E_NOSYS on Windows.
xtc_app_is_shutdown_msg() returns 1 for
the drain request and 0 otherwise.
EXAMPLES
docs/_includes/snippets/12_graceful_drain.c starts workers, sends itself SIGTERM, drains and exits 0.
SEE ALSO
xtc_supervisor(3), xtc_proc(3), xtc_inspect(3), xtc_reg(3), xtc_loop(3), xtc(7)
HISTORY
Appeared in xtc 0.1.
| September 23, 2026 | Debian |