authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-03-31 07:50:19-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-04-01 02:54:25-07:00
log617979ef26bf11b0dc18ecfc2eddb4c6e2c3ed18
tree4f8b3256c4c8d0cab491c91163f6ceabc012a9cb
parentbd05d13d5f36eacbda00f01a17bbdbfaba3bf164

std.Build.WebServer: use Io.concurrent rather than threads

allows the code to also work in evented mode

1 files changed, 21 insertions(+), 16 deletions(-)

lib/std/Build/WebServer.zig+21-16
......@@ -6,7 +6,7 @@ root_prog_node: std.Progress.Node,
66watch: bool,
77
88tcp_server: ?net.Server,
9serve_thread: ?std.Thread,
9serve_task: ?Io.Future(Io.Cancelable!void),
1010
1111/// Uses `Io.Clock.awake`.
1212base_timestamp: Io.Timestamp,
......@@ -103,7 +103,7 @@ pub fn init(opts: Options) WebServer {
103103 .watch = opts.watch,
104104
105105 .tcp_server = null,
106 .serve_thread = null,
106 .serve_task = null,
107107
108108 .base_timestamp = opts.base_timestamp.raw,
109109 .step_names_trailing = step_names_trailing,
......@@ -136,9 +136,9 @@ pub fn deinit(ws: *WebServer) void {
136136 gpa.free(ws.time_report_msgs);
137137 gpa.free(ws.time_report_update_times);
138138
139 if (ws.serve_thread) |t| {
139 if (ws.serve_task) |t| {
140140 if (ws.tcp_server) |*s| s.stream.close(io);
141 t.join();
141 t.await();
142142 }
143143 if (ws.tcp_server) |*s| s.deinit();
144144
......@@ -146,15 +146,15 @@ pub fn deinit(ws: *WebServer) void {
146146}
147147pub fn start(ws: *WebServer) error{AlreadyReported}!void {
148148 assert(ws.tcp_server == null);
149 assert(ws.serve_thread == null);
149 assert(ws.serve_task == null);
150150 const io = ws.graph.io;
151151
152152 ws.tcp_server = ws.listen_address.listen(io, .{ .reuse_address = true }) catch |err| {
153 log.err("failed to listen to port {d}: {s}", .{ ws.listen_address.getPort(), @errorName(err) });
153 log.err("failed to listen to port {d}: {t}", .{ ws.listen_address.getPort(), err });
154154 return error.AlreadyReported;
155155 };
156 ws.serve_thread = std.Thread.spawn(.{}, serve, .{ws}) catch |err| {
157 log.err("unable to spawn web server thread: {s}", .{@errorName(err)});
156 ws.serve_task = io.concurrent(serve, .{ws}) catch |err| {
157 log.err("unable to spawn web server thread: {t}", .{err});
158158 ws.tcp_server.?.deinit(io);
159159 ws.tcp_server = null;
160160 return error.AlreadyReported;
......@@ -165,15 +165,20 @@ pub fn start(ws: *WebServer) error{AlreadyReported}!void {
165165 log.info("hint: pass '--webui={f}' to use the same port next time", .{ws.tcp_server.?.socket.address});
166166 }
167167}
168fn serve(ws: *WebServer) void {
168fn serve(ws: *WebServer) Io.Cancelable!void {
169169 const io = ws.graph.io;
170 var group: Io.Group = .init;
171 defer group.cancel(io);
170172 while (true) {
171 var stream = ws.tcp_server.?.accept(io) catch |err| {
172 log.err("failed to accept connection: {s}", .{@errorName(err)});
173 return;
173 var stream = ws.tcp_server.?.accept(io) catch |err| switch (err) {
174 error.Canceled => |e| return e,
175 else => |e| {
176 log.err("failed to accept connection: {t}", .{e});
177 return;
178 },
174179 };
175 _ = std.Thread.spawn(.{}, accept, .{ ws, stream }) catch |err| {
176 log.err("unable to spawn connection thread: {s}", .{@errorName(err)});
180 group.concurrent(io, accept, .{ ws, stream }) catch |err| {
181 log.err("unable to spawn connection thread: {t}", .{err});
177182 stream.close(io);
178183 continue;
179184 };
......@@ -303,8 +308,8 @@ fn serveWebSocket(ws: *WebServer, sock: *http.Server.WebSocket) !noreturn {
303308 copy.* = @atomicLoad(u8, shared, .monotonic);
304309 }
305310
306 const recv_thread = try std.Thread.spawn(.{}, recvWebSocketMessages, .{ ws, sock });
307 defer recv_thread.join();
311 var recv_thread = try io.concurrent(recvWebSocketMessages, .{ ws, sock });
312 defer recv_thread.cancel(io);
308313
309314 {
310315 const hello_header: abi.Hello = .{