Commit 95f57c3369

kcbanner <kcbanner@gmail.com>
2025-08-10 00:48:15
net: Always set WSA_FLAG_OVERLAPPED when creating Windows sockets. Rework send and receive logic to use overlapped I/O. build-web: Remove the now-redundant supports_recv logic
1 parent 125c4a2
Changed files (6)
lib/build-web/main.js
@@ -6,7 +6,7 @@ const domSummary = {
   stepCount: document.getElementById("summaryStepCount"),
   status: document.getElementById("summaryStatus"),
 };
-let domButtonRebuild = document.getElementById("buttonRebuild");
+const domButtonRebuild = document.getElementById("buttonRebuild");
 const domStepList = document.getElementById("stepList");
 let domSteps = [];
 
@@ -114,13 +114,7 @@ function hello(
   steps_len,
   build_status,
   time_report,
-  supports_recv,
 ) {
-  if (!supports_recv && domButtonRebuild) {
-    domButtonRebuild.remove();
-    domButtonRebuild = null;
-  }
-
   domSummary.stepCount.textContent = steps_len;
   updateBuildStatus(build_status);
   setConnectionStatus("", false);
@@ -167,15 +161,11 @@ function updateBuildStatus(s) {
   if (active) {
     domSummary.status.classList.add("status-running");
     domSummary.status.classList.remove("status-idle");
-    if (domButtonRebuild) {
-      domButtonRebuild.disabled = true;
-    }
+    domButtonRebuild.disabled = true;
   } else {
     domSummary.status.classList.remove("status-running");
     domSummary.status.classList.add("status-idle");
-    if (domButtonRebuild) {
-      domButtonRebuild.disabled = false;
-    }
+    domButtonRebuild.disabled = false;
   }
   if (reset_time_reports) {
     // Grey out and collapse all the time reports
lib/build-web/main.zig
@@ -30,7 +30,6 @@ const js = struct {
         steps_len: u32,
         status: abi.BuildStatus,
         time_report: bool,
-        supports_recv: bool,
     ) void;
     extern "core" fn updateBuildStatus(status: abi.BuildStatus) void;
     extern "core" fn updateStepStatus(step_idx: u32) void;
@@ -161,7 +160,7 @@ fn helloMessage(msg_bytes: []align(4) u8) Allocator.Error!void {
     step_list = steps;
     step_list_data = duped_step_name_data;
 
-    js.hello(step_list.len, hdr.status, hdr.flags.time_report, hdr.flags.supports_recv);
+    js.hello(step_list.len, hdr.status, hdr.flags.time_report);
 }
 fn statusUpdateMessage(msg_bytes: []u8) Allocator.Error!void {
     if (msg_bytes.len < @sizeOf(abi.StatusUpdate)) @panic("malformed StatusUpdate message");
lib/std/Build/abi.zig
@@ -103,9 +103,7 @@ pub const Hello = extern struct {
     pub const Flags = packed struct(u16) {
         /// Whether time reporting is enabled.
         time_report: bool,
-        /// If this platform supports receiving messages from the client
-        supports_recv: bool,
-        _: u14 = 0,
+        _: u15 = 0,
     };
 };
 /// WebSocket server->client.
lib/std/Build/WebServer.zig
@@ -287,20 +287,14 @@ fn serveWebSocket(ws: *WebServer, sock: *http.Server.WebSocket) !noreturn {
         copy.* = @atomicLoad(u8, shared, .monotonic);
     }
 
-    // Calling WSARecvFrom on one thread while another calls WSASend deadlocks.
-    // This functionality is disabled until std.net uses overlapped sockets on Windows.
-    const supports_recv = builtin.os.tag != .windows;
-    const recv_thread = if (supports_recv)
-        try std.Thread.spawn(.{}, recvWebSocketMessages, .{ ws, sock })
-    else {};
-    defer if (supports_recv) recv_thread.join();
+    const recv_thread = try std.Thread.spawn(.{}, recvWebSocketMessages, .{ ws, sock });
+    defer recv_thread.join();
 
     {
         const hello_header: abi.Hello = .{
             .status = prev_build_status,
             .flags = .{
                 .time_report = ws.graph.time_report,
-                .supports_recv = supports_recv,
             },
             .timestamp = ws.now(),
             .steps_len = @intCast(ws.all_steps.len),
lib/std/net.zig
@@ -259,6 +259,7 @@ pub const Address = extern union {
         /// Sets SO_REUSEADDR and SO_REUSEPORT on POSIX.
         /// Sets SO_REUSEADDR on Windows, which is roughly equivalent.
         reuse_address: bool = false,
+        /// Sets O_NONBLOCK.
         force_nonblocking: bool = false,
     };
 
@@ -1998,11 +1999,8 @@ pub const Stream = struct {
                 return n;
             }
 
-            fn streamBufs(r: *Reader, bufs: []windows.ws2_32.WSABUF) Error!u32 {
-                var n: u32 = undefined;
-                var flags: u32 = 0;
-                const rc = windows.ws2_32.WSARecvFrom(r.net_stream.handle, bufs.ptr, @intCast(bufs.len), &n, &flags, null, null, null, null);
-                if (rc != 0) switch (windows.ws2_32.WSAGetLastError()) {
+            fn handleRecvError(winsock_error: windows.ws2_32.WinsockError) Error!void {
+                switch (winsock_error) {
                     .WSAECONNRESET => return error.ConnectionResetByPeer,
                     .WSAEFAULT => unreachable, // a pointer is not completely contained in user address space.
                     .WSAEINPROGRESS, .WSAEINTR => unreachable, // deprecated and removed in WSA 2.2
@@ -2013,10 +2011,39 @@ pub const Stream = struct {
                     .WSAENOTCONN => return error.SocketNotConnected,
                     .WSAEWOULDBLOCK => return error.WouldBlock,
                     .WSANOTINITIALISED => unreachable, // WSAStartup must be called before this function
-                    .WSA_IO_PENDING => unreachable, // not using overlapped I/O
+                    .WSA_IO_PENDING => unreachable,
                     .WSA_OPERATION_ABORTED => unreachable, // not using overlapped I/O
                     else => |err| return windows.unexpectedWSAError(err),
+                }
+            }
+
+            fn streamBufs(r: *Reader, bufs: []windows.ws2_32.WSABUF) Error!u32 {
+                var flags: u32 = 0;
+                var overlapped: windows.OVERLAPPED = std.mem.zeroes(windows.OVERLAPPED);
+
+                var n: u32 = undefined;
+                if (windows.ws2_32.WSARecv(
+                    r.net_stream.handle,
+                    bufs.ptr,
+                    @intCast(bufs.len),
+                    &n,
+                    &flags,
+                    &overlapped,
+                    null,
+                ) == windows.ws2_32.SOCKET_ERROR) switch (windows.ws2_32.WSAGetLastError()) {
+                    .WSA_IO_PENDING => {
+                        var result_flags: u32 = undefined;
+                        if (windows.ws2_32.WSAGetOverlappedResult(
+                            r.net_stream.handle,
+                            &overlapped,
+                            &n,
+                            windows.TRUE,
+                            &result_flags,
+                        ) == windows.FALSE) try handleRecvError(windows.ws2_32.WSAGetLastError());
+                    },
+                    else => |winsock_error| try handleRecvError(winsock_error),
                 };
+
                 return n;
             }
         },
@@ -2136,10 +2163,8 @@ pub const Stream = struct {
                 return io_w.consume(n);
             }
 
-            fn sendBufs(handle: Stream.Handle, bufs: []windows.ws2_32.WSABUF) Error!u32 {
-                var n: u32 = undefined;
-                const rc = windows.ws2_32.WSASend(handle, bufs.ptr, @intCast(bufs.len), &n, 0, null, null);
-                if (rc == windows.ws2_32.SOCKET_ERROR) switch (windows.ws2_32.WSAGetLastError()) {
+            fn handleSendError(winsock_error: windows.ws2_32.WinsockError) Error!void {
+                switch (winsock_error) {
                     .WSAECONNABORTED => return error.ConnectionResetByPeer,
                     .WSAECONNRESET => return error.ConnectionResetByPeer,
                     .WSAEFAULT => unreachable, // a pointer is not completely contained in user address space.
@@ -2155,10 +2180,37 @@ pub const Stream = struct {
                     .WSAESHUTDOWN => unreachable, // cannot send on a socket after write shutdown
                     .WSAEWOULDBLOCK => return error.WouldBlock,
                     .WSANOTINITIALISED => unreachable, // WSAStartup must be called before this function
-                    .WSA_IO_PENDING => unreachable, // not using overlapped I/O
+                    .WSA_IO_PENDING => unreachable,
                     .WSA_OPERATION_ABORTED => unreachable, // not using overlapped I/O
                     else => |err| return windows.unexpectedWSAError(err),
+                }
+            }
+
+            fn sendBufs(handle: Stream.Handle, bufs: []windows.ws2_32.WSABUF) Error!u32 {
+                var n: u32 = undefined;
+                var overlapped: windows.OVERLAPPED = std.mem.zeroes(windows.OVERLAPPED);
+                if (windows.ws2_32.WSASend(
+                    handle,
+                    bufs.ptr,
+                    @intCast(bufs.len),
+                    &n,
+                    0,
+                    &overlapped,
+                    null,
+                ) == windows.ws2_32.SOCKET_ERROR) switch (windows.ws2_32.WSAGetLastError()) {
+                    .WSA_IO_PENDING => {
+                        var result_flags: u32 = undefined;
+                        if (windows.ws2_32.WSAGetOverlappedResult(
+                            handle,
+                            &overlapped,
+                            &n,
+                            windows.TRUE,
+                            &result_flags,
+                        ) == windows.FALSE) try handleSendError(windows.ws2_32.WSAGetLastError());
+                    },
+                    else => |winsock_error| try handleSendError(winsock_error),
                 };
+
                 return n;
             }
         },
lib/std/posix.zig
@@ -3615,13 +3615,11 @@ pub const SocketError = error{
 
 pub fn socket(domain: u32, socket_type: u32, protocol: u32) SocketError!socket_t {
     if (native_os == .windows) {
-        // NOTE: windows translates the SOCK.NONBLOCK/SOCK.CLOEXEC flags into
-        // windows-analogous operations
+        // These flags are not actually part of the Windows API, instead they are converted here for compatibility
         const filtered_sock_type = socket_type & ~@as(u32, SOCK.NONBLOCK | SOCK.CLOEXEC);
-        const flags: u32 = if ((socket_type & SOCK.CLOEXEC) != 0)
-            windows.ws2_32.WSA_FLAG_NO_HANDLE_INHERIT
-        else
-            0;
+        var flags: u32 = windows.ws2_32.WSA_FLAG_OVERLAPPED;
+        if ((socket_type & SOCK.CLOEXEC) != 0) flags |= windows.ws2_32.WSA_FLAG_NO_HANDLE_INHERIT;
+
         const rc = try windows.WSASocketW(
             @bitCast(domain),
             @bitCast(filtered_sock_type),