Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion src/backend/epoll.zig
Original file line number Diff line number Diff line change
Expand Up @@ -1212,7 +1212,11 @@ pub const Operation = union(OperationType) {
socket: posix.socket_t,
addr: posix.sockaddr = undefined,
addr_size: posix.socklen_t = @sizeOf(posix.sockaddr),
flags: u32 = posix.SOCK.CLOEXEC,
/// Non-blocking, like the sockets TCP.init creates on this backend:
/// a write is performed once the fd reports writable, and on a
/// blocking socket one larger than the free buffer stalls the loop.
/// Linux does not inherit O_NONBLOCK from the listener, so ask.
flags: u32 = posix.SOCK.CLOEXEC | posix.SOCK.NONBLOCK,
},

connect: struct {
Expand Down
11 changes: 9 additions & 2 deletions src/posix.zig
Original file line number Diff line number Diff line change
Expand Up @@ -333,12 +333,19 @@ pub fn accept(
addr_size: *posix.socklen_t,
flags: u32,
) AcceptError!posix.socket_t {
const have_accept4 = !builtin.target.os.tag.isDarwin() and builtin.target.os.tag != .haiku;
while (true) {
const rc = system.accept(sock, addr, addr_size);
const rc = if (have_accept4)
system.accept4(sock, addr, addr_size, flags)
else
system.accept(sock, addr, addr_size);
switch (posix.errno(rc)) {
.SUCCESS => {
const fd: posix.socket_t = @intCast(rc);
if (flags & posix.SOCK.CLOEXEC != 0) try setCloexec(fd);
if (!have_accept4) {
errdefer close(fd);
try setSockFlags(fd, flags);
}
return fd;
},
.INTR => continue,
Expand Down
65 changes: 65 additions & 0 deletions src/watcher/tcp.zig
Original file line number Diff line number Diff line change
Expand Up @@ -708,6 +708,71 @@ fn TCPTests(comptime xev: type, comptime Impl: type) type {
// 4. Set up a queued write with the remaining buffer, shutdown() the socket afterwards
// 5. Set up a receiver that loops until it receives the entire buffer
// 6. Assert send_buf == recv_buf
test "TCP: accepted sockets are non-blocking on epoll" {
// epoll performs a write once the fd reports writable, which only
// promises some room: on a blocking socket, a write larger than
// that blocks the whole loop. TCP.init already makes its sockets
// non-blocking there, and Linux, unlike the BSDs kqueue runs on,
// does not pass the listener's O_NONBLOCK on to an accepted one.
if (xev.dynamic) return error.SkipZigTest;
if (xev.backend != .epoll) return error.SkipZigTest;

const testing = std.testing;

var loop = try xev.Loop.init(.{});
defer loop.deinit();

var address = try std.Io.net.IpAddress.parse("127.0.0.1", 0);
const server = try Impl.init(address);
try server.bind(address);
try server.listen(1);

var internal_addr = net.Address.fromIpAddress(address);
var sock_len = internal_addr.getOsSockLen();
try xev_posix.getsockname(server.fd, &internal_addr.any, &sock_len);
address = internal_addr.toIpAddress();
const client = try Impl.init(address);

var c_accept: xev.Completion = undefined;
var c_connect: xev.Completion = undefined;

var server_conn: ?Impl = null;
server.accept(&loop, &c_accept, ?Impl, &server_conn, (struct {
fn callback(
ud: ?*?Impl,
_: *xev.Loop,
_: *xev.Completion,
r: xev.AcceptError!Impl,
) xev.CallbackAction {
ud.?.* = r catch unreachable;
return .disarm;
}
}).callback);
client.connect(&loop, &c_connect, address, void, null, (struct {
fn callback(
_: ?*void,
_: *xev.Loop,
_: *xev.Completion,
_: Impl,
r: xev.ConnectError!void,
) xev.CallbackAction {
_ = r catch unreachable;
return .disarm;
}
}).callback);
try loop.run(.until_done);

const conn = server_conn orelse return error.TestUnexpectedResult;
defer xev_posix.close(conn.fd);
defer xev_posix.close(client.fd);
defer xev_posix.close(server.fd);

const rc = std.posix.system.fcntl(conn.fd, std.posix.F.GETFL, @as(usize, 0));
try testing.expectEqual(std.posix.E.SUCCESS, std.posix.errno(rc));
const nonblock: u32 = @bitCast(std.posix.O{ .NONBLOCK = true });
try testing.expect(@as(u32, @intCast(rc)) & nonblock != 0);
}

test "TCP: Queued writes" {
// We have no way to get a socket in WASI from a WASI context.
if (builtin.os.tag == .wasi) return error.SkipZigTest;
Expand Down