From 125c4a265aa7cfe82b3fc0c1ac7de75a07315411 Mon Sep 17 00:00:00 2001 From: kcbanner Date: Sat, 9 Aug 2025 15:57:15 -0400 Subject: Fix `respondWebSocket`, enable --webui on Windows This commit re-enables the --webui functionality on windows, with the caveat that rebuild functionality is still disabled (due to deadlocks caused by reading to / writing from the same non-overlapped socket on multiple threads). I updated the UI to be aware of this, and hide the `Rebuild` button. http.Server: Remove incorrect advance() call. This was causing browsers to disconnect the websocket, as we were sending undefined bytes. build.WebServer: Re-enable on windows, but disable functionality that requires receiving messages from the client build-web: Show total times in tables --- lib/std/Build/WebServer.zig | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) (limited to 'lib/std/Build/WebServer.zig') diff --git a/lib/std/Build/WebServer.zig b/lib/std/Build/WebServer.zig index 868aabe67e..5a62f7c8c6 100644 --- a/lib/std/Build/WebServer.zig +++ b/lib/std/Build/WebServer.zig @@ -65,16 +65,6 @@ pub fn init(opts: Options) WebServer { std.process.fatal("--webui not yet implemented for single-threaded builds", .{}); } - if (builtin.os.tag == .windows) { - // At the time of writing, there are two bugs in the standard library which break this feature on Windows: - // * Reading from a socket on one thread while writing to it on another seems to deadlock. - // * Vectored writes to sockets currently trigger an infinite loop when a buffer has length 0. - // - // Both of these bugs are expected to be solved by changes which are currently in the unmerged - // 'wrangle-writer-buffering' branch. Until that makes it in, this must remain disabled. - std.process.fatal("--webui is currently disabled on Windows due to bugs", .{}); - } - const all_steps = opts.all_steps; const step_names_trailing = opts.gpa.alloc(u8, len: { @@ -297,13 +287,20 @@ fn serveWebSocket(ws: *WebServer, sock: *http.Server.WebSocket) !noreturn { copy.* = @atomicLoad(u8, shared, .monotonic); } - _ = try std.Thread.spawn(.{}, recvWebSocketMessages, .{ ws, sock }); + // 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 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), -- cgit v1.2.3 From 95f57c3369181123aa7ebd4a9654fc489f8bb4fd Mon Sep 17 00:00:00 2001 From: kcbanner Date: Sat, 9 Aug 2025 18:48:15 -0400 Subject: 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 --- lib/build-web/main.js | 16 ++-------- lib/build-web/main.zig | 3 +- lib/std/Build/WebServer.zig | 10 ++---- lib/std/Build/abi.zig | 4 +-- lib/std/net.zig | 74 ++++++++++++++++++++++++++++++++++++++------- lib/std/posix.zig | 10 +++--- 6 files changed, 74 insertions(+), 43 deletions(-) (limited to 'lib/std/Build/WebServer.zig') diff --git a/lib/build-web/main.js b/lib/build-web/main.js index c6fb86ec63..481c33f024 100644 --- a/lib/build-web/main.js +++ b/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 diff --git a/lib/build-web/main.zig b/lib/build-web/main.zig index 1aff5c374a..e971f6de48 100644 --- a/lib/build-web/main.zig +++ b/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"); diff --git a/lib/std/Build/WebServer.zig b/lib/std/Build/WebServer.zig index 5a62f7c8c6..454e9196b9 100644 --- a/lib/std/Build/WebServer.zig +++ b/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), diff --git a/lib/std/Build/abi.zig b/lib/std/Build/abi.zig index b47fbbdb39..d5b02d951a 100644 --- a/lib/std/Build/abi.zig +++ b/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. diff --git a/lib/std/net.zig b/lib/std/net.zig index f63aa6dd0a..45abc42568 100644 --- a/lib/std/net.zig +++ b/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; } }, diff --git a/lib/std/posix.zig b/lib/std/posix.zig index 5717d57a45..90ab157f0c 100644 --- a/lib/std/posix.zig +++ b/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), -- cgit v1.2.3