28# include <arpa/inet.h>
31# include <netinet/in.h>
35# include <sys/epoll.h>
36# include <sys/eventfd.h>
37# include <sys/socket.h>
50# define REACTOR_IO_TESTS(X) \
51 X("io::read", test_io_read), X("io::write", test_io_write), X("io::poll", test_io_poll), \
52 X("io::accept", test_io_accept), X("io::connect_success", test_io_connect), \
53 X("io::connect_refused", test_io_refused), X("io::recvfrom", test_io_recvfrom), \
54 X("io::sendto", test_io_sendto), X("io::recvmsg", test_io_recvmsg), \
55 X("io::sendmsg", test_io_sendmsg), X("io::shared_fd", test_io_shared_fd), \
56 X("io::accept_fanout", test_io_fanout), X("io::tombstone_reuse", test_io_reuse), \
57 X("io::bad_fd", test_io_bad_fd), X("io::peer_close_eof", test_io_eof), \
58 X("io::write_epipe", test_io_epipe), X("io::fiber_read", test_io_fiber_read), \
59 X("io::fiber_write", test_io_fiber_write), X("io::fiber_sleep", test_io_fiber_sleep), \
60 X("io::fiber_pipe", test_io_fiber_pipe), X("io::fiber_drain", test_io_fiber_drain), \
61 X("io::fiber_file", test_io_fiber_file), \
62 X("io::fiber_accept_connect", test_io_fiber_accept_connect), \
63 X("io::fiber_dgram", test_io_fiber_dgram), X("io::inline_helpers", test_io_inline_helpers), \
64 X("io::channel_admission", test_io_channel_admission), \
65 X("io::busy_retry", test_io_busy_retry), X("io::open_nonblock", test_io_open_nonblock), \
66 X("io::fiber_fifo", test_io_fiber_fifo)
72static void io_nonblock(
int fd) {
73 int fl = fcntl(fd, F_GETFL, 0);
75 TEST_ASSERT(fcntl(fd, F_SETFL, fl | O_NONBLOCK) == 0);
90static void io_socketpair(
int sv[2]) {
91 TEST_ASSERT(socketpair(AF_UNIX, SOCK_STREAM, 0, sv) == 0);
98static int io_tcp_listener(uint16_t *port) {
99 int s = socket(AF_INET, SOCK_STREAM, 0);
102 struct sockaddr_in a = {.sin_family = AF_INET};
103 a.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
104 TEST_ASSERT(bind(s, (
struct sockaddr *)&a,
sizeof(a)) == 0);
106 socklen_t al =
sizeof(a);
107 TEST_ASSERT(getsockname(s, (
struct sockaddr *)&a, &al) == 0);
114static int io_tcp_connect(uint16_t port) {
115 int c = socket(AF_INET, SOCK_STREAM, 0);
117 struct sockaddr_in a = {.sin_family = AF_INET, .sin_port = port};
118 a.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
119 TEST_ASSERT(connect(c, (
struct sockaddr *)&a,
sizeof(a)) == 0);
125static int io_udp_bound(uint16_t *port) {
126 int s = socket(AF_INET, SOCK_DGRAM, 0);
129 struct sockaddr_in a = {.sin_family = AF_INET};
130 a.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
131 TEST_ASSERT(bind(s, (
struct sockaddr *)&a,
sizeof(a)) == 0);
132 socklen_t al =
sizeof(a);
133 TEST_ASSERT(getsockname(s, (
struct sockaddr *)&a, &al) == 0);
143static void test_io_read() {
160 .fiber_data = &marker
162 io_submit(r, 0, &req);
175static void test_io_write() {
183 char out[] =
"world";
191 .fiber_data = &marker
193 io_submit(r, 0, &req);
199 TEST_CHECK(read(sv[1], buf,
sizeof(buf)) == 5);
208static void test_io_poll() {
213 int efd = eventfd(0, EFD_NONBLOCK);
216 TEST_ASSERT(write(efd, &one,
sizeof(one)) == (ssize_t)
sizeof(one));
222 io_submit(r, 0, &req);
233static void test_io_accept() {
239 int lst = io_tcp_listener(&port);
241 struct sockaddr_storage ss;
242 memset(&ss, 0,
sizeof(ss));
248 .addrlen =
sizeof(ss),
249 .fiber_data = &marker
251 io_submit(r, 0, &req);
253 int cli = io_tcp_connect(port);
258 TEST_CHECK(((
struct sockaddr_in *)&ss)->sin_family == AF_INET);
269static void test_io_connect() {
275 int lst = io_tcp_listener(&port);
277 int s = socket(AF_INET, SOCK_STREAM, 0);
281 struct sockaddr_in a = {.sin_family = AF_INET, .sin_port = port};
282 a.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
288 io_submit(r, 0, &req);
300static void test_io_refused() {
306 int lst = io_tcp_listener(&port);
309 int s = socket(AF_INET, SOCK_STREAM, 0);
312 struct sockaddr_in a = {.sin_family = AF_INET, .sin_port = port};
313 a.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
319 io_submit(r, 0, &req);
329static void test_io_recvfrom() {
335 int rcv = io_udp_bound(&port);
336 int snd = socket(AF_INET, SOCK_DGRAM, 0);
339 struct sockaddr_storage ss;
340 memset(&ss, 0,
sizeof(ss));
349 .addrlen =
sizeof(ss),
350 .fiber_data = &marker
352 io_submit(r, 0, &req);
354 struct sockaddr_in to = {.sin_family = AF_INET, .sin_port = port};
355 to.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
357 TEST_ASSERT(sendto(snd, msg, 4, 0, (
struct sockaddr *)&to,
sizeof(to)) == 4);
363 TEST_CHECK(((
struct sockaddr_in *)&ss)->sin_family == AF_INET);
371static void test_io_sendto() {
377 int rcv = io_udp_bound(&port);
378 int snd = socket(AF_INET, SOCK_DGRAM, 0);
382 struct sockaddr_in to = {.sin_family = AF_INET, .sin_port = port};
383 to.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
393 .addrlen =
sizeof(to),
394 .fiber_data = &marker
396 io_submit(r, 0, &req);
402 TEST_CHECK(recvfrom(rcv, buf,
sizeof(buf), 0,
nullptr,
nullptr) == 4);
411static void test_io_recvmsg() {
422 struct iovec iov[2] = {{.iov_base = p1, .iov_len = 3}, {.iov_base = p2, .iov_len = 3}};
424 memset(&mh, 0,
sizeof(mh));
432 io_submit(r, 0, &req);
445static void test_io_sendmsg() {
455 struct iovec iov[2] = {{.iov_base = a, .iov_len = 3}, {.iov_base = b, .iov_len = 3}};
457 memset(&mh, 0,
sizeof(mh));
465 io_submit(r, 0, &req);
471 TEST_CHECK(read(sv[1], buf,
sizeof(buf)) == 6);
486static void test_io_shared_fd() {
505 .fiber_data = &m_read
513 .fiber_data = &m_write
519 bool got_read =
false;
520 bool got_write =
false;
521 for (
int i = 0; i < 2; i++) {
537 TEST_CHECK(read(sv[1], buf,
sizeof(buf)) == 3);
546static void test_io_fanout() {
552 int lst = io_tcp_listener(&port);
556 struct sockaddr_storage ss[K];
557 for (
int i = 0; i < K; i++) {
558 memset(&ss[i], 0,
sizeof(ss[i]));
563 .addrlen =
sizeof(ss[i]),
564 .fiber_data = &markers[i]
571 for (
int i = 0; i < K; i++) {
572 clients[i] = io_tcp_connect(port);
576 for (
int i = 0; i < K; i++) {
585 for (
int i = 0; i < K; i++) {
598static void test_io_reuse() {
617 io_submit(r, 0, &rq1);
633 io_submit(r, 0, &rq2);
643static void test_io_bad_fd() {
660 .fiber_data = &marker
662 io_submit(r, 0, &req);
671static void test_io_eof() {
688 .fiber_data = &marker
690 io_submit(r, 0, &req);
702static void test_io_epipe() {
719 .fiber_data = &marker
721 io_submit(r, 0, &req);
737typedef struct fiber_io_args_t {
746static int fiber_io_done;
750 fiber_io_args_t *a = (fiber_io_args_t *)arg;
751 SRN_WITH_RETRY(a->res.maybe_error, { a->res = srn_fiber_read(a->fd, a->buf, a->len, -1); });
752 return &fiber_io_done;
757 fiber_io_args_t *a = (fiber_io_args_t *)arg;
759 return &fiber_io_done;
765static void test_io_fiber_read() {
776 fiber_io_args_t a = {.fd = sv[0], .buf = buf, .len =
sizeof(buf)};
793static void test_io_fiber_write() {
802 char out[] =
"world";
803 fiber_io_args_t a = {.fd = sv[0], .buf = out, .len = 5};
810 TEST_CHECK(read(sv[1], buf,
sizeof(buf)) == 5);
819typedef struct fiber_sleep_args_t {
820 uint64_t duration_ns;
826 fiber_sleep_args_t *a = (fiber_sleep_args_t *)arg;
828 return &fiber_io_done;
836static void test_io_fiber_sleep() {
843 fiber_sleep_args_t a = {.duration_ns = delay, .err =
nullptr};
862static atomic_int io_adm_ok;
863static atomic_int io_adm_busy;
864static atomic_int io_adm_other;
871 atomic_fetch_add(&io_adm_ok, 1);
873 atomic_fetch_add(&io_adm_busy, 1);
875 atomic_fetch_add(&io_adm_other, 1);
877 return &fiber_io_done;
880static void test_io_channel_admission() {
886 size_t cap = (size_t)1 << engine->config.reactor.ring_magnitude;
889 atomic_store(&io_adm_ok, 0);
890 atomic_store(&io_adm_busy, 0);
891 atomic_store(&io_adm_other, 0);
895 for (
size_t i = 0; i < cap + extra; i++) {
902 "ok=%d busy=%d other=%d cap=%zu", atomic_load(&io_adm_ok), atomic_load(&io_adm_busy),
903 atomic_load(&io_adm_other), cap
905 TEST_CHECK(atomic_load(&io_adm_ok) == (
int)cap);
906 TEST_CHECK(atomic_load(&io_adm_busy) == (
int)extra);
918static atomic_int io_retry_ok;
919static atomic_int io_retry_failed;
929 atomic_fetch_add(&io_retry_ok, 1);
931 atomic_fetch_add(&io_retry_failed, 1);
933 return &fiber_io_done;
936static void test_io_busy_retry() {
942 size_t cap = (size_t)1 << engine->config.reactor.ring_magnitude;
945 atomic_store(&io_retry_ok, 0);
946 atomic_store(&io_retry_failed, 0);
948 for (
size_t i = 0; i <
n; i++) {
954 TEST_MSG(
"ok=%d failed=%d n=%zu", atomic_load(&io_retry_ok), atomic_load(&io_retry_failed),
n);
956 TEST_CHECK(atomic_load(&io_retry_failed) == 0);
968static void test_io_fiber_pipe() {
979 char out[] =
"ping-pong";
980 size_t n =
sizeof(out) - 1;
982 fiber_io_args_t w = {.fd = fds[1], .buf = out, .len =
n};
983 fiber_io_args_t rd = {.fd = fds[0], .buf = in, .len =
sizeof(in)};
1001typedef struct fiber_drain_args_t {
1003 uint64_t duration_ns;
1005} fiber_drain_args_t;
1011 fiber_drain_args_t *a = (fiber_drain_args_t *)arg;
1013 return &fiber_io_done;
1021 fiber_drain_args_t *a = (fiber_drain_args_t *)arg;
1024 return &fiber_io_done;
1033static void test_io_fiber_drain() {
1039 fiber_drain_args_t
sleeper = {
1040 .sched = sched, .duration_ns = 30ULL *
SRN_NS_PER_MS, .err =
nullptr
1042 fiber_drain_args_t starter = {
1043 .sched = sched, .duration_ns = 30ULL *
SRN_NS_PER_MS, .err =
nullptr
1061static void test_io_fiber_file() {
1067 char path[] =
"/tmp/serene_fiber_file_XXXXXX";
1068 int fd = mkstemp(path);
1075 fiber_io_args_t a = {.fd = fd, .buf = buf, .len =
sizeof(buf)};
1095typedef struct fiber_accept_args_t {
1097 struct sockaddr_storage peer;
1099} fiber_accept_args_t;
1103 fiber_accept_args_t *a = (fiber_accept_args_t *)arg;
1104 a->res =
srn_fiber_accept(a->listener, (
struct sockaddr *)&a->peer,
sizeof(a->peer));
1105 return &fiber_io_done;
1108typedef struct fiber_connect_args_t {
1110 struct sockaddr_in to;
1112} fiber_connect_args_t;
1116 fiber_connect_args_t *a = (fiber_connect_args_t *)arg;
1118 return &fiber_io_done;
1125static void test_io_fiber_accept_connect() {
1132 int lst = io_tcp_listener(&port);
1134 int cli = socket(AF_INET, SOCK_STREAM, 0);
1138 fiber_accept_args_t acc = {.listener = lst};
1139 fiber_connect_args_t con = {.fd = cli, .to = {.sin_family = AF_INET, .sin_port = port}};
1140 con.to.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
1149 TEST_CHECK(acc.res.addrlen >=
sizeof(
struct sockaddr_in));
1150 TEST_CHECK(((
struct sockaddr_in *)&acc.peer)->sin_family == AF_INET);
1161typedef struct fiber_recvfrom_args_t {
1164 struct sockaddr_storage from;
1166} fiber_recvfrom_args_t;
1170 fiber_recvfrom_args_t *a = (fiber_recvfrom_args_t *)arg;
1172 a->fd, a->buf,
sizeof(a->buf), 0, (
struct sockaddr *)&a->from,
sizeof(a->from)
1174 return &fiber_io_done;
1177typedef struct fiber_sendto_args_t {
1179 struct sockaddr_in to;
1181} fiber_sendto_args_t;
1185 fiber_sendto_args_t *a = (fiber_sendto_args_t *)arg;
1186 a->res =
srn_fiber_sendto(a->fd,
"ping", 4, 0, (
struct sockaddr *)&a->to,
sizeof(a->to));
1187 return &fiber_io_done;
1193static void test_io_fiber_dgram() {
1200 int rcv = io_udp_bound(&port);
1201 int snd = socket(AF_INET, SOCK_DGRAM, 0);
1205 fiber_recvfrom_args_t r = {.fd = rcv};
1206 fiber_sendto_args_t s = {.fd = snd, .to = {.sin_family = AF_INET, .sin_port = port}};
1207 s.to.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
1218 TEST_CHECK(r.res.addrlen >=
sizeof(
struct sockaddr_in));
1229static void test_io_inline_helpers() {
1234 struct sockaddr_in a = {.sin_family = AF_INET};
1235 a.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
1242 socklen_t gl =
sizeof(got);
1251 char path[] =
"/tmp/serene_io_helper_XXXXXX";
1252 int tmp = mkstemp(path);
1270static void test_io_open_nonblock() {
1274 int fl = fcntl(r.
fd, F_GETFL, 0);
1278 int fdfl = fcntl(r.
fd, F_GETFD, 0);
1289static void test_io_fiber_fifo() {
1295 char dir[] =
"/tmp/serene_fifo_XXXXXX";
1298 TEST_ASSERT(snprintf(path,
sizeof(path),
"%s/fifo", dir) > 0);
1306 char out[] =
"fifo-ping";
1307 size_t n =
sizeof(out) - 1;
1309 fiber_io_args_t w = {.fd = wend.
fd, .buf = out, .len =
n};
1310 fiber_io_args_t rd = {.fd = rend.
fd, .buf = in, .len =
sizeof(in)};
static srn_fiber_result_t sleeper(srn_context_t *ctx, void *arg)
#define TEST_ASSERT(cond)
Internal reactor backend interface.
#define RELEASE_CONTEXT(x)
#define ASSERT_NOT_NULL(x)
#define SHUTDOWN_ENGINE(mm, engine)
#define MAKE_ENGINE(mm, engine)
#define MAKE_CONTEXT(engine, x)
struct srn_error_t srn_error_t
struct srn_scheduler_t srn_scheduler_t
#define HAS_ERROR(x)
True when x – a value carrying an ERROR_HEADER – has an error attached.
@ CHANNEL_BUSY
The channel already has a full ring's worth of operations in flight.
srn_fiber_t * srn_fiber_spawn(srn_context_t *ctx, srn_fiber_entry_t entry, void *arg)
Make and schedule a fiber with every default, the engine's scheduler, the configured stack size,...
void * srn_fiber_result_t
What a fiber's entry produces, type-erased.
srn_error_t * srn_fiber_listen(int fd, int backlog)
Mark fd as a listening socket with the given backlog, like listen(2).
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).
srn_error_t * srn_fiber_close(int fd)
Close fd, like close(2).
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).
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...
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...
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...
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...
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...
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...
srn_error_t * srn_fiber_bind(int fd, const struct sockaddr *addr, socklen_t addrlen)
Bind fd to addr/addrlen, like bind(2).
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...
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...
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...
The fiber facing IO API: blocking looking calls a fiber uses to do IO.
struct srn_io_recvfrom_result_t srn_io_recvfrom_result_t
The result of srn_fiber_recvfrom.
#define SRN_WITH_RETRY(err,...)
Run block and retry it (yielding between attempts through srn_fiber_io_retry) while err holds the tra...
struct srn_io_accept_result_t srn_io_accept_result_t
The result of srn_fiber_accept.
struct srn_io_size_result_t srn_io_size_result_t
The result of a byte transfer op (read/write/sendto/recvmsg/sendmsg).
bool srn_reactor_idle(srn_reactor_t *reactor)
Whether the reactor has no operations in flight.
@ SRN_REACTOR_IO_ACCEPT
Accept a pending connection on the listening socket fd, with flags (the accept4 flags).
@ SRN_REACTOR_IO_READ
Read from a descriptor into buf. Uses fd, buf, len, offset.
@ SRN_REACTOR_IO_SENDTO
Send buf/len on fd to the address in addr/addrlen with flags.
@ SRN_REACTOR_IO_CONNECT
Connect fd to the address in addr/addrlen.
@ SRN_REACTOR_IO_RECVFROM
Receive a datagram from fd into buf/len with flags.
@ SRN_REACTOR_IO_RECVMSG
Receive into the struct msghdr that buf points at, on fd, with flags (scatter/gather and ancillary da...
@ SRN_REACTOR_IO_WRITE
Write buf to a descriptor. Uses fd, buf, len, offset.
@ SRN_REACTOR_IO_POLL
Wait until fd is ready for the events requested in flags (EPOLLIN/EPOLLOUT) without performing any tr...
@ SRN_REACTOR_IO_SENDMSG
Send the struct msghdr that buf points at, on fd, with flags.
static srn_reactor_io_completion_t reactor_test_await(srn_reactor_t *r, size_t channel)
static void reactor_test_start(srn_reactor_t *r, size_t nworkers)
static void reactor_test_submit(srn_reactor_t *r, size_t channel, const srn_reactor_io_request_t *req)
void srn_sched_drain(srn_scheduler_t *sched)
Ask a running scheduler to wind down gracefully.
void srn_sched_run(srn_scheduler_t *sched, size_t nworkers)
Run the scheduler with nworkers os threads draining it, returning once the pool goes quiescent (every...
A runtime error, a tag classifying the failure and a human-readable message.
The result of an op that yields a descriptor (srn_fiber_socket, srn_fiber_open).
void(* wake)(srn_reactor_t *reactor)
A completion, the result of one submission, echoed back on the channel.
void * fiber_data
The fiber_data of the submission this completes.
uint32_t addrlen
For SRN_REACTOR_IO_ACCEPT and SRN_REACTOR_IO_RECVFROM, the actual length of the address written into ...
size_t result
The result of the operation on success, bytes transferred for the transfers, the accepted descriptor ...
A submission, one request a fiber places on its channel.
const srn_reactor_backend_t * backend
The chosen kernel mechanism and its private state, set at activation.
#define SRN_NS_PER_MS
nanoseconds in one millisecond
uint64_t srn_now_ns(void)
The current time on the monotonic clock, in nanoseconds.