authorgravatar for kcbanner@gmail.comCasey Banner <kcbanner@gmail.com> 2025-08-09 15:57:15-04:00
committergravatar for kcbanner@gmail.comCasey Banner <kcbanner@gmail.com> 2025-08-09 16:06:33-04:00
log125c4a265aa7cfe82b3fc0c1ac7de75a07315411
tree42d85f7fdee5e5efc406b474a14df423ad561bc9
parent5f7a0bbabfde4eeb0ff4f40f0942ef710b6104a1

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

7 files changed, 32 insertions(+), 17 deletions(-)

lib/build-web/index.html+2
......@@ -105,6 +105,7 @@
105105 <th scope="col">Semantic Analysis</th>
106106 <th scope="col">Code Generation</th>
107107 <th scope="col">Linking</th>
108 <th scope="col">Total</th>
108109 </tr>
109110 </thead>
110111 <!-- HTML does not allow placing a 'slot' inside of a 'tbody' for backwards-compatibility
......@@ -125,6 +126,7 @@
125126 <th scope="col">Semantic Analysis</th>
126127 <th scope="col">Code Generation</th>
127128 <th scope="col">Linking</th>
129 <th scope="col">Total</th>
128130 </tr>
129131 </thead>
130132 <!-- HTML does not allow placing a 'slot' inside of a 'tbody' for backwards-compatibility
lib/build-web/main.js+13-3
......@@ -6,7 +6,7 @@ const domSummary = {
66 stepCount: document.getElementById("summaryStepCount"),
77 status: document.getElementById("summaryStatus"),
88};
9const domButtonRebuild = document.getElementById("buttonRebuild");
9let domButtonRebuild = document.getElementById("buttonRebuild");
1010const domStepList = document.getElementById("stepList");
1111let domSteps = [];
1212
......@@ -114,7 +114,13 @@ function hello(
114114 steps_len,
115115 build_status,
116116 time_report,
117 supports_recv,
117118) {
119 if (!supports_recv && domButtonRebuild) {
120 domButtonRebuild.remove();
121 domButtonRebuild = null;
122 }
123
118124 domSummary.stepCount.textContent = steps_len;
119125 updateBuildStatus(build_status);
120126 setConnectionStatus("", false);
......@@ -161,11 +167,15 @@ function updateBuildStatus(s) {
161167 if (active) {
162168 domSummary.status.classList.add("status-running");
163169 domSummary.status.classList.remove("status-idle");
164 domButtonRebuild.disabled = true;
170 if (domButtonRebuild) {
171 domButtonRebuild.disabled = true;
172 }
165173 } else {
166174 domSummary.status.classList.remove("status-running");
167175 domSummary.status.classList.add("status-idle");
168 domButtonRebuild.disabled = false;
176 if (domButtonRebuild) {
177 domButtonRebuild.disabled = false;
178 }
169179 }
170180 if (reset_time_reports) {
171181 // Grey out and collapse all the time reports
lib/build-web/main.zig+2-1
......@@ -30,6 +30,7 @@ const js = struct {
3030 steps_len: u32,
3131 status: abi.BuildStatus,
3232 time_report: bool,
33 supports_recv: bool,
3334 ) void;
3435 extern "core" fn updateBuildStatus(status: abi.BuildStatus) void;
3536 extern "core" fn updateStepStatus(step_idx: u32) void;
......@@ -160,7 +161,7 @@ fn helloMessage(msg_bytes: []align(4) u8) Allocator.Error!void {
160161 step_list = steps;
161162 step_list_data = duped_step_name_data;
162163
163 js.hello(step_list.len, hdr.status, hdr.flags.time_report);
164 js.hello(step_list.len, hdr.status, hdr.flags.time_report, hdr.flags.supports_recv);
164165}
165166fn statusUpdateMessage(msg_bytes: []u8) Allocator.Error!void {
166167 if (msg_bytes.len < @sizeOf(abi.StatusUpdate)) @panic("malformed StatusUpdate message");
lib/build-web/time_report.zig+4
......@@ -175,6 +175,7 @@ pub fn compileResultMessage(msg_bytes: []u8) error{OutOfMemory}!void {
175175 \\ <td>{D}</td>
176176 \\ <td>{D}</td>
177177 \\ <td>{D}</td>
178 \\ <td>{D}</td>
178179 \\</tr>
179180 \\
180181 , .{
......@@ -182,6 +183,7 @@ pub fn compileResultMessage(msg_bytes: []u8) error{OutOfMemory}!void {
182183 file.ns_sema,
183184 file.ns_codegen,
184185 file.ns_link,
186 file.ns_sema + file.ns_codegen + file.ns_link,
185187 });
186188 }
187189 if (slowest_files.len > max_table_rows) {
......@@ -203,6 +205,7 @@ pub fn compileResultMessage(msg_bytes: []u8) error{OutOfMemory}!void {
203205 \\ <td>{D}</td>
204206 \\ <td>{D}</td>
205207 \\ <td>{D}</td>
208 \\ <td>{D}</td>
206209 \\</tr>
207210 \\
208211 , .{
......@@ -212,6 +215,7 @@ pub fn compileResultMessage(msg_bytes: []u8) error{OutOfMemory}!void {
212215 decl.ns_sema,
213216 decl.ns_codegen,
214217 decl.ns_link,
218 decl.ns_sema + decl.ns_codegen + decl.ns_link,
215219 });
216220 }
217221 if (slowest_decls.len > max_table_rows) {
lib/std/Build/WebServer.zig+8-11
......@@ -65,16 +65,6 @@ pub fn init(opts: Options) WebServer {
6565 std.process.fatal("--webui not yet implemented for single-threaded builds", .{});
6666 }
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
7868 const all_steps = opts.all_steps;
7969
8070 const step_names_trailing = opts.gpa.alloc(u8, len: {
......@@ -297,13 +287,20 @@ fn serveWebSocket(ws: *WebServer, sock: *http.Server.WebSocket) !noreturn {
297287 copy.* = @atomicLoad(u8, shared, .monotonic);
298288 }
299289
300 _ = try std.Thread.spawn(.{}, recvWebSocketMessages, .{ ws, sock });
290 // Calling WSARecvFrom on one thread while another calls WSASend deadlocks.
291 // This functionality is disabled until std.net uses overlapped sockets on Windows.
292 const supports_recv = builtin.os.tag != .windows;
293 const recv_thread = if (supports_recv)
294 try std.Thread.spawn(.{}, recvWebSocketMessages, .{ ws, sock })
295 else {};
296 defer if (supports_recv) recv_thread.join();
301297
302298 {
303299 const hello_header: abi.Hello = .{
304300 .status = prev_build_status,
305301 .flags = .{
306302 .time_report = ws.graph.time_report,
303 .supports_recv = supports_recv,
307304 },
308305 .timestamp = ws.now(),
309306 .steps_len = @intCast(ws.all_steps.len),
lib/std/Build/abi.zig+3-1
......@@ -103,7 +103,9 @@ pub const Hello = extern struct {
103103 pub const Flags = packed struct(u16) {
104104 /// Whether time reporting is enabled.
105105 time_report: bool,
106 _: u15 = 0,
106 /// If this platform supports receiving messages from the client
107 supports_recv: bool,
108 _: u14 = 0,
107109 };
108110};
109111/// WebSocket server->client.
lib/std/http/Server.zig-1
......@@ -546,7 +546,6 @@ pub const Request = struct {
546546 try out.writeAll("connection: upgrade\r\nupgrade: websocket\r\nsec-websocket-accept: ");
547547 const base64_digest = try out.writableArray(28);
548548 assert(std.base64.standard.Encoder.encode(base64_digest, &digest).len == base64_digest.len);
549 out.advance(base64_digest.len);
550549 try out.writeAll("\r\n");
551550
552551 for (options.extra_headers) |header| {