libxtc 0.4.0
Async concurrency for C: Tokio + Seastar + BEAM, in one library
Loading...
Searching...
No Matches
xtc_svr.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_svr.h
7 * The L4 gen_server: a structured pattern for a long-running
8 * process that handles three kinds of incoming traffic:
9 *
10 * - call: a synchronous request expecting a reply.
11 * - cast: a fire-and-forget command.
12 * - info: any other message that lands in the mailbox
13 * (timer ticks, monitor DOWNs, raw sends, etc.)
14 *
15 * The server runs as an xtc_proc. Behaviour is supplied via a
16 * vtable of callbacks. All callbacks run in the server's
17 * process; they may use any xtc_proc / xtc_sync API.
18 *
19 * Modeled on Erlang's gen_server. The xtc_call_t handle ties
20 * a synchronous reply back to its caller via xtc_chan_oneshot
21 * so the caller can park on the reply with a timeout.
22 */
23
24#ifndef XTC_SVR_H
25#define XTC_SVR_H
26
27#include "xtc_export.h"
28
29#include <stddef.h>
30#include <stdint.h>
31
32#include "xtc.h"
33#include "xtc_chan.h"
34#include "xtc_loop.h"
35#include "xtc_proc.h"
36#include "xtc_sync.h"
37
38/* Result codes for handle_call / handle_cast / handle_info: the
39 * callback may either keep running (XTC_SVR_CONTINUE) or request
40 * the server to stop (XTC_SVR_STOP). handle_call may also defer its
41 * reply (XTC_SVR_NOREPLY): it saved the call with xtc_svr_call_save
42 * and will xtc_svr_reply later. */
43#define XTC_SVR_CONTINUE 0
44#define XTC_SVR_STOP 1
45#define XTC_SVR_NOREPLY 2
46
47typedef struct xtc_svr xtc_svr_t;
48typedef struct xtc_svr_call xtc_svr_call_t;
49
50typedef struct xtc_svr_callbacks {
51 size_t size; /* sizeof(xtc_svr_callbacks_t): XTC_SVR_CALLBACKS_INIT */
52 int (*init) (void *state); /* OK to be NULL */
53 int (*handle_call) (void *state, const void *req, size_t req_size,
54 xtc_svr_call_t *call); /* required if calls used */
55 int (*handle_cast) (void *state, const void *msg, size_t size); /* OK to be NULL */
56 int (*handle_info) (void *state, const void *msg, size_t size); /* OK to be NULL */
57 /* Runs before the next recv when a callback armed a continuation via
58 * xtc_svr_continue(cont); `cont` is that argument. Lets init (or a
59 * handler) return fast -- unblocking the caller / the supervisor --
60 * and finish expensive work off the critical path, race-free (no
61 * self-send that could race an incoming message). A handle_continue
62 * may itself arm another continuation. OK to be NULL (an armed
63 * continuation with no handler is dropped). */
64 void (*terminate) (void *state, int reason); /* OK to be NULL */
65 int (*handle_continue)(void *state, void *cont); /* OK to be NULL */
67
68/* Set the callbacks with designated initializers after it:
69 * { .size = sizeof(xtc_svr_callbacks_t), .handle_call = f }. */
70#define XTC_SVR_CALLBACKS_INIT { .size = sizeof(xtc_svr_callbacks_t) }
71
72typedef struct xtc_svr_opts {
73 size_t size; /* sizeof(xtc_svr_opts_t): XTC_SVR_OPTS_INIT */
74 const char *name; /* optional, for logs */
75 size_t mailbox_cap; /* 0 = default */
77
78#define XTC_SVR_OPTS_INIT { .size = sizeof(xtc_svr_opts_t) }
79
80/*
81 * PUBLIC: int xtc_svr_start __P((xtc_loop_t *, const xtc_svr_callbacks_t *, void *, const xtc_svr_opts_t *, xtc_svr_t **));
82 * PUBLIC: int xtc_svr_stop __P((xtc_svr_t *));
83 * PUBLIC: int xtc_svr_join __P((xtc_svr_t *, int64_t));
84 * PUBLIC: xtc_pid_t xtc_svr_pid __P((const xtc_svr_t *));
85 *
86 * PUBLIC: int xtc_svr_call __P((xtc_pid_t, const void *, size_t, void **, size_t *, int64_t));
87 * PUBLIC: int xtc_svr_call_abortable __P((xtc_pid_t, const void *, size_t, void **, size_t *, int64_t, xtc_abort_token_t *));
88 * PUBLIC: int xtc_svr_cast __P((xtc_pid_t, const void *, size_t));
89 * PUBLIC: int xtc_svr_reply __P((xtc_svr_call_t *, const void *, size_t));
90 * PUBLIC: int xtc_svr_continue __P((void *));
91 * PUBLIC: xtc_svr_call_t *xtc_svr_call_save __P((const xtc_svr_call_t *));
92 */
93
94XTC_API int xtc_svr_start(xtc_loop_t *loop,
95 const xtc_svr_callbacks_t *cb,
96 void *state,
97 const xtc_svr_opts_t *opts,
98 xtc_svr_t **out);
99
100XTC_API int xtc_svr_stop(xtc_svr_t *svr);
101XTC_API int xtc_svr_join(xtc_svr_t *svr, int64_t timeout_ns);
102XTC_API xtc_pid_t xtc_svr_pid(const xtc_svr_t *svr);
103
104/* Synchronous call: send `req`, wait for reply, copy reply into a
105 * heap-allocated buffer that the caller must xtc_free. Callable from a
106 * proc or from a plain thread. Returns:
107 * XTC_OK -- *out_reply / *out_size populated
108 * XTC_E_AGAIN -- timeout
109 * XTC_E_NOTFOUND -- the server exited before replying (noticed
110 * within ~25 ms; any timeout, including -1)
111 * XTC_E_INVAL -- bad pid / not a server / server already gone
112 * A timed-out, aborted or server-gone call only stops the CALLER
113 * waiting. A reply the server sends afterwards to an off-proc caller is
114 * discarded (that xtc_svr_reply returns XTC_E_NOTFOUND); to an in-proc
115 * caller it is delivered to the caller's mailbox as an internally framed
116 * reply envelope (never confused with an application message), which the
117 * caller's next xtc_svr_call discards; a proc that makes no further call
118 * can still see it with a plain xtc_recv. */
119XTC_API int xtc_svr_call(xtc_pid_t target,
120 const void *req, size_t req_size,
121 void **out_reply, size_t *out_size,
122 int64_t timeout_ns);
123
124/* Like xtc_svr_call, but cancellable: while waiting for the reply the
125 * abort token is polled, and the call returns XTC_E_ABORTED if it
126 * fires first. Fire the token's source (xtc_abort_source_fire) from
127 * a timeout or a cancel-request path -- the cooperative cancellation
128 * primitive (e.g. a statement timeout delivering a cancel at the next
129 * wait point). Cancellation stops only the caller's wait; the
130 * server keeps processing, and a late reply is handled as for a
131 * timed-out xtc_svr_call. */
132XTC_API int xtc_svr_call_abortable(xtc_pid_t target,
133 const void *req, size_t req_size,
134 void **out_reply, size_t *out_size,
135 int64_t timeout_ns, xtc_abort_token_t *tok);
136
137/* Fire-and-forget: send `msg` to the server. Server's handle_cast
138 * (if non-NULL) will see it; if NULL, falls through to handle_info. */
139XTC_API int xtc_svr_cast(xtc_pid_t target, const void *msg, size_t size);
140
141/* From inside handle_call, send the reply and release the call.
142 * Each call must be replied exactly once. */
143XTC_API int xtc_svr_reply(xtc_svr_call_t *call,
144 const void *reply, size_t size);
145
146/*
147 * Arm a continuation from within a server callback (init or a handle_*):
148 * handle_continue(state, cont) runs before the server's next recv. Use
149 * it to return from init quickly (unblocking the supervisor / the
150 * xtc_svr_start caller) and finish expensive setup before the first
151 * message, without the self-send that races an incoming message.
152 * Returns XTC_E_INVAL if called outside a server callback.
153 */
154XTC_API int xtc_svr_continue(void *cont);
155
156/*
157 * Deferred reply (gen_server:reply/2). The xtc_svr_call_t passed to
158 * handle_call is valid only for the duration of that callback. To
159 * reply LATER -- after a batch fills, after other shards answer, after
160 * a timer fires -- call xtc_svr_call_save() inside handle_call to get
161 * a heap-allocated handle that outlives the callback, stash it in the
162 * server state, return XTC_SVR_NOREPLY, and call xtc_svr_reply() on
163 * the saved handle from any later callback. xtc_svr_reply frees a
164 * saved handle after sending. Each saved handle must be replied
165 * exactly once.
166 *
167 * Safe for in-proc callers (the reply routes to the caller's mailbox
168 * by tag, so it is harmless even if the caller has gone). For an
169 * off-proc caller the reply targets the caller's stack-resident reply
170 * slot, so the caller must remain blocked in xtc_svr_call (not time
171 * out) until the deferred reply is sent.
172 */
173XTC_API xtc_svr_call_t *xtc_svr_call_save(const xtc_svr_call_t *call);
174
175#endif /* XTC_SVR_H */