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 @@...@@ -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/main.js+13-3
...@@ -6,7 +6,7 @@ const domSummary = {...@@ -6,7 +6,7 @@ const domSummary = {
6 stepCount: document.getElementById("summaryStepCount"),6 stepCount: document.getElementById("summaryStepCount"),
7 status: document.getElementById("summaryStatus"),7 status: document.getElementById("summaryStatus"),
8};8};
9const domButtonRebuild = document.getElementById("buttonRebuild");9let domButtonRebuild = document.getElementById("buttonRebuild");
10const domStepList = document.getElementById("stepList");10const domStepList = document.getElementById("stepList");
11let domSteps = [];11let domSteps = [];
1212
...@@ -114,7 +114,13 @@ function hello(...@@ -114,7 +114,13 @@ function hello(
114 steps_len,114 steps_len,
115 build_status,115 build_status,
116 time_report,116 time_report,
117 supports_recv,
117) {118) {
119 if (!supports_recv && domButtonRebuild) {
120 domButtonRebuild.remove();
121 domButtonRebuild = null;
122 }
123
118 domSummary.stepCount.textContent = steps_len;124 domSummary.stepCount.textContent = steps_len;
119 updateBuildStatus(build_status);125 updateBuildStatus(build_status);
120 setConnectionStatus("", false);126 setConnectionStatus("", false);
...@@ -161,11 +167,15 @@ function updateBuildStatus(s) {...@@ -161,11 +167,15 @@ function updateBuildStatus(s) {
161 if (active) {167 if (active) {
162 domSummary.status.classList.add("status-running");168 domSummary.status.classList.add("status-running");
163 domSummary.status.classList.remove("status-idle");169 domSummary.status.classList.remove("status-idle");
164 domButtonRebuild.disabled = true;170 if (domButtonRebuild) {
171 domButtonRebuild.disabled = true;
172 }
165 } else {173 } else {
166 domSummary.status.classList.remove("status-running");174 domSummary.status.classList.remove("status-running");
167 domSummary.status.classList.add("status-idle");175 domSummary.status.classList.add("status-idle");
168 domButtonRebuild.disabled = false;176 if (domButtonRebuild) {
177 domButtonRebuild.disabled = false;
178 }
169 }179 }
170 if (reset_time_reports) {180 if (reset_time_reports) {
171 // Grey out and collapse all the time reports181 // Grey out and collapse all the time reports
lib/build-web/main.zig+2-1
...@@ -30,6 +30,7 @@ const js = struct {...@@ -30,6 +30,7 @@ const js = struct {
30 steps_len: u32,30 steps_len: u32,
31 status: abi.BuildStatus,31 status: abi.BuildStatus,
32 time_report: bool,32 time_report: bool,
33 supports_recv: bool,
33 ) void;34 ) void;
34 extern "core" fn updateBuildStatus(status: abi.BuildStatus) void;35 extern "core" fn updateBuildStatus(status: abi.BuildStatus) void;
35 extern "core" fn updateStepStatus(step_idx: u32) void;36 extern "core" fn updateStepStatus(step_idx: u32) void;
...@@ -160,7 +161,7 @@ fn helloMessage(msg_bytes: []align(4) u8) Allocator.Error!void {...@@ -160,7 +161,7 @@ fn helloMessage(msg_bytes: []align(4) u8) Allocator.Error!void {
160 step_list = steps;161 step_list = steps;
161 step_list_data = duped_step_name_data;162 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);
164}165}
165fn statusUpdateMessage(msg_bytes: []u8) Allocator.Error!void {166fn statusUpdateMessage(msg_bytes: []u8) Allocator.Error!void {
166 if (msg_bytes.len < @sizeOf(abi.StatusUpdate)) @panic("malformed StatusUpdate message");167 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 {...@@ -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+8-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,13 +287,20 @@ fn serveWebSocket(ws: *WebServer, sock: *http.Server.WebSocket) !noreturn {...@@ -297,13 +287,20 @@ 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 // 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
302 {298 {
303 const hello_header: abi.Hello = .{299 const hello_header: abi.Hello = .{
304 .status = prev_build_status,300 .status = prev_build_status,
305 .flags = .{301 .flags = .{
306 .time_report = ws.graph.time_report,302 .time_report = ws.graph.time_report,
303 .supports_recv = supports_recv,
307 },304 },
308 .timestamp = ws.now(),305 .timestamp = ws.now(),
309 .steps_len = @intCast(ws.all_steps.len),306 .steps_len = @intCast(ws.all_steps.len),
lib/std/Build/abi.zig+3-1
...@@ -103,7 +103,9 @@ pub const Hello = extern struct {...@@ -103,7 +103,9 @@ pub const Hello = extern struct {
103 pub const Flags = packed struct(u16) {103 pub const Flags = packed struct(u16) {
104 /// Whether time reporting is enabled.104 /// Whether time reporting is enabled.
105 time_report: bool,105 time_report: bool,
106 _: u15 = 0,106 /// If this platform supports receiving messages from the client
107 supports_recv: bool,
108 _: u14 = 0,
107 };109 };
108};110};
109/// WebSocket server->client.111/// WebSocket server->client.
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| {