libxtc 0.4.0
Async concurrency for C: Tokio + Seastar + BEAM, in one library
Loading...
Searching...
No Matches
xtc_io.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_io.h
6 * The L1 event-notification engine: register interest in fd
7 * readiness, poll for ready events, wake the poller from another
8 * thread.
9 *
10 * Exactly one backend is compiled in per binary (configure-time).
11 * M2 ships poll(2) and epoll; later milestones add io_uring,
12 * kqueue, IOCP, Solaris event ports, and AIX pollset.
13 *
14 * See M2_CLAIMS.md.
15 */
16
17#ifndef XTC_IO_H
18#define XTC_IO_H
19
20#include "xtc_export.h"
21
22#include <stdint.h>
23
24typedef struct xtc_io xtc_io_t;
25
26/*
27 * Event flag bits. Stable across minor versions; new flags appear
28 * at higher bit positions only (PLAN.md (S)18).
29 */
30#define XTC_IO_READABLE 0x01u
31#define XTC_IO_WRITABLE 0x02u
32#define XTC_IO_HUP 0x04u
33#define XTC_IO_ERR 0x08u
34#define XTC_IO_WAKEUP 0x10u /* set on the synthetic event delivered
35 by xtc_io_wakeup; tag is NULL. */
36#define XTC_IO_AIO 0x20u /* set on an async file-I/O completion;
37 tag is the xtc_aio_t's tag. */
38
39typedef struct xtc_io_event {
40 void *tag; /* the value passed at registration; NULL on wakeups */
41 uint32_t flags; /* XTC_IO_* bitset */
43
44/*
45 * Async file I/O. A native completion-based file operation: io_uring
46 * submits IORING_OP_READ/WRITE/FSYNC; kqueue submits POSIX AIO with
47 * SIGEV_KEVENT and reaps EVFILT_AIO; IOCP uses overlapped reads/writes
48 * on the port -- in each case the completion wakes the tagged task. On
49 * a readiness-only backend (epoll, poll, select, event ports, AIX)
50 * xtc_io_aio_submit returns XTC_E_NOSYS and the caller offloads the op
51 * to the blocking pool instead, because a regular file is not pollable.
52 * See xtc_aio(3).
53 */
54enum {
55 XTC_AIO_PREAD = 0,
56 XTC_AIO_PWRITE = 1,
57 XTC_AIO_FSYNC = 2, /* full sync: data + metadata */
58 XTC_AIO_FDATASYNC = 3, /* data only (fsync on macOS, which lacks it) */
59 XTC_AIO_PREADV = 4, /* vectored read (iov/iovcnt) */
60 XTC_AIO_PWRITEV = 5 /* vectored write (iov/iovcnt) */
61};
62typedef struct xtc_aio {
63 int fd;
64 int op; /* XTC_AIO_* */
65 void *buf; /* read/write buffer (scalar ops; NULL for FSYNC/V) */
66 uint32_t len; /* byte count (scalar ops) */
67 int64_t off; /* file offset (ignored for FSYNC) */
68 void *tag; /* woken on completion (an xtc_task_t *) */
69 /*
70 * 0 until the completion is reaped. CONCURRENCY CONTRACT: on a
71 * native completion backend (io_uring / IOCP / kqueue-AIO / Solaris
72 * ports) the REAPING thread stores this and the PARKED FIBER reads it
73 * in its wake-recheck loop, so it is a cross-thread flag.
74 *
75 * It is deliberately a PLAIN int, not `_Atomic int`, and the library
76 * accesses it through the __os_atomic_{load,store}_i32 helpers in
77 * src/inc/os_atomic.h -- which exist precisely for this shape (atomic
78 * primitives applied to the ADDRESS of plain storage, giving the same
79 * ordering guarantees as an _Atomic-qualified type while leaving
80 * ordinary load/store usable on the single-threaded pre-publication
81 * init path, where xtc_io_aio_submit clears it).
82 *
83 * Why not `_Atomic int` (measured, 2026-09):
84 * - sizeof/_Alignof/every field offset of xtc_aio_t are IDENTICAL
85 * either way (64/8, done@40, res@44, iov@48, iovcnt@56; gcc and
86 * clang, x86-64), so layout was NOT the objection.
87 * - but `_Atomic` is not valid C++, and this header has no
88 * extern "C" guard of its own (xtc.h has one; xtc_io.h is also
89 * includable directly). g++ -std=c++17 compiles this header today
90 * and would fail outright with `'_Atomic' does not name a type`.
91 * Consumers allocate xtc_aio_t on the stack, so that is a source
92 * break for every C++ embedder, in exchange for nothing the
93 * helper accesses do not already provide.
94 * The accessor route gets the well-defined cross-thread access with
95 * no ABI change and no C++ break. Do not "upgrade" it.
96 */
97 int done;
98 int32_t res; /* bytes transferred, or -errno */
99 /* Vectored ops (PREADV / PWRITEV): iov points at the caller's
100 * iovec array (struct iovec *, kept opaque here to avoid a
101 * <sys/uio.h> dependency in this header) and iovcnt is its count.
102 * Zero/NULL for the scalar and sync ops. */
103 void *iov; /* const struct iovec * */
104 int iovcnt;
105} xtc_aio_t;
106
107/*
108 * PUBLIC: int xtc_io_init __P((xtc_io_t **));
109 * PUBLIC: int xtc_io_fini __P((xtc_io_t *));
110 * PUBLIC: const char *xtc_io_backend_name __P((void));
111 * PUBLIC: int xtc_io_reg_fd __P((xtc_io_t *, int, uint32_t, void *));
112 * PUBLIC: int xtc_io_mod_fd __P((xtc_io_t *, int, uint32_t, void *));
113 * PUBLIC: int xtc_io_del_fd __P((xtc_io_t *, int));
114 * PUBLIC: int xtc_io_poll __P((xtc_io_t *, xtc_io_event_t *, int, int64_t, int *));
115 * PUBLIC: int xtc_io_aio_submit __P((xtc_io_t *, xtc_aio_t *));
116 * PUBLIC: int xtc_io_wakeup __P((xtc_io_t *));
117 *
118 * PUBLIC: void xtc_io_set_iowq_max_workers __P((unsigned, unsigned));
119 */
120
121/* Lifecycle. */
122XTC_API int xtc_io_init(xtc_io_t **out);
123XTC_API int xtc_io_fini(xtc_io_t *io);
124XTC_API const char *xtc_io_backend_name(void);
125
126/* Registration. */
127XTC_API int xtc_io_reg_fd(xtc_io_t *io, int fd, uint32_t interest, void *tag);
128XTC_API int xtc_io_mod_fd(xtc_io_t *io, int fd, uint32_t interest, void *tag);
129XTC_API int xtc_io_del_fd(xtc_io_t *io, int fd);
130
131/*
132 * xtc_io_poll --
133 * Wait for events. On return *n_out holds the number of events
134 * written (0..max).
135 *
136 * timeout_ns:
137 * == 0 -> non-blocking
138 * > 0 -> wait at most that many nanoseconds
139 * < 0 -> wait indefinitely (until an fd becomes ready or
140 * xtc_io_wakeup is called)
141 */
142XTC_API int xtc_io_poll(xtc_io_t *io, xtc_io_event_t *events, int max,
143 int64_t timeout_ns, int *n_out);
144
145/* Submit an async file op. XTC_OK if queued (the caller parks until
146 * the completion event with tag a->tag arrives, then reads a->res);
147 * XTC_E_NOSYS if the backend has no native file completion (caller
148 * should offload instead). */
149XTC_API int xtc_io_aio_submit(xtc_io_t *io, xtc_aio_t *a);
150
151/*
152 * xtc_io_wakeup --
153 * From any thread, cause the next (or in-flight) xtc_io_poll on
154 * this io to return. Safe to call concurrently from many threads;
155 * multiple wakeups before the next poll coalesce into one event.
156 */
157XTC_API int xtc_io_wakeup(xtc_io_t *io);
158
159/*
160 * Cap the per-ring io_uring io-wq kernel worker pool (Linux io_uring
161 * backend only; a no-op on other backends). See the implementation
162 * comment in io_uring.c: prevents N carriers on an M-core box from
163 * accumulating N*M idle io-wq kernel threads. Call before the
164 * executor/loop creates its rings.
165 */
166XTC_API void xtc_io_set_iowq_max_workers(unsigned bound,
167 unsigned unbound);
168
169#endif /* XTC_IO_H */