From 2665f6a5efe61a1d366944b6c49cc6456de75f9c Mon Sep 17 00:00:00 2001 From: Halil Durak Date: Fri, 26 Sep 2025 17:41:17 +0300 Subject: [PATCH 1/7] feat(iocp): prefer async connect --- src/backend/iocp.zig | 79 ++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 72 insertions(+), 7 deletions(-) diff --git a/src/backend/iocp.zig b/src/backend/iocp.zig index 671d08c..08a0d0c 100644 --- a/src/backend/iocp.zig +++ b/src/backend/iocp.zig @@ -545,13 +545,58 @@ pub const Loop = struct { .close => |v| .{ .result = .{ .close = windows.CloseHandle(v.fd) } }, .connect => |*v| action: { - const result = windows.ws2_32.connect(asSocket(v.socket), &v.addr.any, @as(i32, @intCast(v.addr.getOsSockLen()))); - if (result != 0) { - const err = windows.ws2_32.WSAGetLastError(); - break :action switch (err) { - else => .{ .result = .{ .connect = windows.unexpectedWSAError(err) } }, + const as_socket = asSocket(v.socket); + // Associate our socket with loop's completion port. + self.associate_fd(v.socket) catch unreachable; + + // ConnectEx requires socket to be initially bound. + // https://github.com/tigerbeetle/tigerbeetle/blob/main/src/io/windows.zig#L467 + { + const inaddr_any = std.mem.zeroes([4]u8); + const bind_addr = std.net.Address.initIp4(inaddr_any, 0); + // NOTE: This may return many other errors; we should extend `ConnectError` set. + posix.bind(as_socket, &bind_addr.any, bind_addr.getOsSockLen()) catch unreachable; + } + + // NOTE: This can be declared in somewhere else; it all happens in comptime though so no issue putting it here. + const LPFN_CONNECTEX = *const fn ( + Socket: windows.ws2_32.SOCKET, + SockAddr: *const windows.ws2_32.sockaddr, + SockLen: posix.socklen_t, + SendBuf: ?*const anyopaque, + SendBufLen: windows.DWORD, + BytesSent: *windows.DWORD, + Overlapped: *windows.OVERLAPPED, + ) callconv(.winapi) windows.BOOL; + + // Dynamically load the ConnectEx function. + const ConnectEx = windows.loadWinsockExtensionFunction(LPFN_CONNECTEX, as_socket, windows.ws2_32.WSAID_CONNECTEX) catch |err| switch (err) { + error.OperationNotSupported => unreachable, // Something other than sockets has given. + error.FileDescriptorNotASocket => unreachable, // Must be preferred on a socket. + error.ShortRead => unreachable, + error.Unexpected => break :action .{ .result = .{ .connect = error.Unexpected } }, + }; + + // Connect attempt. + var bytes_transferred: windows.DWORD = 0; + const result = ConnectEx(as_socket, &v.addr.any, v.addr.getOsSockLen(), null, 0, &bytes_transferred, &completion.overlapped); + + // If ConnectEx returns `windows.TRUE`, it means operation completed immediately. + // Which is most of the time not the case; we should check it anyways though! + if (result == windows.FALSE) { + // NOTE: This may return many other errors; we should extend `ConnectError` set. + break :action switch (windows.ws2_32.WSAGetLastError()) { + .WSA_IO_PENDING, .WSAEWOULDBLOCK, .WSA_IO_INCOMPLETE => .{ .submitted = {} }, // Operation will be completed in the future. + else => |err| .{ .result = .{ .connect = windows.unexpectedWSAError(err) } }, }; } + + // Surprisingly, we connected immediately. + // The following is necessary for various functions (shutdown, getsockopt, setsockopt, getsockname, getpeername) to work. + // https://learn.microsoft.com/en-us/windows/win32/api/mswsock/nc-mswsock-lpfn_connectex#remarks + // https://stackoverflow.com/questions/13598530/connectex-requires-the-socket-to-be-initially-bound-but-to-what + _ = windows.ws2_32.setsockopt(asSocket(v.socket), windows.ws2_32.SOL.SOCKET, windows.ws2_32.SO.UPDATE_CONNECT_CONTEXT, null, 0); + break :action .{ .result = .{ .connect = {} } }; }, @@ -960,7 +1005,7 @@ pub const Completion = struct { /// operation for the completion. pub fn perform(self: *Completion) Result { return switch (self.op) { - .noop, .close, .connect, .shutdown, .timer, .cancel => { + .noop, .close, .shutdown, .timer, .cancel => { std.log.warn("perform op={s}", .{@tagName(self.op)}); unreachable; }, @@ -985,7 +1030,27 @@ pub const Completion = struct { return .{ .accept = self.op.accept.internal_accept_socket.? }; }, - .read => |*v| { + .connect => |*v| r: { + const as_socket = asSocket(v.socket); + var transferred: windows.DWORD = 0; + var flags: windows.DWORD = 0; + const result = windows.ws2_32.WSAGetOverlappedResult(as_socket, &self.overlapped, &transferred, windows.FALSE, &flags); + + // Connected successfully. + if (result == windows.TRUE) { + // The following is necessary for various functions (shutdown, getsockopt, setsockopt, getsockname, getpeername) to work. + // https://learn.microsoft.com/en-us/windows/win32/api/mswsock/nc-mswsock-lpfn_connectex#remarks + // https://stackoverflow.com/questions/13598530/connectex-requires-the-socket-to-be-initially-bound-but-to-what + _ = windows.ws2_32.setsockopt(as_socket, windows.ws2_32.SOL.SOCKET, windows.ws2_32.SO.UPDATE_CONNECT_CONTEXT, null, 0); + + break :r .{ .connect = {} }; + } + + // We got an error. + break :r .{ .connect = windows.unexpectedWSAError(windows.ws2_32.WSAGetLastError()) }; + }, + + .read => |*v| r: { var bytes_transferred: windows.DWORD = 0; const result = windows.kernel32.GetOverlappedResult(v.fd, &self.overlapped, &bytes_transferred, windows.FALSE); if (result == windows.FALSE) { From ffdf269b587115002c2e81f4f21355fd0908e9c0 Mon Sep 17 00:00:00 2001 From: Halil Durak Date: Fri, 26 Sep 2025 17:46:15 +0300 Subject: [PATCH 2/7] remove unused block label --- src/backend/iocp.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/backend/iocp.zig b/src/backend/iocp.zig index 08a0d0c..219a1d7 100644 --- a/src/backend/iocp.zig +++ b/src/backend/iocp.zig @@ -1050,7 +1050,7 @@ pub const Completion = struct { break :r .{ .connect = windows.unexpectedWSAError(windows.ws2_32.WSAGetLastError()) }; }, - .read => |*v| r: { + .read => |*v| { var bytes_transferred: windows.DWORD = 0; const result = windows.kernel32.GetOverlappedResult(v.fd, &self.overlapped, &bytes_transferred, windows.FALSE); if (result == windows.FALSE) { From ecfdb668af2ac1fd7dc5aa3ab68290e8ac6ccc4e Mon Sep 17 00:00:00 2001 From: Halil Durak Date: Fri, 26 Sep 2025 17:50:17 +0300 Subject: [PATCH 3/7] move `LPFN_CONNECTEX` to `windows.zig` --- src/backend/iocp.zig | 13 +------------ src/windows.zig | 11 +++++++++++ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/backend/iocp.zig b/src/backend/iocp.zig index 219a1d7..52b126a 100644 --- a/src/backend/iocp.zig +++ b/src/backend/iocp.zig @@ -558,19 +558,8 @@ pub const Loop = struct { posix.bind(as_socket, &bind_addr.any, bind_addr.getOsSockLen()) catch unreachable; } - // NOTE: This can be declared in somewhere else; it all happens in comptime though so no issue putting it here. - const LPFN_CONNECTEX = *const fn ( - Socket: windows.ws2_32.SOCKET, - SockAddr: *const windows.ws2_32.sockaddr, - SockLen: posix.socklen_t, - SendBuf: ?*const anyopaque, - SendBufLen: windows.DWORD, - BytesSent: *windows.DWORD, - Overlapped: *windows.OVERLAPPED, - ) callconv(.winapi) windows.BOOL; - // Dynamically load the ConnectEx function. - const ConnectEx = windows.loadWinsockExtensionFunction(LPFN_CONNECTEX, as_socket, windows.ws2_32.WSAID_CONNECTEX) catch |err| switch (err) { + const ConnectEx = windows.loadWinsockExtensionFunction(windows.LPFN_CONNECTEX, as_socket, windows.ws2_32.WSAID_CONNECTEX) catch |err| switch (err) { error.OperationNotSupported => unreachable, // Something other than sockets has given. error.FileDescriptorNotASocket => unreachable, // Must be preferred on a socket. error.ShortRead => unreachable, diff --git a/src/windows.zig b/src/windows.zig index 7e13a73..4de0f90 100644 --- a/src/windows.zig +++ b/src/windows.zig @@ -36,6 +36,17 @@ pub const CreateIoCompletionPort = windows.CreateIoCompletionPort; pub extern "kernel32" fn DeleteFileW(lpFileName: [*:0]const u16) callconv(windows.WINAPI) windows.BOOL; +/// https://learn.microsoft.com/en-us/windows/win32/api/mswsock/nc-mswsock-lpfn_connectex +pub const LPFN_CONNECTEX = *const fn ( + Socket: windows.ws2_32.SOCKET, + SockAddr: *const windows.ws2_32.sockaddr, + SockLen: posix.socklen_t, + SendBuf: ?*const anyopaque, + SendBufLen: windows.DWORD, + BytesSent: *windows.DWORD, + Overlapped: *windows.OVERLAPPED, +) callconv(.winapi) windows.BOOL; + pub const exp = struct { pub const STATUS_PENDING = 0x00000103; pub const STILL_ACTIVE = STATUS_PENDING; From a3eec8f431282713b27578bb1b620a41215372ff Mon Sep 17 00:00:00 2001 From: Halil Durak Date: Fri, 26 Sep 2025 17:50:47 +0300 Subject: [PATCH 4/7] prefer already casted `as_socket` --- src/backend/iocp.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/backend/iocp.zig b/src/backend/iocp.zig index 52b126a..7b272eb 100644 --- a/src/backend/iocp.zig +++ b/src/backend/iocp.zig @@ -584,7 +584,7 @@ pub const Loop = struct { // The following is necessary for various functions (shutdown, getsockopt, setsockopt, getsockname, getpeername) to work. // https://learn.microsoft.com/en-us/windows/win32/api/mswsock/nc-mswsock-lpfn_connectex#remarks // https://stackoverflow.com/questions/13598530/connectex-requires-the-socket-to-be-initially-bound-but-to-what - _ = windows.ws2_32.setsockopt(asSocket(v.socket), windows.ws2_32.SOL.SOCKET, windows.ws2_32.SO.UPDATE_CONNECT_CONTEXT, null, 0); + _ = windows.ws2_32.setsockopt(as_socket, windows.ws2_32.SOL.SOCKET, windows.ws2_32.SO.UPDATE_CONNECT_CONTEXT, null, 0); break :action .{ .result = .{ .connect = {} } }; }, From 4d6c9b43ce2c036777f747b5e3981b11ecf1da8d Mon Sep 17 00:00:00 2001 From: Halil Durak Date: Fri, 26 Sep 2025 17:51:05 +0300 Subject: [PATCH 5/7] prefer `completion.handle()` instead of `v.socket` --- src/backend/iocp.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/backend/iocp.zig b/src/backend/iocp.zig index 7b272eb..7dadd42 100644 --- a/src/backend/iocp.zig +++ b/src/backend/iocp.zig @@ -547,7 +547,7 @@ pub const Loop = struct { .connect => |*v| action: { const as_socket = asSocket(v.socket); // Associate our socket with loop's completion port. - self.associate_fd(v.socket) catch unreachable; + self.associate_fd(completion.handle().?) catch unreachable; // ConnectEx requires socket to be initially bound. // https://github.com/tigerbeetle/tigerbeetle/blob/main/src/io/windows.zig#L467 From 30390849ff1229215385aef0e6800af482bd9f83 Mon Sep 17 00:00:00 2001 From: Halil Durak Date: Fri, 26 Sep 2025 18:03:12 +0300 Subject: [PATCH 6/7] forward `loadWinsockExtensionFunction` --- src/windows.zig | 1 + 1 file changed, 1 insertion(+) diff --git a/src/windows.zig b/src/windows.zig index 4de0f90..6d149c7 100644 --- a/src/windows.zig +++ b/src/windows.zig @@ -33,6 +33,7 @@ pub const QueryPerformanceFrequency = windows.QueryPerformanceFrequency; pub const GetQueuedCompletionStatusEx = windows.GetQueuedCompletionStatusEx; pub const PostQueuedCompletionStatus = windows.PostQueuedCompletionStatus; pub const CreateIoCompletionPort = windows.CreateIoCompletionPort; +pub const loadWinsockExtensionFunction = windows.loadWinsockExtensionFunction; pub extern "kernel32" fn DeleteFileW(lpFileName: [*:0]const u16) callconv(windows.WINAPI) windows.BOOL; From 40f8f68633995757f435630f43fa98cc3aa0cd59 Mon Sep 17 00:00:00 2001 From: Halil Durak Date: Fri, 26 Sep 2025 18:09:15 +0300 Subject: [PATCH 7/7] make `Completion.handle` function be aware of `connect` --- src/backend/iocp.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/backend/iocp.zig b/src/backend/iocp.zig index 7dadd42..3a92e8c 100644 --- a/src/backend/iocp.zig +++ b/src/backend/iocp.zig @@ -984,7 +984,7 @@ pub const Completion = struct { /// Returns a handle for the current operation if it makes sense. fn handle(self: Completion) ?windows.HANDLE { return switch (self.op) { - inline .accept => |*v| v.socket, + inline .accept, .connect => |*v| v.socket, inline .read, .pread, .write, .pwrite, .recv, .send, .recvfrom, .sendto => |*v| v.fd, else => null, };