Serene Runtime 1.0.0-dev
C runtime for the Serene programming language
Loading...
Searching...
No Matches
io.h
Go to the documentation of this file.
1/* -*- C -*-
2 * Serene programming language
3 * Copyright (C) 2019-2026 Sameer Rahmani <[email protected]>
4 *
5 * This library is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU Lesser General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public License
16 * along with this library. If not, see <https://www.gnu.org/licenses/>.
17 */
18
19#pragma once
20
21/** @file
22 The fiber facing IO API: blocking looking calls a fiber uses to do IO.
23
24 Each call builds a request, suspends the calling fiber, and hands the request to the reactor; the
25fiber resumes with the result once the operation completes. The calling worker is never blocked --
26it runs other fibers while this one is parked. These are the fiber world replacements for the raw
27`read`/`write` and other similar IO related syscalls.
28
29 Each call returns a small per-op result which is a fallible value (see errors.h): `maybe_error` is
30null on success and carries the failure otherwise, so the success fields are meaningful only when
31there is no error. Calls with no value to report (connect, sleep) return `srn_error_t *` directly --
32null on success.
33
34 Constraints: a suspending call must run on a fiber that is on a worker (it suspends the fiber, so
35there must be one). A pollable, non-blocking descriptor (socket, pipe, ...) goes through the
36reactor; a regular file is served by a synchronous syscall on the worker, since a readiness backend
37cannot poll it.
38
39 Backpressure: a channel admits at most one ring's worth of operations in flight, and a suspending
40call past that cap fails with the transient CHANNEL_BUSY error. `SRN_WITH_RETRY` wraps a block in
41the retry loop (yielding between attempts through `srn_fiber_io_retry`):
42
43 srn_io_size_result_t r;
44 SRN_WITH_RETRY(r.maybe_error, {
45 r = srn_fiber_read(fd, buf, len, -1);
46 });
47
48 At the end of this file there are a set of convenient non-suspending helpers (socket, bind, listen,
49 close, shutdown, getsockopt, setsockopt, open), thin wrappers over the plain syscalls that share
50the same fallible result convention. They do not touch the reactor and may be called from any
51context. `socket` and `open` force `O_NONBLOCK`/`O_CLOEXEC` so a descriptor handed to the
52suspending calls above is always safe for the reactor.
53*/
54
55#include <stddef.h>
56#include <stdint.h>
57#include <sys/socket.h>
58#include <sys/types.h>
59
60#include "serene/rt/errors.h"
61
62/**
63 * The result of an op that yields a descriptor (srn_fiber_socket, srn_fiber_open).
64 * On success `fd` is the descriptor; on failure `maybe_error` is set.
65 */
70
71/**
72 * The result of a byte transfer op (read/write/sendto/recvmsg/sendmsg). On
73 * success `count` is the number of bytes moved, on failure `maybe_error` is set
74 * and `count` is unspecified.
75 */
80
81/**
82 * The result of srn_fiber_accept. On success `fd` is the accepted descriptor
83 * and, when an address buffer was given, `addrlen` is the length of the peer
84 * address written into it.
85 */
91
92/**
93 * The result of srn_fiber_recvfrom. On success `count` is the number of bytes
94 * received and, when an address buffer was given, `addrlen` is the length of
95 * the sender address written into it.
96 */
102
103/**
104 * Whether a suspending IO call should be retried, true exactly when `e` is
105 * the transient CHANNEL_BUSY error, in which case the calling fiber yields
106 * first so its worker can consume completions (which is what reopens the
107 * channel's admission window) and run other fibers before the retry. Any
108 * other error, and success, return false, so a do/while over this helper
109 * (see the file docs) retries precisely the backpressure case and hands
110 * every real outcome through. A retrying fiber with no runnable peers and
111 * no due completions degenerates to a paced busy loop; if that shows up in
112 * practice the upgrade is suspending on the channel until a slot frees,
113 * instead of polling.
114 */
116
117/**
118 * Run `block` and retry it (yielding between attempts through
119 * srn_fiber_io_retry) while `err` holds the transient CHANNEL_BUSY error.
120 * `err` is the error lvalue the block assigns, `r.maybe_error` for a
121 * fallible result struct, the variable itself for a plain `srn_error_t *`.
122 * Variadic so commas inside the block do not split the arguments.
123 *
124 * srn_io_size_result_t r;
125 * SRN_WITH_RETRY(r.maybe_error, {
126 * r = srn_fiber_read(fd, buf, len, -1);
127 * });
128 *
129 * srn_error_t *e;
130 * SRN_WITH_RETRY(e, { e = srn_fiber_sleep(duration); });
131 */
132#define SRN_WITH_RETRY(err, ...) \
133 do \
134 __VA_ARGS__ \
135 while (srn_fiber_io_retry(err))
136
137/**
138 * Read up to `len` bytes from `fd` into `buf`, suspending the calling fiber
139 * until the operation completes rather than blocking its worker. `offset` is
140 * the file offset, or -1 to read at the descriptor's current position. On
141 * success `count` is the number of bytes read (0 at end of file).
142 */
143srn_io_size_result_t srn_fiber_read(int fd, void *buf, size_t len, int64_t offset);
144
145/**
146 * Write `len` bytes from `buf` to `fd`, suspending the calling fiber until the
147 * operation completes rather than blocking its worker. `offset` is the file
148 * offset, or -1 to write at the descriptor's current position. On success
149 * `count` is the number of bytes written.
150 */
151srn_io_size_result_t srn_fiber_write(int fd, const void *buf, size_t len, int64_t offset);
152
153/**
154 * Suspend the calling fiber for at least `duration_ns` nanoseconds, letting its
155 * worker run other fibers meanwhile. Returns null on a normal wake, or the
156 * CANCELED error if the sleep was cut short by a scheduler wind down.
157 */
158srn_error_t *srn_fiber_sleep_ns(uint64_t duration_ns);
159
160/**
161 * Suspend the calling fiber for at least `duration_us` microseconds, letting its
162 * worker run other fibers meanwhile. Returns null on a normal wake, or the
163 * CANCELED error if the sleep was cut short by a scheduler wind down.
164 */
165srn_error_t *srn_fiber_sleep_us(uint64_t duration_us);
166
167/**
168 * Suspend the calling fiber for at least `duration_ms` miliseconds, letting its
169 * worker run other fibers meanwhile. Returns null on a normal wake, or the
170 * CANCELED error if the sleep was cut short by a scheduler wind down.
171 */
172srn_error_t *srn_fiber_sleep_ms(uint64_t duration_ms);
173
174/**
175 * Suspend the calling fiber for at least `duration` seconds, letting its
176 * worker run other fibers meanwhile. Returns null on a normal wake, or the
177 * CANCELED error if the sleep was cut short by a scheduler wind down.
178 */
179srn_error_t *srn_fiber_sleep(uint64_t duration);
180
181/**
182 * Accept a pending connection on the listening socket `fd`, suspending the
183 * calling fiber until one arrives. When `addr` is non-null the peer address is
184 * written into it, up to `addr_cap` bytes, and the result's `addrlen` reports
185 * how many were written. The accepted socket is created non-blocking and
186 * close on exec, so it is ready for further fiber IO. On success the result's
187 * `fd` is the accepted descriptor. Must run on a fiber that is on a worker.
188 */
189srn_io_accept_result_t srn_fiber_accept(int fd, struct sockaddr *addr, socklen_t addr_cap);
190
191/**
192 * Connect the socket `fd` to `addr`/`addrlen`, suspending the calling fiber
193 * until the connection completes rather than blocking its worker. Returns null
194 * on success, or the error (such as CONNECTION_REFUSED). Must run on a fiber
195 * that is on a worker.
196 */
197srn_error_t *srn_fiber_connect(int fd, const struct sockaddr *addr, socklen_t addrlen);
198
199/**
200 * Receive a datagram from `fd` into `buf`/`len` with `flags` (the `MSG_*`
201 * flags), suspending the calling fiber until one arrives. When `addr` is
202 * non-null the sender address is written into it, up to `addr_cap` bytes, and
203 * the result's `addrlen` reports how many. On success `count` is the number of
204 * bytes received. Must run on a fiber that is on a worker.
205 */
207 int fd, void *buf, size_t len, int flags, struct sockaddr *addr, socklen_t addr_cap
208);
209
210/**
211 * Send `buf`/`len` on `fd` to `addr`/`addrlen` with `flags` (the `MSG_*`
212 * flags), suspending the calling fiber until the send completes rather than
213 * blocking its worker. On success `count` is the number of bytes sent. Must run
214 * on a fiber that is on a worker.
215 */
217 int fd, const void *buf, size_t len, int flags, const struct sockaddr *addr, socklen_t addrlen
218);
219
220/**
221 * Receive into the `struct msghdr` `msg` on `fd` with `flags`, suspending the
222 * calling fiber until data arrives. Scatters across `msg`'s iovecs and fills
223 * its `msg_name`/`msg_control` as requested. On success `count` is the number
224 * of bytes received. Must run on a fiber that is on a worker.
225 */
226srn_io_size_result_t srn_fiber_recvmsg(int fd, struct msghdr *msg, int flags);
227
228/**
229 * Send the `struct msghdr` `msg` on `fd` with `flags`, suspending the calling
230 * fiber until the send completes. Gathers from `msg`'s iovecs. On success
231 * `count` is the number of bytes sent. Must run on a fiber that is on a worker.
232 */
233srn_io_size_result_t srn_fiber_sendmsg(int fd, const struct msghdr *msg, int flags);
234
235// ---------------------------------------------------------------------------
236// Non-suspending helpers
237// ---------------------------------------------------------------------------
238/**
239 * Create a socket, like `socket(2)`, additionally forcing `SOCK_NONBLOCK` and
240 * `SOCK_CLOEXEC` so the descriptor is safe to hand to the suspending calls (a
241 * blocking socket would stall the reactor thread). On success `fd` is the new
242 * descriptor.
243 */
244srn_io_fd_result_t srn_fiber_socket(int domain, int type, int protocol);
245
246/**
247 * Bind `fd` to `addr`/`addrlen`, like `bind(2)`. Returns null on success, or the
248 * error.
249 */
250srn_error_t *srn_fiber_bind(int fd, const struct sockaddr *addr, socklen_t addrlen);
251
252/**
253 * Mark `fd` as a listening socket with the given `backlog`, like `listen(2)`.
254 * Returns null on success, or the error.
255 */
256srn_error_t *srn_fiber_listen(int fd, int backlog);
257
258/**
259 * Close `fd`, like `close(2)`. Returns null on success, or the error.
260 */
262
263/**
264 * Shut down part or all of the connection on `fd` (`how` is `SHUT_RD`/`SHUT_WR`/
265 * `SHUT_RDWR`), like `shutdown(2)`. Returns null on success, or the error.
266 */
267srn_error_t *srn_fiber_shutdown(int fd, int how);
268
269/**
270 * Read a socket option into `optval`/`optlen`, like `getsockopt(2)`. Returns
271 * null on success, or the error.
272 */
273srn_error_t *srn_fiber_getsockopt(int fd, int level, int optname, void *optval, socklen_t *optlen);
274
275/**
276 * Set a socket option from `optval`/`optlen`, like `setsockopt(2)`. Returns null
277 * on success, or the error.
278 */
280srn_fiber_setsockopt(int fd, int level, int optname, const void *optval, socklen_t optlen);
281
282/**
283 * Open `path`, like `open(2)`, additionally forcing `O_NONBLOCK` and
284 * `O_CLOEXEC` so the descriptor is always safe to hand to the suspending
285 * calls above. `mode` applies only when `flags` includes `O_CREAT`. The open
286 * runs synchronously on the calling thread. On success `fd` is the new
287 * descriptor. Regular files ignore `O_NONBLOCK` (they are served on the
288 * worker, not the reactor). FIFOs follow the open(2) O_NONBLOCK semantics:
289 * a read end opens at once with no writer present, and a write end with no
290 * reader fails instead of blocking until one appears.
291 */
292srn_io_fd_result_t srn_fiber_open(const char *path, int flags, mode_t mode);
Error handling for the runtime.
srn_io_size_result_t srn_fiber_recvmsg(int fd, struct msghdr *msg, int flags)
Receive into the struct msghdr msg on fd with flags, suspending the calling fiber until data arrives.
Definition io.c:376
srn_io_size_result_t srn_fiber_sendmsg(int fd, const struct msghdr *msg, int flags)
Send the struct msghdr msg on fd with flags, suspending the calling fiber until the send completes.
Definition io.c:387
srn_error_t * srn_fiber_listen(int fd, int backlog)
Mark fd as a listening socket with the given backlog, like listen(2).
Definition io.c:443
srn_error_t * srn_fiber_getsockopt(int fd, int level, int optname, void *optval, socklen_t *optlen)
Read a socket option into optval/optlen, like getsockopt(2).
Definition io.c:467
srn_error_t * srn_fiber_close(int fd)
Close fd, like close(2).
Definition io.c:451
srn_error_t * srn_fiber_sleep_us(uint64_t duration_us)
Suspend the calling fiber for at least duration_us microseconds, letting its worker run other fibers ...
Definition io.c:274
srn_error_t * srn_fiber_setsockopt(int fd, int level, int optname, const void *optval, socklen_t optlen)
Set a socket option from optval/optlen, like setsockopt(2).
Definition io.c:476
srn_io_size_result_t srn_fiber_write(int fd, const void *buf, size_t len, int64_t offset)
Write len bytes from buf to fd, suspending the calling fiber until the operation completes rather tha...
Definition io.c:296
srn_error_t * srn_fiber_sleep_ms(uint64_t duration_ms)
Suspend the calling fiber for at least duration_ms miliseconds, letting its worker run other fibers m...
Definition io.c:278
srn_io_recvfrom_result_t srn_fiber_recvfrom(int fd, void *buf, size_t len, int flags, struct sockaddr *addr, socklen_t addr_cap)
Receive a datagram from fd into buf/len with flags (the MSG_* flags), suspending the calling fiber un...
Definition io.c:340
srn_error_t * srn_fiber_connect(int fd, const struct sockaddr *addr, socklen_t addrlen)
Connect the socket fd to addr/addrlen, suspending the calling fiber until the connection completes ra...
Definition io.c:330
srn_io_fd_result_t srn_fiber_socket(int domain, int type, int protocol)
Create a socket, like socket(2), additionally forcing SOCK_NONBLOCK and SOCK_CLOEXEC so the descripto...
Definition io.c:427
srn_io_size_result_t srn_fiber_sendto(int fd, const void *buf, size_t len, int flags, const struct sockaddr *addr, socklen_t addrlen)
Send buf/len on fd to addr/addrlen with flags (the MSG_* flags), suspending the calling fiber until t...
Definition io.c:360
bool srn_fiber_io_retry(srn_error_t *e)
Whether a suspending IO call should be retried, true exactly when e is the transient CHANNEL_BUSY err...
Definition io.c:243
srn_error_t * srn_fiber_bind(int fd, const struct sockaddr *addr, socklen_t addrlen)
Bind fd to addr/addrlen, like bind(2).
Definition io.c:435
srn_io_accept_result_t srn_fiber_accept(int fd, struct sockaddr *addr, socklen_t addr_cap)
Accept a pending connection on the listening socket fd, suspending the calling fiber until one arrive...
Definition io.c:308
srn_error_t * srn_fiber_shutdown(int fd, int how)
Shut down part or all of the connection on fd (how is SHUT_RD/SHUT_WR/ SHUT_RDWR),...
Definition io.c:459
srn_error_t * srn_fiber_sleep_ns(uint64_t duration_ns)
Suspend the calling fiber for at least duration_ns nanoseconds, letting its worker run other fibers m...
Definition io.c:251
srn_error_t * srn_fiber_sleep(uint64_t duration)
Suspend the calling fiber for at least duration seconds, letting its worker run other fibers meanwhil...
Definition io.c:282
srn_io_fd_result_t srn_fiber_open(const char *path, int flags, mode_t mode)
Open path, like open(2), additionally forcing O_NONBLOCK and O_CLOEXEC so the descriptor is always sa...
Definition io.c:484
srn_io_size_result_t srn_fiber_read(int fd, void *buf, size_t len, int64_t offset)
Read up to len bytes from fd into buf, suspending the calling fiber until the operation completes rat...
Definition io.c:284
A runtime error, a tag classifying the failure and a human-readable message.
Definition errors.h:125
The result of srn_fiber_accept.
Definition io.h:86
socklen_t addrlen
Definition io.h:89
The result of an op that yields a descriptor (srn_fiber_socket, srn_fiber_open).
Definition io.h:66
The result of srn_fiber_recvfrom.
Definition io.h:97
socklen_t addrlen
Definition io.h:100
The result of a byte transfer op (read/write/sendto/recvmsg/sendmsg).
Definition io.h:76
size_t count
Definition io.h:78