From bd05d13d5f36eacbda00f01a17bbdbfaba3bf164 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 31 Mar 2026 07:49:19 -0700 Subject: [PATCH 1/2] std: make file locking tests use io not threads This will allow the tests to also run in single-threaded evented mode. --- lib/std/fs/test.zig | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/lib/std/fs/test.zig b/lib/std/fs/test.zig index dabe9662ffdd0417827ca3efce79a617408f8ba8..8957b67b78afca11341a444ba876e3949ee87969 100644 --- a/lib/std/fs/test.zig +++ b/lib/std/fs/test.zig @@ -1758,9 +1758,7 @@ test "open file with exclusive and shared nonblocking lock" { } test "open file with exclusive lock twice, make sure second lock waits" { - if (builtin.single_threaded) return error.SkipZigTest; - - try testWithAllSupportedPathTypes(struct { + testWithAllSupportedPathTypes(struct { fn impl(ctx: *TestContext) !void { const io = ctx.io; const filename = try ctx.transformPath("file_lock_test.txt"); @@ -1781,8 +1779,8 @@ test "open file with exclusive lock twice, make sure second lock waits" { var started: Io.Event = .unset; var locked: Io.Event = .unset; - const t = try std.Thread.spawn(.{}, S.checkFn, .{ ctx, filename, &started, &locked }); - defer t.join(); + var t = try io.concurrent(S.checkFn, .{ ctx, filename, &started, &locked }); + defer t.cancel(io) catch {}; // Wait for the spawned thread to start trying to acquire the exclusive file lock. // Then wait a bit to make sure that can't acquire it since we currently hold the file lock. @@ -1795,8 +1793,12 @@ test "open file with exclusive lock twice, make sure second lock waits" { // Release the file lock which should unlock the thread to lock it and set the locked event. file.close(io); try locked.wait(io); + try t.await(io); } - }.impl); + }.impl) catch |err| switch (err) { + error.ConcurrencyUnavailable => return error.SkipZigTest, + else => |e| return e, + }; } test "open file with exclusive nonblocking lock twice (absolute paths)" { -- 2.54.0 From 617979ef26bf11b0dc18ecfc2eddb4c6e2c3ed18 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 31 Mar 2026 07:50:19 -0700 Subject: [PATCH 2/2] std.Build.WebServer: use Io.concurrent rather than threads allows the code to also work in evented mode --- lib/std/Build/WebServer.zig | 37 +++++++++++++++++++++---------------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/lib/std/Build/WebServer.zig b/lib/std/Build/WebServer.zig index 928427ad703a74e5fa547f3701d228dd6a380eae..02778dec7a685dde4f39eae3f74b184cdcd00606 100644 --- a/lib/std/Build/WebServer.zig +++ b/lib/std/Build/WebServer.zig @@ -6,7 +6,7 @@ root_prog_node: std.Progress.Node, watch: bool, tcp_server: ?net.Server, -serve_thread: ?std.Thread, +serve_task: ?Io.Future(Io.Cancelable!void), /// Uses `Io.Clock.awake`. base_timestamp: Io.Timestamp, @@ -103,7 +103,7 @@ pub fn init(opts: Options) WebServer { .watch = opts.watch, .tcp_server = null, - .serve_thread = null, + .serve_task = null, .base_timestamp = opts.base_timestamp.raw, .step_names_trailing = step_names_trailing, @@ -136,9 +136,9 @@ pub fn deinit(ws: *WebServer) void { gpa.free(ws.time_report_msgs); gpa.free(ws.time_report_update_times); - if (ws.serve_thread) |t| { + if (ws.serve_task) |t| { if (ws.tcp_server) |*s| s.stream.close(io); - t.join(); + t.await(); } if (ws.tcp_server) |*s| s.deinit(); @@ -146,15 +146,15 @@ pub fn deinit(ws: *WebServer) void { } pub fn start(ws: *WebServer) error{AlreadyReported}!void { assert(ws.tcp_server == null); - assert(ws.serve_thread == null); + assert(ws.serve_task == null); const io = ws.graph.io; ws.tcp_server = ws.listen_address.listen(io, .{ .reuse_address = true }) catch |err| { - log.err("failed to listen to port {d}: {s}", .{ ws.listen_address.getPort(), @errorName(err) }); + log.err("failed to listen to port {d}: {t}", .{ ws.listen_address.getPort(), err }); return error.AlreadyReported; }; - ws.serve_thread = std.Thread.spawn(.{}, serve, .{ws}) catch |err| { - log.err("unable to spawn web server thread: {s}", .{@errorName(err)}); + ws.serve_task = io.concurrent(serve, .{ws}) catch |err| { + log.err("unable to spawn web server thread: {t}", .{err}); ws.tcp_server.?.deinit(io); ws.tcp_server = null; return error.AlreadyReported; @@ -165,15 +165,20 @@ pub fn start(ws: *WebServer) error{AlreadyReported}!void { log.info("hint: pass '--webui={f}' to use the same port next time", .{ws.tcp_server.?.socket.address}); } } -fn serve(ws: *WebServer) void { +fn serve(ws: *WebServer) Io.Cancelable!void { const io = ws.graph.io; + var group: Io.Group = .init; + defer group.cancel(io); while (true) { - var stream = ws.tcp_server.?.accept(io) catch |err| { - log.err("failed to accept connection: {s}", .{@errorName(err)}); - return; + var stream = ws.tcp_server.?.accept(io) catch |err| switch (err) { + error.Canceled => |e| return e, + else => |e| { + log.err("failed to accept connection: {t}", .{e}); + return; + }, }; - _ = std.Thread.spawn(.{}, accept, .{ ws, stream }) catch |err| { - log.err("unable to spawn connection thread: {s}", .{@errorName(err)}); + group.concurrent(io, accept, .{ ws, stream }) catch |err| { + log.err("unable to spawn connection thread: {t}", .{err}); stream.close(io); continue; }; @@ -303,8 +308,8 @@ fn serveWebSocket(ws: *WebServer, sock: *http.Server.WebSocket) !noreturn { copy.* = @atomicLoad(u8, shared, .monotonic); } - const recv_thread = try std.Thread.spawn(.{}, recvWebSocketMessages, .{ ws, sock }); - defer recv_thread.join(); + var recv_thread = try io.concurrent(recvWebSocketMessages, .{ ws, sock }); + defer recv_thread.cancel(io); { const hello_header: abi.Hello = .{ -- 2.54.0