Commit 125c4a265a

kcbanner <kcbanner@gmail.com>
2025-08-09 21:57:15
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
1 parent 5f7a0bb
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
lib/build-web/main.js
@@ -6,7 +6,7 @@ const domSummary = {
   stepCount: document.getElementById("summaryStepCount"),
   status: document.getElementById("summaryStatus"),
 };
-const domButtonRebuild = document.getElementById("buttonRebuild");
+let domButtonRebuild = document.getElementById("buttonRebuild");
 const domStepList = document.getElementById("stepList");
 let domSteps = [];
 
@@ -114,7 +114,13 @@ 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);
@@ -161,11 +167,15 @@ function updateBuildStatus(s) {
   if (active) {
     domSummary.status.classList.add("status-running");
     domSummary.status.classList.remove("status-idle");
-    domButtonRebuild.disabled = true;
+    if (domButtonRebuild) {
+      domButtonRebuild.disabled = true;
+    }
   } else {
     domSummary.status.classList.remove("status-running");
     domSummary.status.classList.add("status-idle");
-    domButtonRebuild.disabled = false;
+    if (domButtonRebuild) {
+      domButtonRebuild.disabled = false;
+    }
   }
   if (reset_time_reports) {
     // Grey out and collapse all the time reports
lib/build-web/main.zig
@@ -30,6 +30,7 @@ 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;
@@ -160,7 +161,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);
+    js.hello(step_list.len, hdr.status, hdr.flags.time_report, hdr.flags.supports_recv);
 }
 fn statusUpdateMessage(msg_bytes: []u8) Allocator.Error!void {
     if (msg_bytes.len < @sizeOf(abi.StatusUpdate)) @panic("malformed StatusUpdate message");
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) {
lib/std/Build/abi.zig
@@ -103,7 +103,9 @@ pub const Hello = extern struct {
     pub const Flags = packed struct(u16) {
         /// Whether time reporting is enabled.
         time_report: bool,
-        _: u15 = 0,
+        /// If this platform supports receiving messages from the client
+        supports_recv: bool,
+        _: u14 = 0,
     };
 };
 /// WebSocket server->client.
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),
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| {