authorgravatar for jan.hafer@rwth-aachen.deJan Philipp Hafer <jan.hafer@rwth-aachen.de> 2023-10-31 21:54:03+01:00
committergravatar for git@vexu.euVeikka Tuominen <git@vexu.eu> 2023-11-21 18:04:33+02:00
log27b34a5b77623b106ae1e1c7c4a466608a10905e
treef1c37a8dff7023ba49d3775798d6b1f567998bed
parent40b8c993f54fce9075044252d2802452dcf1df5e

std.net: enable forcing non-blocking mode for accept

Justification: It is common for non-CPU bound short routines to do non-blocking accept to eliminate unnecessary delays before subscribing to data, for example in hardware integration tests.

2 files changed, 39 insertions(+), 6 deletions(-)

lib/std/net.zig+15-6
......@@ -1871,6 +1871,7 @@ pub const StreamServer = struct {
18711871 kernel_backlog: u31,
18721872 reuse_address: bool,
18731873 reuse_port: bool,
1874 force_nonblocking: bool,
18741875
18751876 /// `undefined` until `listen` returns successfully.
18761877 listen_address: Address,
......@@ -1888,6 +1889,9 @@ pub const StreamServer = struct {
18881889
18891890 /// Enable SO.REUSEPORT on the socket.
18901891 reuse_port: bool = false,
1892
1893 /// Force non-blocking mode.
1894 force_nonblocking: bool = false,
18911895 };
18921896
18931897 /// After this call succeeds, resources have been acquired and must
......@@ -1898,6 +1902,7 @@ pub const StreamServer = struct {
18981902 .kernel_backlog = options.kernel_backlog,
18991903 .reuse_address = options.reuse_address,
19001904 .reuse_port = options.reuse_port,
1905 .force_nonblocking = options.force_nonblocking,
19011906 .listen_address = undefined,
19021907 };
19031908 }
......@@ -1911,9 +1916,11 @@ pub const StreamServer = struct {
19111916 pub fn listen(self: *StreamServer, address: Address) !void {
19121917 const nonblock = if (std.io.is_async) os.SOCK.NONBLOCK else 0;
19131918 const sock_flags = os.SOCK.STREAM | os.SOCK.CLOEXEC | nonblock;
1919 var use_sock_flags: u32 = sock_flags;
1920 if (self.force_nonblocking) use_sock_flags |= os.SOCK.NONBLOCK;
19141921 const proto = if (address.any.family == os.AF.UNIX) @as(u32, 0) else os.IPPROTO.TCP;
19151922
1916 const sockfd = try os.socket(address.any.family, sock_flags, proto);
1923 const sockfd = try os.socket(address.any.family, use_sock_flags, proto);
19171924 self.sockfd = sockfd;
19181925 errdefer {
19191926 os.closeSocket(sockfd);
......@@ -1963,8 +1970,8 @@ pub const StreamServer = struct {
19631970 /// The system-wide limit on the total number of open files has been reached.
19641971 SystemFdQuotaExceeded,
19651972
1966 /// Not enough free memory. This often means that the memory allocation is limited
1967 /// by the socket buffer limits, not by the system memory.
1973 /// Not enough free memory. This often means that the memory allocation
1974 /// is limited by the socket buffer limits, not by the system memory.
19681975 SystemResources,
19691976
19701977 /// Socket is not listening for new connections.
......@@ -1972,6 +1979,9 @@ pub const StreamServer = struct {
19721979
19731980 ProtocolFailure,
19741981
1982 /// Socket is in non-blocking mode and there is no connection to accept.
1983 WouldBlock,
1984
19751985 /// Firewall rules forbid connection.
19761986 BlockedByFirewall,
19771987
......@@ -2007,9 +2017,8 @@ pub const StreamServer = struct {
20072017 .stream = Stream{ .handle = fd },
20082018 .address = accepted_addr,
20092019 };
2010 } else |err| switch (err) {
2011 error.WouldBlock => unreachable,
2012 else => |e| return e,
2020 } else |err| {
2021 return err;
20132022 }
20142023 }
20152024};
lib/std/net/test.zig+24
......@@ -340,3 +340,27 @@ fn generateFileName(base_name: []const u8) ![]const u8 {
340340 _ = std.fs.base64_encoder.encode(&sub_path, &random_bytes);
341341 return std.fmt.allocPrint(testing.allocator, "{s}-{s}", .{ sub_path[0..], base_name });
342342}
343
344test "non-blocking tcp server" {
345 if (builtin.os.tag == .wasi) return error.SkipZigTest;
346
347 const localhost = try net.Address.parseIp("127.0.0.1", 0);
348 var server = net.StreamServer.init(.{ .force_nonblocking = true });
349 defer server.deinit();
350 try server.listen(localhost);
351
352 const accept_err = server.accept();
353 try testing.expectError(error.WouldBlock, accept_err);
354
355 const socket_file = try net.tcpConnectToAddress(server.listen_address);
356 defer socket_file.close();
357
358 var client = try server.accept();
359 const stream = client.stream.writer();
360 try stream.print("hello from server\n", .{});
361
362 var buf: [100]u8 = undefined;
363 const len = try socket_file.read(&buf);
364 const msg = buf[0..len];
365 try testing.expect(mem.eql(u8, msg, "hello from server\n"));
366}