Merge pull request #24774 from kcbanner/fixup_webui_windows

Fix `respondWebSocket`, use overlapped sockets on Windows, and re-enable --webui
This commit is contained in:
Andrew Kelley 2025-08-10 11:06:36 -07:00 committed by GitHub
commit e25168d01b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 75 additions and 29 deletions

View file

@ -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

View file

@ -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) {

View file

@ -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 = .{

View file

@ -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| {

View file

@ -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;
}
},

View file

@ -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),