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