Zig Version
0.16.0 / 0.17.0-dev.292+fc1c83a36
Steps to Reproduce and Observed Behavior
I'm having problems using std.Io.Threaded with socket receive timeouts (SO_RCVTIMEO).
This is on macOS, but I believe can be reproduced on other unix-y platforms.
This C program attempts to use socket receive timeouts and blocking reads to wait one second and then timeout:
#include <arpa/inet.h>#include <errno.h>#include <netinet/in.h>#include <stdio.h>#include <string.h>#include <sys/socket.h>#include <sys/time.h>#include <unistd.h>
static int die(const char *msg) {
fprintf(stderr, "%s: %s\n", msg, strerror(errno));
return 1;
}
int main(void) {
int listen_fd = socket(AF_INET, SOCK_STREAM, 0);
if (listen_fd < 0) return die("socket(listen)");
int one = 1;
setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof one);
struct sockaddr_in laddr = {0};
laddr.sin_family = AF_INET;
laddr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
laddr.sin_port = 0;
if (bind(listen_fd, (struct sockaddr *)&laddr, sizeof laddr) < 0)
return die("bind");
if (listen(listen_fd, 1) < 0) return die("listen");
socklen_t laddr_len = sizeof laddr;
if (getsockname(listen_fd, (struct sockaddr *)&laddr, &laddr_len) < 0)
return die("getsockname");
int client_fd = socket(AF_INET, SOCK_STREAM, 0);
if (client_fd < 0) return die("socket(client)");
if (connect(client_fd, (struct sockaddr *)&laddr, sizeof laddr) < 0)
return die("connect");
int accepted_fd = accept(listen_fd, NULL, NULL);
if (accepted_fd < 0) return die("accept");
struct timeval tv = { .tv_sec = 1, .tv_usec = 0 };
if (setsockopt(accepted_fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof tv) < 0)
return die("setsockopt(SO_RCVTIMEO)");
char buf[64];
ssize_t n = recv(accepted_fd, buf, sizeof buf, 0);
int saved = errno;
printf("recv returned %zd, errno=%d (%s)\n",
n, saved, strerror(saved));
close(accepted_fd);
close(client_fd);
close(listen_fd);
return 0;
}
You can see that it takes 1 second and generates EAGAIN:
$ gcc repro.c
$ time ./a.out
recv returned -1, errno=35 (Resource temporarily unavailable)
./a.out 0.00s user 0.00s system 0% cpu 1.004 total
The equivalent Zig program using std.Io:
conststd=@import("std");pubfnmain(init:std.process.Init)!void{constio=init.io;varlisten_addr=trystd.Io.net.IpAddress.resolve(io,"127.0.0.1",0);varserver=trylisten_addr.listen(io,.{.reuse_address=true});deferserver.deinit(io);varconnect_addr=server.socket.address;varclient=tryconnect_addr.connect(io,.{.mode=.stream});deferclient.close(io);varaccepted=tryserver.accept(io);deferaccepted.close(io);consttv:std.posix.timeval=.{.sec=1,.usec=0};trystd.posix.setsockopt(accepted.socket.handle,std.posix.SOL.SOCKET,std.posix.SO.RCVTIMEO,std.mem.asBytes(&tv),);varbuf:[64]u8=undefined;varsr=accepted.reader(io,&buf);trysr.interface.fill(1);std.debug.print("fill returned ok (unexpected)\n",.{});}
Causes this programmer bug:
$ zig build-exe repro.zig
$ time ./repro
thread 82525610 panic: programmer bug caused syscall error: AGAIN
.zvm/master/lib/std/Io/Threaded.zig:14192:34: 0x102db14f3 in errnoBug (repro)
if (is_debug) std.debug.panic("programmer bug caused syscall error: {t}", .{err});
^
.zvm/master/lib/std/Io/Threaded.zig:12759:52: 0x102dedd63 in netReadPosix (repro)
.AGAIN => |err| return errnoBug(err),
^
.zvm/master/lib/std/Io/Threaded.zig:12694:24: 0x102ded57b in netRead (repro)
return netReadPosix(socket_handle, data);
^
.zvm/master/lib/std/Io/Threaded.zig:2566:32: 0x102decf8b in operate (repro)
.net_read = netRead(o.socket_handle, o.data) catch |err| switch (err) {
^
.zvm/master/lib/std/Io.zig:462:29: 0x102d1ca1f in operate (repro)
return io.vtable.operate(io.userdata, operation);
^
.zvm/master/lib/std/Io/net.zig:1251:31: 0x102e125bf in read (repro)
return (try io.operate(.{ .net_read = .{
^
.zvm/master/lib/std/Io/net.zig:1305:36: 0x102e12227 in readVec (repro)
const n = r.stream.read(io, dest) catch |err| {
^
.zvm/master/lib/std/Io/Reader.zig:1123:56: 0x102d1795b in fillUnbuffered (repro)
while (r.end < r.seek + n) _ = try r.vtable.readVec(r, &bufs);
^
.zvm/master/lib/std/Io/Reader.zig:1109:26: 0x102d1776f in fill (repro)
return fillUnbuffered(r, n);
^
/private/tmp/zig-io-eagain-repro/repro.zig:39:26: 0x102e0db4f in main (repro)
try sr.interface.fill(1);
^
.zvm/master/lib/std/start.zig:750:30: 0x102e0e133 in callMain (repro)
return wrapMain(root.main(.{
^
???:?:?: 0x182133da3 in start (/usr/lib/dyld)
zsh: abort ./repro
./repro 0.08s user 0.01s system 7% cpu 1.084 total
Expected Behavior
I would like to be able to use send and receive timeouts with std.Io.
### Zig Version
0.16.0 / 0.17.0-dev.292+fc1c83a36
### Steps to Reproduce and Observed Behavior
I'm having problems using ``std.Io.Threaded`` with socket receive timeouts (``SO_RCVTIMEO``).
This is on macOS, but I believe can be reproduced on other unix-y platforms.
This C program attempts to use socket receive timeouts and blocking reads to wait one second and then timeout:
```c
#include <arpa/inet.h>
#include <errno.h>
#include <netinet/in.h>
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <unistd.h>
static int die(const char *msg) {
fprintf(stderr, "%s: %s\n", msg, strerror(errno));
return 1;
}
int main(void) {
int listen_fd = socket(AF_INET, SOCK_STREAM, 0);
if (listen_fd < 0) return die("socket(listen)");
int one = 1;
setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof one);
struct sockaddr_in laddr = {0};
laddr.sin_family = AF_INET;
laddr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
laddr.sin_port = 0;
if (bind(listen_fd, (struct sockaddr *)&laddr, sizeof laddr) < 0)
return die("bind");
if (listen(listen_fd, 1) < 0) return die("listen");
socklen_t laddr_len = sizeof laddr;
if (getsockname(listen_fd, (struct sockaddr *)&laddr, &laddr_len) < 0)
return die("getsockname");
int client_fd = socket(AF_INET, SOCK_STREAM, 0);
if (client_fd < 0) return die("socket(client)");
if (connect(client_fd, (struct sockaddr *)&laddr, sizeof laddr) < 0)
return die("connect");
int accepted_fd = accept(listen_fd, NULL, NULL);
if (accepted_fd < 0) return die("accept");
struct timeval tv = { .tv_sec = 1, .tv_usec = 0 };
if (setsockopt(accepted_fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof tv) < 0)
return die("setsockopt(SO_RCVTIMEO)");
char buf[64];
ssize_t n = recv(accepted_fd, buf, sizeof buf, 0);
int saved = errno;
printf("recv returned %zd, errno=%d (%s)\n",
n, saved, strerror(saved));
close(accepted_fd);
close(client_fd);
close(listen_fd);
return 0;
}
```
You can see that it takes 1 second and generates EAGAIN:
```
$ gcc repro.c
$ time ./a.out
recv returned -1, errno=35 (Resource temporarily unavailable)
./a.out 0.00s user 0.00s system 0% cpu 1.004 total
```
The equivalent Zig program using ``std.Io``:
```zig
const std = @import("std");
pub fn main(init: std.process.Init) !void {
const io = init.io;
var listen_addr = try std.Io.net.IpAddress.resolve(io, "127.0.0.1", 0);
var server = try listen_addr.listen(io, .{ .reuse_address = true });
defer server.deinit(io);
var connect_addr = server.socket.address;
var client = try connect_addr.connect(io, .{ .mode = .stream });
defer client.close(io);
var accepted = try server.accept(io);
defer accepted.close(io);
const tv: std.posix.timeval = .{ .sec = 1, .usec = 0 };
try std.posix.setsockopt(
accepted.socket.handle,
std.posix.SOL.SOCKET,
std.posix.SO.RCVTIMEO,
std.mem.asBytes(&tv),
);
var buf: [64]u8 = undefined;
var sr = accepted.reader(io, &buf);
try sr.interface.fill(1);
std.debug.print("fill returned ok (unexpected)\n", .{});
}
```
Causes this *programmer bug*:
```
$ zig build-exe repro.zig
$ time ./repro
thread 82525610 panic: programmer bug caused syscall error: AGAIN
.zvm/master/lib/std/Io/Threaded.zig:14192:34: 0x102db14f3 in errnoBug (repro)
if (is_debug) std.debug.panic("programmer bug caused syscall error: {t}", .{err});
^
.zvm/master/lib/std/Io/Threaded.zig:12759:52: 0x102dedd63 in netReadPosix (repro)
.AGAIN => |err| return errnoBug(err),
^
.zvm/master/lib/std/Io/Threaded.zig:12694:24: 0x102ded57b in netRead (repro)
return netReadPosix(socket_handle, data);
^
.zvm/master/lib/std/Io/Threaded.zig:2566:32: 0x102decf8b in operate (repro)
.net_read = netRead(o.socket_handle, o.data) catch |err| switch (err) {
^
.zvm/master/lib/std/Io.zig:462:29: 0x102d1ca1f in operate (repro)
return io.vtable.operate(io.userdata, operation);
^
.zvm/master/lib/std/Io/net.zig:1251:31: 0x102e125bf in read (repro)
return (try io.operate(.{ .net_read = .{
^
.zvm/master/lib/std/Io/net.zig:1305:36: 0x102e12227 in readVec (repro)
const n = r.stream.read(io, dest) catch |err| {
^
.zvm/master/lib/std/Io/Reader.zig:1123:56: 0x102d1795b in fillUnbuffered (repro)
while (r.end < r.seek + n) _ = try r.vtable.readVec(r, &bufs);
^
.zvm/master/lib/std/Io/Reader.zig:1109:26: 0x102d1776f in fill (repro)
return fillUnbuffered(r, n);
^
/private/tmp/zig-io-eagain-repro/repro.zig:39:26: 0x102e0db4f in main (repro)
try sr.interface.fill(1);
^
.zvm/master/lib/std/start.zig:750:30: 0x102e0e133 in callMain (repro)
return wrapMain(root.main(.{
^
???:?:?: 0x182133da3 in start (/usr/lib/dyld)
zsh: abort ./repro
./repro 0.08s user 0.01s system 7% cpu 1.084 total
```
### Expected Behavior
I would like to be able to use send and receive timeouts with ``std.Io``.