zig

fork of https://codeberg.org/ziglang/zig
Log | Files | Refs | README | LICENSE

commit e25168d01be35bea2c8e811fb5fddab847ae6938 (tree)
parent 6eeceb4b14965a11d3f6b2969b82a696ba99bffc
Author: Andrew Kelley <andrew@ziglang.org>
Date:   Sun, 10 Aug 2025 11:06:36 -0700

Merge pull request #24774 from kcbanner/fixup_webui_windows

Fix `respondWebSocket`, use overlapped sockets on Windows, and re-enable --webui
Diffstat:
Mlib/build-web/index.html | 2++
Mlib/build-web/time_report.zig | 4++++
Mlib/std/Build/WebServer.zig | 13++-----------
Mlib/std/http/Server.zig | 1-
Mlib/std/net.zig | 74+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------
Mlib/std/posix.zig | 10++++------
6 files changed, 75 insertions(+), 29 deletions(-)

diff --git a/lib/build-web/index.html b/lib/build-web/index.html @@ -105,6 +105,7 @@ <th scope="col">Semantic Analysis</th> <th scope="col">Code Generation</th> <th scope="col">Linking</th> + <th scope="col">Total</th> </tr> </thead> <!-- HTML does not allow placing a 'slot' inside of a 'tbody' for backwards-compatibility @@ -125,6 +126,7 @@ <th scope="col">Semantic Analysis</th> <th scope="col">Code Generation</th> <th scope="col">Linking</th> + <th scope="col">Total</th> </tr> </thead> <!-- HTML does not allow placing a 'slot' inside of a 'tbody' for backwards-compatibility diff --git a/lib/build-web/time_report.zig b/lib/build-web/time_report.zig @@ -175,6 +175,7 @@ pub fn compileResultMessage(msg_bytes: []u8) error{OutOfMemory}!void { \\ <td>{D}</td> \\ <td>{D}</td> \\ <td>{D}</td> + \\ <td>{D}</td> \\</tr> \\ , .{ @@ -182,6 +183,7 @@ pub fn compileResultMessage(msg_bytes: []u8) error{OutOfMemory}!void { file.ns_sema, file.ns_codegen, file.ns_link, + file.ns_sema + file.ns_codegen + file.ns_link, }); } if (slowest_files.len > max_table_rows) { @@ -203,6 +205,7 @@ pub fn compileResultMessage(msg_bytes: []u8) error{OutOfMemory}!void { \\ <td>{D}</td> \\ <td>{D}</td> \\ <td>{D}</td> + \\ <td>{D}</td> \\</tr> \\ , .{ @@ -212,6 +215,7 @@ pub fn compileResultMessage(msg_bytes: []u8) error{OutOfMemory}!void { decl.ns_sema, decl.ns_codegen, decl.ns_link, + decl.ns_sema + decl.ns_codegen + decl.ns_link, }); } if (slowest_decls.len > max_table_rows) { diff --git 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,7 +287,8 @@ fn serveWebSocket(ws: *WebServer, sock: *http.Server.WebSocket) !noreturn { copy.* = @atomicLoad(u8, shared, .monotonic); } - _ = try std.Thread.spawn(.{}, recvWebSocketMessages, .{ ws, sock }); + const recv_thread = try std.Thread.spawn(.{}, recvWebSocketMessages, .{ ws, sock }); + defer recv_thread.join(); { const hello_header: abi.Hello = .{ diff --git a/lib/std/http/Server.zig b/lib/std/http/Server.zig @@ -546,7 +546,6 @@ pub const Request = struct { try out.writeAll("connection: upgrade\r\nupgrade: websocket\r\nsec-websocket-accept: "); const base64_digest = try out.writableArray(28); assert(std.base64.standard.Encoder.encode(base64_digest, &digest).len == base64_digest.len); - out.advance(base64_digest.len); try out.writeAll("\r\n"); for (options.extra_headers) |header| { diff --git 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 @@ -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),