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 {...@@ -1871,6 +1871,7 @@ pub const StreamServer = struct {
1871 kernel_backlog: u31,1871 kernel_backlog: u31,
1872 reuse_address: bool,1872 reuse_address: bool,
1873 reuse_port: bool,1873 reuse_port: bool,
1874 force_nonblocking: bool,
18741875
1875 /// `undefined` until `listen` returns successfully.1876 /// `undefined` until `listen` returns successfully.
1876 listen_address: Address,1877 listen_address: Address,
...@@ -1888,6 +1889,9 @@ pub const StreamServer = struct {...@@ -1888,6 +1889,9 @@ pub const StreamServer = struct {
18881889
1889 /// Enable SO.REUSEPORT on the socket.1890 /// Enable SO.REUSEPORT on the socket.
1890 reuse_port: bool = false,1891 reuse_port: bool = false,
1892
1893 /// Force non-blocking mode.
1894 force_nonblocking: bool = false,
1891 };1895 };
18921896
1893 /// After this call succeeds, resources have been acquired and must1897 /// After this call succeeds, resources have been acquired and must
...@@ -1898,6 +1902,7 @@ pub const StreamServer = struct {...@@ -1898,6 +1902,7 @@ pub const StreamServer = struct {
1898 .kernel_backlog = options.kernel_backlog,1902 .kernel_backlog = options.kernel_backlog,
1899 .reuse_address = options.reuse_address,1903 .reuse_address = options.reuse_address,
1900 .reuse_port = options.reuse_port,1904 .reuse_port = options.reuse_port,
1905 .force_nonblocking = options.force_nonblocking,
1901 .listen_address = undefined,1906 .listen_address = undefined,
1902 };1907 };
1903 }1908 }
...@@ -1911,9 +1916,11 @@ pub const StreamServer = struct {...@@ -1911,9 +1916,11 @@ pub const StreamServer = struct {
1911 pub fn listen(self: *StreamServer, address: Address) !void {1916 pub fn listen(self: *StreamServer, address: Address) !void {
1912 const nonblock = if (std.io.is_async) os.SOCK.NONBLOCK else 0;1917 const nonblock = if (std.io.is_async) os.SOCK.NONBLOCK else 0;
1913 const sock_flags = os.SOCK.STREAM | os.SOCK.CLOEXEC | nonblock;1918 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;
1914 const proto = if (address.any.family == os.AF.UNIX) @as(u32, 0) else os.IPPROTO.TCP;1921 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);
1917 self.sockfd = sockfd;1924 self.sockfd = sockfd;
1918 errdefer {1925 errdefer {
1919 os.closeSocket(sockfd);1926 os.closeSocket(sockfd);
...@@ -1963,8 +1970,8 @@ pub const StreamServer = struct {...@@ -1963,8 +1970,8 @@ pub const StreamServer = struct {
1963 /// The system-wide limit on the total number of open files has been reached.1970 /// The system-wide limit on the total number of open files has been reached.
1964 SystemFdQuotaExceeded,1971 SystemFdQuotaExceeded,
19651972
1966 /// Not enough free memory. This often means that the memory allocation is limited1973 /// Not enough free memory. This often means that the memory allocation
1967 /// by the socket buffer limits, not by the system memory.1974 /// is limited by the socket buffer limits, not by the system memory.
1968 SystemResources,1975 SystemResources,
19691976
1970 /// Socket is not listening for new connections.1977 /// Socket is not listening for new connections.
...@@ -1972,6 +1979,9 @@ pub const StreamServer = struct {...@@ -1972,6 +1979,9 @@ pub const StreamServer = struct {
19721979
1973 ProtocolFailure,1980 ProtocolFailure,
19741981
1982 /// Socket is in non-blocking mode and there is no connection to accept.
1983 WouldBlock,
1984
1975 /// Firewall rules forbid connection.1985 /// Firewall rules forbid connection.
1976 BlockedByFirewall,1986 BlockedByFirewall,
19771987
...@@ -2007,9 +2017,8 @@ pub const StreamServer = struct {...@@ -2007,9 +2017,8 @@ pub const StreamServer = struct {
2007 .stream = Stream{ .handle = fd },2017 .stream = Stream{ .handle = fd },
2008 .address = accepted_addr,2018 .address = accepted_addr,
2009 };2019 };
2010 } else |err| switch (err) {2020 } else |err| {
2011 error.WouldBlock => unreachable,2021 return err;
2012 else => |e| return e,
2013 }2022 }
2014 }2023 }
2015};2024};
lib/std/net/test.zig+24
...@@ -340,3 +340,27 @@ fn generateFileName(base_name: []const u8) ![]const u8 {...@@ -340,3 +340,27 @@ fn generateFileName(base_name: []const u8) ![]const u8 {
340 _ = std.fs.base64_encoder.encode(&sub_path, &random_bytes);340 _ = std.fs.base64_encoder.encode(&sub_path, &random_bytes);
341 return std.fmt.allocPrint(testing.allocator, "{s}-{s}", .{ sub_path[0..], base_name });341 return std.fmt.allocPrint(testing.allocator, "{s}-{s}", .{ sub_path[0..], base_name });
342}342}
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}