authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-08-10 11:06:36-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2025-08-10 11:06:36-07:00
loge25168d01be35bea2c8e811fb5fddab847ae6938
tree2b085034a0cd8e46fb778dea134d48e55e7be418
parent6eeceb4b14965a11d3f6b2969b82a696ba99bffc
parent95f57c3369181123aa7ebd4a9654fc489f8bb4fd
signaturebadge-check Signed by PGP key B5690EEEBB952194

Merge pull request #24774 from kcbanner/fixup_webui_windows

Fix `respondWebSocket`, use overlapped sockets on Windows, and re-enable --webui

6 files changed, 75 insertions(+), 29 deletions(-)

lib/build-web/index.html+2
...@@ -105,6 +105,7 @@...@@ -105,6 +105,7 @@
105 <th scope="col">Semantic Analysis</th>105 <th scope="col">Semantic Analysis</th>
106 <th scope="col">Code Generation</th>106 <th scope="col">Code Generation</th>
107 <th scope="col">Linking</th>107 <th scope="col">Linking</th>
108 <th scope="col">Total</th>
108 </tr>109 </tr>
109 </thead>110 </thead>
110 <!-- HTML does not allow placing a 'slot' inside of a 'tbody' for backwards-compatibility111 <!-- HTML does not allow placing a 'slot' inside of a 'tbody' for backwards-compatibility
...@@ -125,6 +126,7 @@...@@ -125,6 +126,7 @@
125 <th scope="col">Semantic Analysis</th>126 <th scope="col">Semantic Analysis</th>
126 <th scope="col">Code Generation</th>127 <th scope="col">Code Generation</th>
127 <th scope="col">Linking</th>128 <th scope="col">Linking</th>
129 <th scope="col">Total</th>
128 </tr>130 </tr>
129 </thead>131 </thead>
130 <!-- HTML does not allow placing a 'slot' inside of a 'tbody' for backwards-compatibility132 <!-- HTML does not allow placing a 'slot' inside of a 'tbody' for backwards-compatibility
lib/build-web/time_report.zig+4
...@@ -175,6 +175,7 @@ pub fn compileResultMessage(msg_bytes: []u8) error{OutOfMemory}!void {...@@ -175,6 +175,7 @@ pub fn compileResultMessage(msg_bytes: []u8) error{OutOfMemory}!void {
175 \\ <td>{D}</td>175 \\ <td>{D}</td>
176 \\ <td>{D}</td>176 \\ <td>{D}</td>
177 \\ <td>{D}</td>177 \\ <td>{D}</td>
178 \\ <td>{D}</td>
178 \\</tr>179 \\</tr>
179 \\180 \\
180 , .{181 , .{
...@@ -182,6 +183,7 @@ pub fn compileResultMessage(msg_bytes: []u8) error{OutOfMemory}!void {...@@ -182,6 +183,7 @@ pub fn compileResultMessage(msg_bytes: []u8) error{OutOfMemory}!void {
182 file.ns_sema,183 file.ns_sema,
183 file.ns_codegen,184 file.ns_codegen,
184 file.ns_link,185 file.ns_link,
186 file.ns_sema + file.ns_codegen + file.ns_link,
185 });187 });
186 }188 }
187 if (slowest_files.len > max_table_rows) {189 if (slowest_files.len > max_table_rows) {
...@@ -203,6 +205,7 @@ pub fn compileResultMessage(msg_bytes: []u8) error{OutOfMemory}!void {...@@ -203,6 +205,7 @@ pub fn compileResultMessage(msg_bytes: []u8) error{OutOfMemory}!void {
203 \\ <td>{D}</td>205 \\ <td>{D}</td>
204 \\ <td>{D}</td>206 \\ <td>{D}</td>
205 \\ <td>{D}</td>207 \\ <td>{D}</td>
208 \\ <td>{D}</td>
206 \\</tr>209 \\</tr>
207 \\210 \\
208 , .{211 , .{
...@@ -212,6 +215,7 @@ pub fn compileResultMessage(msg_bytes: []u8) error{OutOfMemory}!void {...@@ -212,6 +215,7 @@ pub fn compileResultMessage(msg_bytes: []u8) error{OutOfMemory}!void {
212 decl.ns_sema,215 decl.ns_sema,
213 decl.ns_codegen,216 decl.ns_codegen,
214 decl.ns_link,217 decl.ns_link,
218 decl.ns_sema + decl.ns_codegen + decl.ns_link,
215 });219 });
216 }220 }
217 if (slowest_decls.len > max_table_rows) {221 if (slowest_decls.len > max_table_rows) {
lib/std/Build/WebServer.zig+2-11
...@@ -65,16 +65,6 @@ pub fn init(opts: Options) WebServer {...@@ -65,16 +65,6 @@ pub fn init(opts: Options) WebServer {
65 std.process.fatal("--webui not yet implemented for single-threaded builds", .{});65 std.process.fatal("--webui not yet implemented for single-threaded builds", .{});
66 }66 }
6767
68 if (builtin.os.tag == .windows) {
69 // At the time of writing, there are two bugs in the standard library which break this feature on Windows:
70 // * Reading from a socket on one thread while writing to it on another seems to deadlock.
71 // * Vectored writes to sockets currently trigger an infinite loop when a buffer has length 0.
72 //
73 // Both of these bugs are expected to be solved by changes which are currently in the unmerged
74 // 'wrangle-writer-buffering' branch. Until that makes it in, this must remain disabled.
75 std.process.fatal("--webui is currently disabled on Windows due to bugs", .{});
76 }
77
78 const all_steps = opts.all_steps;68 const all_steps = opts.all_steps;
7969
80 const step_names_trailing = opts.gpa.alloc(u8, len: {70 const step_names_trailing = opts.gpa.alloc(u8, len: {
...@@ -297,7 +287,8 @@ fn serveWebSocket(ws: *WebServer, sock: *http.Server.WebSocket) !noreturn {...@@ -297,7 +287,8 @@ fn serveWebSocket(ws: *WebServer, sock: *http.Server.WebSocket) !noreturn {
297 copy.* = @atomicLoad(u8, shared, .monotonic);287 copy.* = @atomicLoad(u8, shared, .monotonic);
298 }288 }
299289
300 _ = try std.Thread.spawn(.{}, recvWebSocketMessages, .{ ws, sock });290 const recv_thread = try std.Thread.spawn(.{}, recvWebSocketMessages, .{ ws, sock });
291 defer recv_thread.join();
301292
302 {293 {
303 const hello_header: abi.Hello = .{294 const hello_header: abi.Hello = .{
lib/std/http/Server.zig-1
...@@ -546,7 +546,6 @@ pub const Request = struct {...@@ -546,7 +546,6 @@ pub const Request = struct {
546 try out.writeAll("connection: upgrade\r\nupgrade: websocket\r\nsec-websocket-accept: ");546 try out.writeAll("connection: upgrade\r\nupgrade: websocket\r\nsec-websocket-accept: ");
547 const base64_digest = try out.writableArray(28);547 const base64_digest = try out.writableArray(28);
548 assert(std.base64.standard.Encoder.encode(base64_digest, &digest).len == base64_digest.len);548 assert(std.base64.standard.Encoder.encode(base64_digest, &digest).len == base64_digest.len);
549 out.advance(base64_digest.len);
550 try out.writeAll("\r\n");549 try out.writeAll("\r\n");
551550
552 for (options.extra_headers) |header| {551 for (options.extra_headers) |header| {
lib/std/net.zig+63-11
...@@ -259,6 +259,7 @@ pub const Address = extern union {...@@ -259,6 +259,7 @@ pub const Address = extern union {
259 /// Sets SO_REUSEADDR and SO_REUSEPORT on POSIX.259 /// Sets SO_REUSEADDR and SO_REUSEPORT on POSIX.
260 /// Sets SO_REUSEADDR on Windows, which is roughly equivalent.260 /// Sets SO_REUSEADDR on Windows, which is roughly equivalent.
261 reuse_address: bool = false,261 reuse_address: bool = false,
262 /// Sets O_NONBLOCK.
262 force_nonblocking: bool = false,263 force_nonblocking: bool = false,
263 };264 };
264265
...@@ -1998,11 +1999,8 @@ pub const Stream = struct {...@@ -1998,11 +1999,8 @@ pub const Stream = struct {
1998 return n;1999 return n;
1999 }2000 }
20002001
2001 fn streamBufs(r: *Reader, bufs: []windows.ws2_32.WSABUF) Error!u32 {2002 fn handleRecvError(winsock_error: windows.ws2_32.WinsockError) Error!void {
2002 var n: u32 = undefined;2003 switch (winsock_error) {
2003 var flags: u32 = 0;
2004 const rc = windows.ws2_32.WSARecvFrom(r.net_stream.handle, bufs.ptr, @intCast(bufs.len), &n, &flags, null, null, null, null);
2005 if (rc != 0) switch (windows.ws2_32.WSAGetLastError()) {
2006 .WSAECONNRESET => return error.ConnectionResetByPeer,2004 .WSAECONNRESET => return error.ConnectionResetByPeer,
2007 .WSAEFAULT => unreachable, // a pointer is not completely contained in user address space.2005 .WSAEFAULT => unreachable, // a pointer is not completely contained in user address space.
2008 .WSAEINPROGRESS, .WSAEINTR => unreachable, // deprecated and removed in WSA 2.22006 .WSAEINPROGRESS, .WSAEINTR => unreachable, // deprecated and removed in WSA 2.2
...@@ -2013,10 +2011,39 @@ pub const Stream = struct {...@@ -2013,10 +2011,39 @@ pub const Stream = struct {
2013 .WSAENOTCONN => return error.SocketNotConnected,2011 .WSAENOTCONN => return error.SocketNotConnected,
2014 .WSAEWOULDBLOCK => return error.WouldBlock,2012 .WSAEWOULDBLOCK => return error.WouldBlock,
2015 .WSANOTINITIALISED => unreachable, // WSAStartup must be called before this function2013 .WSANOTINITIALISED => unreachable, // WSAStartup must be called before this function
2016 .WSA_IO_PENDING => unreachable, // not using overlapped I/O2014 .WSA_IO_PENDING => unreachable,
2017 .WSA_OPERATION_ABORTED => unreachable, // not using overlapped I/O2015 .WSA_OPERATION_ABORTED => unreachable, // not using overlapped I/O
2018 else => |err| return windows.unexpectedWSAError(err),2016 else => |err| return windows.unexpectedWSAError(err),
2017 }
2018 }
2019
2020 fn streamBufs(r: *Reader, bufs: []windows.ws2_32.WSABUF) Error!u32 {
2021 var flags: u32 = 0;
2022 var overlapped: windows.OVERLAPPED = std.mem.zeroes(windows.OVERLAPPED);
2023
2024 var n: u32 = undefined;
2025 if (windows.ws2_32.WSARecv(
2026 r.net_stream.handle,
2027 bufs.ptr,
2028 @intCast(bufs.len),
2029 &n,
2030 &flags,
2031 &overlapped,
2032 null,
2033 ) == windows.ws2_32.SOCKET_ERROR) switch (windows.ws2_32.WSAGetLastError()) {
2034 .WSA_IO_PENDING => {
2035 var result_flags: u32 = undefined;
2036 if (windows.ws2_32.WSAGetOverlappedResult(
2037 r.net_stream.handle,
2038 &overlapped,
2039 &n,
2040 windows.TRUE,
2041 &result_flags,
2042 ) == windows.FALSE) try handleRecvError(windows.ws2_32.WSAGetLastError());
2043 },
2044 else => |winsock_error| try handleRecvError(winsock_error),
2019 };2045 };
2046
2020 return n;2047 return n;
2021 }2048 }
2022 },2049 },
...@@ -2136,10 +2163,8 @@ pub const Stream = struct {...@@ -2136,10 +2163,8 @@ pub const Stream = struct {
2136 return io_w.consume(n);2163 return io_w.consume(n);
2137 }2164 }
21382165
2139 fn sendBufs(handle: Stream.Handle, bufs: []windows.ws2_32.WSABUF) Error!u32 {2166 fn handleSendError(winsock_error: windows.ws2_32.WinsockError) Error!void {
2140 var n: u32 = undefined;2167 switch (winsock_error) {
2141 const rc = windows.ws2_32.WSASend(handle, bufs.ptr, @intCast(bufs.len), &n, 0, null, null);
2142 if (rc == windows.ws2_32.SOCKET_ERROR) switch (windows.ws2_32.WSAGetLastError()) {
2143 .WSAECONNABORTED => return error.ConnectionResetByPeer,2168 .WSAECONNABORTED => return error.ConnectionResetByPeer,
2144 .WSAECONNRESET => return error.ConnectionResetByPeer,2169 .WSAECONNRESET => return error.ConnectionResetByPeer,
2145 .WSAEFAULT => unreachable, // a pointer is not completely contained in user address space.2170 .WSAEFAULT => unreachable, // a pointer is not completely contained in user address space.
...@@ -2155,10 +2180,37 @@ pub const Stream = struct {...@@ -2155,10 +2180,37 @@ pub const Stream = struct {
2155 .WSAESHUTDOWN => unreachable, // cannot send on a socket after write shutdown2180 .WSAESHUTDOWN => unreachable, // cannot send on a socket after write shutdown
2156 .WSAEWOULDBLOCK => return error.WouldBlock,2181 .WSAEWOULDBLOCK => return error.WouldBlock,
2157 .WSANOTINITIALISED => unreachable, // WSAStartup must be called before this function2182 .WSANOTINITIALISED => unreachable, // WSAStartup must be called before this function
2158 .WSA_IO_PENDING => unreachable, // not using overlapped I/O2183 .WSA_IO_PENDING => unreachable,
2159 .WSA_OPERATION_ABORTED => unreachable, // not using overlapped I/O2184 .WSA_OPERATION_ABORTED => unreachable, // not using overlapped I/O
2160 else => |err| return windows.unexpectedWSAError(err),2185 else => |err| return windows.unexpectedWSAError(err),
2186 }
2187 }
2188
2189 fn sendBufs(handle: Stream.Handle, bufs: []windows.ws2_32.WSABUF) Error!u32 {
2190 var n: u32 = undefined;
2191 var overlapped: windows.OVERLAPPED = std.mem.zeroes(windows.OVERLAPPED);
2192 if (windows.ws2_32.WSASend(
2193 handle,
2194 bufs.ptr,
2195 @intCast(bufs.len),
2196 &n,
2197 0,
2198 &overlapped,
2199 null,
2200 ) == windows.ws2_32.SOCKET_ERROR) switch (windows.ws2_32.WSAGetLastError()) {
2201 .WSA_IO_PENDING => {
2202 var result_flags: u32 = undefined;
2203 if (windows.ws2_32.WSAGetOverlappedResult(
2204 handle,
2205 &overlapped,
2206 &n,
2207 windows.TRUE,
2208 &result_flags,
2209 ) == windows.FALSE) try handleSendError(windows.ws2_32.WSAGetLastError());
2210 },
2211 else => |winsock_error| try handleSendError(winsock_error),
2161 };2212 };
2213
2162 return n;2214 return n;
2163 }2215 }
2164 },2216 },
lib/std/posix.zig+4-6
...@@ -3615,13 +3615,11 @@ pub const SocketError = error{...@@ -3615,13 +3615,11 @@ pub const SocketError = error{
36153615
3616pub fn socket(domain: u32, socket_type: u32, protocol: u32) SocketError!socket_t {3616pub fn socket(domain: u32, socket_type: u32, protocol: u32) SocketError!socket_t {
3617 if (native_os == .windows) {3617 if (native_os == .windows) {
3618 // NOTE: windows translates the SOCK.NONBLOCK/SOCK.CLOEXEC flags into3618 // These flags are not actually part of the Windows API, instead they are converted here for compatibility
3619 // windows-analogous operations
3620 const filtered_sock_type = socket_type & ~@as(u32, SOCK.NONBLOCK | SOCK.CLOEXEC);3619 const filtered_sock_type = socket_type & ~@as(u32, SOCK.NONBLOCK | SOCK.CLOEXEC);
3621 const flags: u32 = if ((socket_type & SOCK.CLOEXEC) != 0)3620 var flags: u32 = windows.ws2_32.WSA_FLAG_OVERLAPPED;
3622 windows.ws2_32.WSA_FLAG_NO_HANDLE_INHERIT3621 if ((socket_type & SOCK.CLOEXEC) != 0) flags |= windows.ws2_32.WSA_FLAG_NO_HANDLE_INHERIT;
3623 else3622
3624 0;
3625 const rc = try windows.WSASocketW(3623 const rc = try windows.WSASocketW(
3626 @bitCast(domain),3624 @bitCast(domain),
3627 @bitCast(filtered_sock_type),3625 @bitCast(filtered_sock_type),