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 <lxsameer@lxsameer.com>
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 The error a call reports is one of the reactor's shared static errors, so it is safe to keep and
35compare but never written to. Take a copy with `ERR_FROM` before pushing a trace onto it or storing
36it as an owned error.
37
38 Constraints: a suspending call must run on a fiber that is on a worker (it suspends the fiber, so
39there must be one). A pollable, non-blocking descriptor (socket, pipe, ...) goes through the
40reactor; a regular file is served by a synchronous syscall on the worker, since a readiness backend
41cannot poll it.
42
43 Backpressure: a channel admits at most one ring's worth of operations in flight, and a suspending
44call past that cap fails with the transient CHANNEL_BUSY error. `SRN_WITH_RETRY` wraps a block in
45the retry loop (yielding between attempts through `srn_fiber_io_retry`):
46
47 srn_io_size_result_t r;
48 SRN_WITH_RETRY(r.maybe_error, {
49 r = srn_fiber_read(fd, buf, len, -1);
50 });
51
52 At the end of this file there are a set of convenient non-suspending helpers (socket, bind, listen,
53 close, shutdown, getsockopt, setsockopt, open), thin wrappers over the plain syscalls that share
54the same fallible result convention. They do not touch the reactor and may be called from any
55context. `socket` and `open` force `O_NONBLOCK`/`O_CLOEXEC` so a descriptor handed to the
56suspending calls above is always safe for the reactor.
57*/
58
59#include <stddef.h>
60#include <stdint.h>
61#include <sys/socket.h>
62#include <sys/types.h>
63
64#include "serene/rt/errors.h"
65
66/**
67 * The result of an op that yields a descriptor (srn_fiber_socket, srn_fiber_open).
68 * On success `fd` is the descriptor; on failure `maybe_error` is set.
69 */
74
75/**
76 * The result of a byte transfer op (read/write/sendto/recvmsg/sendmsg). On
77 * success `count` is the number of bytes moved, on failure `maybe_error` is set
78 * and `count` is unspecified.
79 */
84
85/**
86 * The result of srn_fiber_accept. On success `fd` is the accepted descriptor
87 * and, when an address buffer was given, `addrlen` is the length of the peer
88 * address written into it.
89 */
95
96/**
97 * The result of srn_fiber_recvfrom. On success `count` is the number of bytes
98 * received and, when an address buffer was given, `addrlen` is the length of
99 * the sender address written into it.
100 */
106
107/**
108 * Whether a suspending IO call should be retried, true exactly when `e` is
109 * the transient CHANNEL_BUSY error, in which case the calling fiber yields
110 * first so its worker can consume completions (which is what reopens the
111 * channel's admission window) and run other fibers before the retry. Any
112 * other error, and success, return false, so a do/while over this helper
113 * (see the file docs) retries precisely the backpressure case and hands
114 * every real outcome through. A retrying fiber with no runnable peers and
115 * no due completions degenerates to a paced busy loop; if that shows up in
116 * practice the upgrade is suspending on the channel until a slot frees,
117 * instead of polling.
118 */
120
121/**
122 * Run `block` and retry it (yielding between attempts through
123 * srn_fiber_io_retry) while `err` holds the transient CHANNEL_BUSY error.
124 * `err` is the error value the block assigns, `r.maybe_error` for a
125 * fallible result struct, the variable itself for a plain `srn_error_t *`.
126 * Variadic so commas inside the block do not split the arguments.
127 *
128 * srn_io_size_result_t r;
129 * SRN_WITH_RETRY(r.maybe_error, {
130 * r = srn_fiber_read(fd, buf, len, -1);
131 * });
132 *
133 * srn_error_t *e;
134 * SRN_WITH_RETRY(e, { e = srn_fiber_sleep(duration); });
135 */
136#define SRN_WITH_RETRY(err, ...) \
137 do \
138 __VA_ARGS__ \
139 while (srn_fiber_io_retry(err))
140
141/**
142 * Read up to `len` bytes from `fd` into `buf`, suspending the calling fiber
143 * until the operation completes rather than blocking its worker. `offset` is
144 * the file offset, or -1 to read at the descriptor's current position. On
145 * success `count` is the number of bytes read (0 at end of file).
146 */
147srn_io_size_result_t srn_fiber_read(int fd, void *buf, size_t len, int64_t offset);
148
149/**
150 * Write `len` bytes from `buf` to `fd`, suspending the calling fiber until the
151 * operation completes rather than blocking its worker. `offset` is the file
152 * offset, or -1 to write at the descriptor's current position. On success
153 * `count` is the number of bytes written.
154 */
155srn_io_size_result_t srn_fiber_write(int fd, const void *buf, size_t len, int64_t offset);
156
157/**
158 * Suspend the calling fiber for at least `duration_ns` nanoseconds, letting its
159 * worker run other fibers meanwhile. Returns null on a normal wake, or the
160 * CANCELED error if the sleep was cut short by a scheduler wind down.
161 */
162srn_error_t *srn_fiber_sleep_ns(uint64_t duration_ns);
163
164/**
165 * Suspend the calling fiber for at least `duration_us` microseconds, letting its
166 * worker run other fibers meanwhile. Returns null on a normal wake, or the
167 * CANCELED error if the sleep was cut short by a scheduler wind down.
168 */
169srn_error_t *srn_fiber_sleep_us(uint64_t duration_us);
170
171/**
172 * Suspend the calling fiber for at least `duration_ms` miliseconds, letting its
173 * worker run other fibers meanwhile. Returns null on a normal wake, or the
174 * CANCELED error if the sleep was cut short by a scheduler wind down.
175 */
176srn_error_t *srn_fiber_sleep_ms(uint64_t duration_ms);
177
178/**
179 * Suspend the calling fiber for at least `duration` seconds, letting its
180 * worker run other fibers meanwhile. Returns null on a normal wake, or the
181 * CANCELED error if the sleep was cut short by a scheduler wind down.
182 */
183srn_error_t *srn_fiber_sleep(uint64_t duration);
184
185/**
186 * Accept a pending connection on the listening socket `fd`, suspending the
187 * calling fiber until one arrives. When `addr` is non-null the peer address is
188 * written into it, up to `addr_cap` bytes, and the result's `addrlen` reports
189 * how many were written. The accepted socket is created non-blocking and
190 * close on exec, so it is ready for further fiber IO. On success the result's
191 * `fd` is the accepted descriptor. Must run on a fiber that is on a worker.
192 */
193srn_io_accept_result_t srn_fiber_accept(int fd, struct sockaddr *addr, socklen_t addr_cap);
194
195/**
196 * Connect the socket `fd` to `addr`/`addrlen`, suspending the calling fiber
197 * until the connection completes rather than blocking its worker. Returns null
198 * on success, or the error (such as CONNECTION_REFUSED). Must run on a fiber
199 * that is on a worker.
200 */
201srn_error_t *srn_fiber_connect(int fd, const struct sockaddr *addr, socklen_t addrlen);
202
203/**
204 * Receive a datagram from `fd` into `buf`/`len` with `flags` (the `MSG_*`
205 * flags), suspending the calling fiber until one arrives. When `addr` is
206 * non-null the sender address is written into it, up to `addr_cap` bytes, and
207 * the result's `addrlen` reports how many. On success `count` is the number of
208 * bytes received. Must run on a fiber that is on a worker.
209 */
211 int fd, void *buf, size_t len, int flags, struct sockaddr *addr, socklen_t addr_cap
212);
213
214/**
215 * Send `buf`/`len` on `fd` to `addr`/`addrlen` with `flags` (the `MSG_*`
216 * flags), suspending the calling fiber until the send completes rather than
217 * blocking its worker. On success `count` is the number of bytes sent. Must run
218 * on a fiber that is on a worker.
219 */
221 int fd, const void *buf, size_t len, int flags, const struct sockaddr *addr, socklen_t addrlen
222);
223
224/**
225 * Receive into the `struct msghdr` `msg` on `fd` with `flags`, suspending the
226 * calling fiber until data arrives. Scatters across `msg`'s iovecs and fills
227 * its `msg_name`/`msg_control` as requested. On success `count` is the number
228 * of bytes received. Must run on a fiber that is on a worker.
229 */
230srn_io_size_result_t srn_fiber_recvmsg(int fd, struct msghdr *msg, int flags);
231
232/**
233 * Send the `struct msghdr` `msg` on `fd` with `flags`, suspending the calling
234 * fiber until the send completes. Gathers from `msg`'s iovecs. On success
235 * `count` is the number of bytes sent. Must run on a fiber that is on a worker.
236 */
237srn_io_size_result_t srn_fiber_sendmsg(int fd, const struct msghdr *msg, int flags);
238
239// ---------------------------------------------------------------------------
240// Non-suspending helpers
241// ---------------------------------------------------------------------------
242/**
243 * Create a socket, like `socket(2)`, additionally forcing `SOCK_NONBLOCK` and
244 * `SOCK_CLOEXEC` so the descriptor is safe to hand to the suspending calls (a
245 * blocking socket would stall the reactor thread). On success `fd` is the new
246 * descriptor.
247 */
248srn_io_fd_result_t srn_fiber_socket(int domain, int type, int protocol);
249
250/**
251 * Bind `fd` to `addr`/`addrlen`, like `bind(2)`. Returns null on success, or the
252 * error.
253 */
254srn_error_t *srn_fiber_bind(int fd, const struct sockaddr *addr, socklen_t addrlen);
255
256/**
257 * Mark `fd` as a listening socket with the given `backlog`, like `listen(2)`.
258 * Returns null on success, or the error.
259 */
260srn_error_t *srn_fiber_listen(int fd, int backlog);
261
262/**
263 * Close `fd`, like `close(2)`. Returns null on success, or the error.
264 */
266
267/**
268 * Shut down part or all of the connection on `fd` (`how` is `SHUT_RD`/`SHUT_WR`/
269 * `SHUT_RDWR`), like `shutdown(2)`. Returns null on success, or the error.
270 */
271srn_error_t *srn_fiber_shutdown(int fd, int how);
272
273/**
274 * Read a socket option into `optval`/`optlen`, like `getsockopt(2)`. Returns
275 * null on success, or the error.
276 */
277srn_error_t *srn_fiber_getsockopt(int fd, int level, int optname, void *optval, socklen_t *optlen);
278
279/**
280 * Set a socket option from `optval`/`optlen`, like `setsockopt(2)`. Returns null
281 * on success, or the error.
282 */
284srn_fiber_setsockopt(int fd, int level, int optname, const void *optval, socklen_t optlen);
285
286/**
287 * Open `path`, like `open(2)`, additionally forcing `O_NONBLOCK` and
288 * `O_CLOEXEC` so the descriptor is always safe to hand to the suspending
289 * calls above. `mode` applies only when `flags` includes `O_CREAT`. The open
290 * runs synchronously on the calling thread. On success `fd` is the new
291 * descriptor. Regular files ignore `O_NONBLOCK` (they are served on the
292 * worker, not the reactor). FIFOs follow the open(2) O_NONBLOCK semantics:
293 * a read end opens at once with no writer present, and a write end with no
294 * reader fails instead of blocking until one appears.
295 */
296srn_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:395
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:406
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:462
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:486
srn_error_t * srn_fiber_close(int fd)
Close fd, like close(2).
Definition io.c:470
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:293
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:495
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:315
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:297
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:359
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:349
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:446
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:379
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:261
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:454
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:327
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:478
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:270
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:301
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:503
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:303
const size_t len
char path[PATH_MAX]
A runtime error, a tag classifying the failure and a human-readable message.
Definition errors.h:173
The result of srn_fiber_accept.
Definition io.h:90
socklen_t addrlen
Definition io.h:93
The result of an op that yields a descriptor (srn_fiber_socket, srn_fiber_open).
Definition io.h:70
The result of srn_fiber_recvfrom.
Definition io.h:101
socklen_t addrlen
Definition io.h:104
The result of a byte transfer op (read/write/sendto/recvmsg/sendmsg).
Definition io.h:80
size_t count
Definition io.h:82