From 29e418cbfb3d5e5800ac2ad267b21cf2b8942c2c Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 27 Nov 2025 08:42:06 -0800 Subject: [PATCH 01/14] std.Io.Threaded: fix the cancellation race Now, before a syscall is entered, beginSyscall is called, which may return error.Canceled. After syscall returns, whether error or success, endSyscall is called. If the syscall returns EINTR then checkCancel is called. `cancelRequested` is removed from the std.Io VTable for now, with plans to replace it with a more powerful API that allows protection against cancellation requests. closes #25751 --- lib/std/Io.zig | 5 - lib/std/Io/Threaded.zig | 3111 ++++++++++++++++++++++++--------------- 2 files changed, 1956 insertions(+), 1160 deletions(-) diff --git a/lib/std/Io.zig b/lib/std/Io.zig index aa860abb36..eb854e3eca 100644 --- a/lib/std/Io.zig +++ b/lib/std/Io.zig @@ -620,11 +620,6 @@ pub const VTable = struct { result: []u8, result_alignment: std.mem.Alignment, ) void, - /// Returns whether the current thread of execution is known to have - /// been requested to cancel. - /// - /// Thread-safe. - cancelRequested: *const fn (?*anyopaque) bool, /// When this function returns, implementation guarantees that `start` has /// either already been called, or a unit of concurrency has been assigned diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index b5d40b5b01..ee26c6153d 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -50,6 +50,8 @@ cpu_count_error: ?std.Thread.CpuCountError, /// available count, subtract this from either `async_limit` or /// `concurrent_limit`. busy_count: usize = 0, +main_thread: Thread, +pid: Pid = .unknown, wsa: if (is_windows) Wsa else struct {} = .{}, @@ -57,7 +59,79 @@ have_signal_handler: bool, old_sig_io: if (have_sig_io) posix.Sigaction else void, old_sig_pipe: if (have_sig_pipe) posix.Sigaction else void, -threadlocal var current_closure: ?*Closure = null; +pub const Pid = if (native_os == .linux) enum(posix.pid_t) { + unknown = 0, + _, +} else enum(u0) { unknown = 0 }; + +const Thread = struct { + /// The value that needs to be passed to pthread_kill or tgkill in order to + /// send a signal. + signal_id: SignalId, + current_closure: ?*Closure = null, + + const SignalId = if (std.Thread.use_pthreads) std.c.pthread_t else std.Thread.Id; + + threadlocal var current: ?*Thread = null; + + fn getCurrent(t: *Threaded) *Thread { + return current orelse return &t.main_thread; + } + + fn checkCancel(thread: *Thread) error{Canceled}!void { + const closure = thread.current_closure orelse return; + switch (@cmpxchgStrong( + CancelStatus, + &closure.cancel_status, + .requested, + .acknowledged, + .acq_rel, + .acquire, + ) orelse return error.Canceled) { + .none => return, + .requested => unreachable, + .acknowledged => unreachable, + _ => return, + } + } + + fn beginSyscall(thread: *Thread) error{Canceled}!void { + const closure = thread.current_closure orelse return; + + switch (@cmpxchgStrong( + CancelStatus, + &closure.cancel_status, + .none, + .fromSignalId(thread.signal_id), + .acq_rel, + .acquire, + ) orelse return) { + .none => unreachable, + .requested => { + @atomicStore(CancelStatus, &closure.cancel_status, .acknowledged, .release); + return error.Canceled; + }, + .acknowledged => unreachable, + _ => unreachable, + } + } + + fn endSyscall(thread: *Thread) void { + const closure = thread.current_closure orelse return; + _ = @cmpxchgStrong( + CancelStatus, + &closure.cancel_status, + .fromSignalId(thread.signal_id), + .none, + .acq_rel, + .acquire, + ); + } + + fn currentSignalId() SignalId { + return if (std.Thread.use_pthreads) std.c.pthread_self() else std.Thread.getCurrentId(); + } +}; const max_iovecs_len = 8; const splat_buffer_size = 64; @@ -66,48 +140,93 @@ comptime { if (@TypeOf(posix.IOV_MAX) != void) assert(max_iovecs_len <= posix.IOV_MAX); } -const CancelId = enum(usize) { +const CancelStatus = enum(usize) { + /// Cancellation has neither been requested, nor checked. The async + /// operation will check status before entering a blocking syscall. + /// This is also the status used for uninteruptible tasks. none = 0, - canceling = std.math.maxInt(usize), + /// Cancellation has been requested and the status will be checked before + /// entering a blocking syscall. + requested = std.math.maxInt(usize) - 1, + /// Cancellation has been acknowledged and is in progress. Signals should + /// not be sent. + acknowledged = std.math.maxInt(usize), + /// Stores a `Thread.SignalId` and indicates that sending a signal to this thread + /// is needed in order to cancel. This state is set before going into + /// a blocking operation that needs to get unblocked via signal. _, - const ThreadId = if (std.Thread.use_pthreads) std.c.pthread_t else std.Thread.Id; + const Unpacked = union(enum) { + none, + requested, + acknowledged, + signal_id: Thread.SignalId, + }; - fn currentThread() CancelId { - if (std.Thread.use_pthreads) { - return @enumFromInt(@intFromPtr(std.c.pthread_self())); - } else { - return @enumFromInt(std.Thread.getCurrentId()); - } + fn unpack(cs: CancelStatus) Unpacked { + return switch (cs) { + .none => .none, + .requested => .requested, + .acknowledged => .acknowledged, + _ => |signal_id| .{ + .signal_id = if (std.Thread.use_pthreads) + @ptrFromInt(@intFromEnum(signal_id)) + else + @truncate(@intFromEnum(signal_id)), + }, + }; } - fn toThreadId(cancel_id: CancelId) ThreadId { - if (std.Thread.use_pthreads) { - return @ptrFromInt(@intFromEnum(cancel_id)); - } else { - return @intCast(@intFromEnum(cancel_id)); - } + fn fromSignalId(signal_id: Thread.SignalId) CancelStatus { + return if (std.Thread.use_pthreads) + @enumFromInt(@intFromPtr(signal_id)) + else + @enumFromInt(signal_id); } }; const Closure = struct { start: Start, node: std.SinglyLinkedList.Node = .{}, - cancel_tid: CancelId, + cancel_status: CancelStatus, - const Start = *const fn (*Closure) void; + const Start = *const fn (*Closure, *Threaded) void; - fn requestCancel(closure: *Closure) void { - switch (@atomicRmw(CancelId, &closure.cancel_tid, .Xchg, .canceling, .acq_rel)) { - .none, .canceling => {}, - else => |tid| { - if (std.Thread.use_pthreads) { - const rc = std.c.pthread_kill(tid.toThreadId(), .IO); - if (is_debug) assert(rc == 0); - } else if (native_os == .linux) { - _ = std.os.linux.tgkill(std.os.linux.getpid(), @bitCast(tid.toThreadId()), .IO); - } - }, + fn requestCancel(closure: *Closure, t: *Threaded) void { + var signal_id = switch (@atomicRmw(CancelStatus, &closure.cancel_status, .Xchg, .requested, .monotonic).unpack()) { + .none, .acknowledged, .requested => return, + .signal_id => |signal_id| signal_id, + }; + // The task will enter a blocking syscall before checking for cancellation again. + // We can send a signal to interrupt the syscall, but if it arrives before + // the syscall instruction, it will be missed. Therefore, this code tries + // again until the cancellation request is acknowledged. + const max_attempts = 3; + for (0..max_attempts) |_| { + if (std.Thread.use_pthreads) { + const rc = std.c.pthread_kill(signal_id, .IO); + if (is_debug) assert(rc == 0); + } else if (native_os == .linux) { + const pid: posix.pid_t = p: { + const cached_pid = @atomicLoad(Pid, &t.pid, .monotonic); + if (cached_pid != .unknown) break :p @intFromEnum(cached_pid); + const pid = std.os.linux.getpid(); + @atomicStore(Pid, &t.pid, @enumFromInt(pid), .monotonic); + break :p pid; + }; + _ = std.os.linux.tgkill(pid, @bitCast(signal_id), .IO); + } else { + return; + } + + // TODO make this a nanosleep with 1 << attempt duration + std.Thread.yield() catch {}; + + switch (@atomicRmw(CancelStatus, &closure.cancel_status, .Xchg, .requested, .monotonic).unpack()) { + .requested => continue, + .none, .acknowledged => return, + .signal_id => |new_signal_id| signal_id = new_signal_id, + } } } }; @@ -136,6 +255,9 @@ pub fn init( .old_sig_io = undefined, .old_sig_pipe = undefined, .have_signal_handler = false, + .main_thread = .{ + .signal_id = Thread.currentSignalId(), + }, }; if (posix.Sigaction != void) { @@ -169,6 +291,7 @@ pub const init_single_threaded: Threaded = .{ .old_sig_io = undefined, .old_sig_pipe = undefined, .have_signal_handler = false, + .main_thread = .{ .signal_id = undefined }, }; pub fn setAsyncLimit(t: *Threaded, new_limit: Io.Limit) void { @@ -201,6 +324,11 @@ fn join(t: *Threaded) void { } fn worker(t: *Threaded) void { + var thread: Thread = .{ + .signal_id = Thread.currentSignalId(), + }; + Thread.current = &thread; + defer t.wait_group.finish(); t.mutex.lock(); @@ -210,7 +338,7 @@ fn worker(t: *Threaded) void { while (t.run_queue.popFirst()) |closure_node| { t.mutex.unlock(); const closure: *Closure = @fieldParentPtr("node", closure_node); - closure.start(closure); + closure.start(closure, t); t.mutex.lock(); t.busy_count -= 1; } @@ -227,7 +355,6 @@ pub fn io(t: *Threaded) Io { .concurrent = concurrent, .await = await, .cancel = cancel, - .cancelRequested = cancelRequested, .select = select, .groupAsync = groupAsync, @@ -324,7 +451,6 @@ pub fn ioBasic(t: *Threaded) Io { .concurrent = concurrent, .await = await, .cancel = cancel, - .cancelRequested = cancelRequested, .select = select, .groupAsync = groupAsync, @@ -418,24 +544,12 @@ const AsyncClosure = struct { const done_reset_event: *ResetEvent = @ptrFromInt(@alignOf(ResetEvent)); - fn start(closure: *Closure) void { + fn start(closure: *Closure, t: *Threaded) void { const ac: *AsyncClosure = @alignCast(@fieldParentPtr("closure", closure)); - const tid: CancelId = .currentThread(); - if (@cmpxchgStrong(CancelId, &closure.cancel_tid, .none, tid, .acq_rel, .acquire)) |cancel_tid| { - assert(cancel_tid == .canceling); - // Even though we already know the task is canceled, we must still - // run the closure in order to make the return value valid and in - // case there are side effects. - } - current_closure = closure; + const current_thread = Thread.getCurrent(t); + current_thread.current_closure = closure; ac.func(ac.contextPointer(), ac.resultPointer()); - current_closure = null; - - // In case a cancel happens after successful task completion, prevents - // signal from being delivered to the thread in `requestCancel`. - if (@cmpxchgStrong(CancelId, &closure.cancel_tid, tid, .none, .acq_rel, .acquire)) |cancel_tid| { - assert(cancel_tid == .canceling); - } + current_thread.current_closure = null; if (@atomicRmw(?*ResetEvent, &ac.select_condition, .Xchg, done_reset_event, .release)) |select_reset| { assert(select_reset != done_reset_event); @@ -476,7 +590,7 @@ const AsyncClosure = struct { const actual_result_offset = actual_result_addr - @intFromPtr(ac); ac.* = .{ .closure = .{ - .cancel_tid = .none, + .cancel_status = .none, .start = start, }, .func = func, @@ -493,7 +607,7 @@ const AsyncClosure = struct { fn waitAndDeinit(ac: *AsyncClosure, t: *Threaded, result: []u8) void { ac.reset_event.wait(t) catch |err| switch (err) { error.Canceled => { - ac.closure.requestCancel(); + ac.closure.requestCancel(t); ac.reset_event.waitUncancelable(); }, }; @@ -604,7 +718,6 @@ fn concurrent( const GroupClosure = struct { closure: Closure, - t: *Threaded, group: *Io.Group, /// Points to sibling `GroupClosure`. Used for walking the group to cancel all. node: std.SinglyLinkedList.Node, @@ -612,26 +725,15 @@ const GroupClosure = struct { context_alignment: Alignment, alloc_len: usize, - fn start(closure: *Closure) void { + fn start(closure: *Closure, t: *Threaded) void { const gc: *GroupClosure = @alignCast(@fieldParentPtr("closure", closure)); - const tid: CancelId = .currentThread(); + const current_thread = Thread.getCurrent(t); const group = gc.group; const group_state: *std.atomic.Value(usize) = @ptrCast(&group.state); const reset_event: *ResetEvent = @ptrCast(&group.context); - if (@cmpxchgStrong(CancelId, &closure.cancel_tid, .none, tid, .acq_rel, .acquire)) |cancel_tid| { - assert(cancel_tid == .canceling); - // Even though we already know the task is canceled, we must still - // run the closure in case there are side effects. - } - current_closure = closure; + current_thread.current_closure = closure; gc.func(group, gc.contextPointer()); - current_closure = null; - - // In case a cancel happens after successful task completion, prevents - // signal from being delivered to the thread in `requestCancel`. - if (@cmpxchgStrong(CancelId, &closure.cancel_tid, tid, .none, .acq_rel, .acquire)) |cancel_tid| { - assert(cancel_tid == .canceling); - } + current_thread.current_closure = null; const prev_state = group_state.fetchSub(sync_one_pending, .acq_rel); assert((prev_state / sync_one_pending) > 0); @@ -647,7 +749,6 @@ const GroupClosure = struct { /// Does not initialize the `node` field. fn init( gpa: Allocator, - t: *Threaded, group: *Io.Group, context: []const u8, context_alignment: Alignment, @@ -662,10 +763,9 @@ const GroupClosure = struct { gc.* = .{ .closure = .{ - .cancel_tid = .none, + .cancel_status = .none, .start = start, }, - .t = t, .group = group, .node = undefined, .func = func, @@ -696,7 +796,7 @@ fn groupAsync( if (builtin.single_threaded) return start(group, context.ptr); const gpa = t.allocator; - const gc = GroupClosure.init(gpa, t, group, context, context_alignment, start) catch + const gc = GroupClosure.init(gpa, group, context, context_alignment, start) catch return start(group, context.ptr); t.mutex.lock(); @@ -752,7 +852,7 @@ fn groupConcurrent( const t: *Threaded = @ptrCast(@alignCast(userdata)); const gpa = t.allocator; - const gc = GroupClosure.init(gpa, t, group, context, context_alignment, start) catch + const gc = GroupClosure.init(gpa, group, context, context_alignment, start) catch return error.ConcurrencyUnavailable; t.mutex.lock(); @@ -806,7 +906,7 @@ fn groupWait(userdata: ?*anyopaque, group: *Io.Group, token: *anyopaque) void { var node: *std.SinglyLinkedList.Node = @ptrCast(@alignCast(token)); while (true) { const gc: *GroupClosure = @fieldParentPtr("node", node); - gc.closure.requestCancel(); + gc.closure.requestCancel(t); node = node.next orelse break; } reset_event.waitUncancelable(); @@ -832,7 +932,7 @@ fn groupCancel(userdata: ?*anyopaque, group: *Io.Group, token: *anyopaque) void var node: *std.SinglyLinkedList.Node = @ptrCast(@alignCast(token)); while (true) { const gc: *GroupClosure = @fieldParentPtr("node", node); - gc.closure.requestCancel(); + gc.closure.requestCancel(t); node = node.next orelse break; } } @@ -875,30 +975,20 @@ fn cancel( _ = result_alignment; const t: *Threaded = @ptrCast(@alignCast(userdata)); const ac: *AsyncClosure = @ptrCast(@alignCast(any_future)); - ac.closure.requestCancel(); + ac.closure.requestCancel(t); ac.waitAndDeinit(t, result); } -fn cancelRequested(userdata: ?*anyopaque) bool { - const t: *Threaded = @ptrCast(@alignCast(userdata)); - _ = t; - const closure = current_closure orelse return false; - return @atomicLoad(CancelId, &closure.cancel_tid, .acquire) == .canceling; -} - -fn checkCancel(t: *Threaded) error{Canceled}!void { - if (cancelRequested(t)) return error.Canceled; -} - fn mutexLock(userdata: ?*anyopaque, prev_state: Io.Mutex.State, mutex: *Io.Mutex) Io.Cancelable!void { if (builtin.single_threaded) unreachable; // Interface should have prevented this. if (native_os == .netbsd) @panic("TODO"); const t: *Threaded = @ptrCast(@alignCast(userdata)); + const current_thread = Thread.getCurrent(t); if (prev_state == .contended) { - try futexWait(t, @ptrCast(&mutex.state), @intFromEnum(Io.Mutex.State.contended)); + try futexWait(current_thread, @ptrCast(&mutex.state), @intFromEnum(Io.Mutex.State.contended)); } while (@atomicRmw(Io.Mutex.State, &mutex.state, .Xchg, .contended, .acquire) != .unlocked) { - try futexWait(t, @ptrCast(&mutex.state), @intFromEnum(Io.Mutex.State.contended)); + try futexWait(current_thread, @ptrCast(&mutex.state), @intFromEnum(Io.Mutex.State.contended)); } } @@ -960,6 +1050,7 @@ fn conditionWait(userdata: ?*anyopaque, cond: *Io.Condition, mutex: *Io.Mutex) I if (builtin.single_threaded) unreachable; // Deadlock. if (native_os == .netbsd) @panic("TODO"); const t: *Threaded = @ptrCast(@alignCast(userdata)); + const current_thread = Thread.getCurrent(t); const t_io = ioBasic(t); comptime assert(@TypeOf(cond.state) == u64); const ints: *[2]std.atomic.Value(u32) = @ptrCast(&cond.state); @@ -988,7 +1079,7 @@ fn conditionWait(userdata: ?*anyopaque, cond: *Io.Condition, mutex: *Io.Mutex) I defer mutex.lockUncancelable(t_io); while (true) { - try futexWait(t, cond_epoch, epoch); + try futexWait(current_thread, cond_epoch, epoch); epoch = cond_epoch.load(.acquire); state = cond_state.load(.monotonic); @@ -1074,35 +1165,46 @@ const dirMake = switch (native_os) { fn dirMakePosix(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, mode: Io.Dir.Mode) Io.Dir.MakeError!void { const t: *Threaded = @ptrCast(@alignCast(userdata)); + const current_thread = Thread.getCurrent(t); var path_buffer: [posix.PATH_MAX]u8 = undefined; const sub_path_posix = try pathToPosix(sub_path, &path_buffer); + try current_thread.beginSyscall(); while (true) { - try t.checkCancel(); switch (posix.errno(posix.system.mkdirat(dir.handle, sub_path_posix, mode))) { - .SUCCESS => return, - .INTR => continue, - .CANCELED => return error.Canceled, - - .ACCES => return error.AccessDenied, - .BADF => |err| return errnoBug(err), // File descriptor used after closed. - .PERM => return error.PermissionDenied, - .DQUOT => return error.DiskQuota, - .EXIST => return error.PathAlreadyExists, - .FAULT => |err| return errnoBug(err), - .LOOP => return error.SymLinkLoop, - .MLINK => return error.LinkQuotaExceeded, - .NAMETOOLONG => return error.NameTooLong, - .NOENT => return error.FileNotFound, - .NOMEM => return error.SystemResources, - .NOSPC => return error.NoSpaceLeft, - .NOTDIR => return error.NotDir, - .ROFS => return error.ReadOnlyFileSystem, - // dragonfly: when dir_fd is unlinked from filesystem - .NOTCONN => return error.FileNotFound, - .ILSEQ => return error.BadPathName, - else => |err| return posix.unexpectedErrno(err), + .SUCCESS => { + current_thread.endSyscall(); + return; + }, + .INTR => { + try current_thread.checkCancel(); + continue; + }, + else => |e| { + current_thread.endSyscall(); + switch (e) { + .CANCELED => return error.Canceled, + .ACCES => return error.AccessDenied, + .BADF => |err| return errnoBug(err), // File descriptor used after closed. + .PERM => return error.PermissionDenied, + .DQUOT => return error.DiskQuota, + .EXIST => return error.PathAlreadyExists, + .FAULT => |err| return errnoBug(err), + .LOOP => return error.SymLinkLoop, + .MLINK => return error.LinkQuotaExceeded, + .NAMETOOLONG => return error.NameTooLong, + .NOENT => return error.FileNotFound, + .NOMEM => return error.SystemResources, + .NOSPC => return error.NoSpaceLeft, + .NOTDIR => return error.NotDir, + .ROFS => return error.ReadOnlyFileSystem, + // dragonfly: when dir_fd is unlinked from filesystem + .NOTCONN => return error.FileNotFound, + .ILSEQ => return error.BadPathName, + else => |err| return posix.unexpectedErrno(err), + } + }, } } } @@ -1110,11 +1212,18 @@ fn dirMakePosix(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, mode: fn dirMakeWasi(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, mode: Io.Dir.Mode) Io.Dir.MakeError!void { if (builtin.link_libc) return dirMakePosix(userdata, dir, sub_path, mode); const t: *Threaded = @ptrCast(@alignCast(userdata)); + const current_thread = Thread.getCurrent(t); + try current_thread.beginSyscall(); while (true) { - try t.checkCancel(); switch (std.os.wasi.path_create_directory(dir.handle, sub_path.ptr, sub_path.len)) { - .SUCCESS => return, - .INTR => continue, + .SUCCESS => { + current_thread.endSyscall(); + return; + }, + .INTR => { + try current_thread.checkCancel(); + continue; + }, .CANCELED => return error.Canceled, .ACCES => return error.AccessDenied, @@ -1140,7 +1249,8 @@ fn dirMakeWasi(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, mode: I fn dirMakeWindows(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, mode: Io.Dir.Mode) Io.Dir.MakeError!void { const t: *Threaded = @ptrCast(@alignCast(userdata)); - try t.checkCancel(); + const current_thread = Thread.getCurrent(t); + try current_thread.checkCancel(); const sub_path_w = try windows.sliceToPrefixedFileW(dir.handle, sub_path); _ = mode; @@ -1213,6 +1323,7 @@ fn dirMakeOpenPathWindows( options: Io.Dir.OpenOptions, ) Io.Dir.MakeOpenPathError!Io.Dir { const t: *Threaded = @ptrCast(@alignCast(userdata)); + const current_thread = Thread.getCurrent(t); const w = windows; const access_mask = w.STANDARD_RIGHTS_READ | w.FILE_READ_ATTRIBUTES | w.FILE_READ_EA | w.SYNCHRONIZE | w.FILE_TRAVERSE | @@ -1226,7 +1337,7 @@ fn dirMakeOpenPathWindows( }; while (true) { - try t.checkCancel(); + try current_thread.checkCancel(); const sub_path_w_array = try w.sliceToPrefixedFileW(dir.handle, component.path); const sub_path_w = sub_path_w_array.span(); @@ -1328,8 +1439,7 @@ fn dirMakeOpenPathWasi( fn dirStat(userdata: ?*anyopaque, dir: Io.Dir) Io.Dir.StatError!Io.Dir.Stat { const t: *Threaded = @ptrCast(@alignCast(userdata)); - try t.checkCancel(); - + _ = t; _ = dir; @panic("TODO implement dirStat"); } @@ -1348,6 +1458,7 @@ fn dirStatPathLinux( options: Io.Dir.StatPathOptions, ) Io.Dir.StatPathError!Io.File.Stat { const t: *Threaded = @ptrCast(@alignCast(userdata)); + const current_thread = Thread.getCurrent(t); const linux = std.os.linux; var path_buffer: [posix.PATH_MAX]u8 = undefined; @@ -1356,8 +1467,8 @@ fn dirStatPathLinux( const flags: u32 = linux.AT.NO_AUTOMOUNT | @as(u32, if (!options.follow_symlinks) linux.AT.SYMLINK_NOFOLLOW else 0); + try current_thread.beginSyscall(); while (true) { - try t.checkCancel(); var statx = std.mem.zeroes(linux.Statx); const rc = linux.statx( dir.handle, @@ -1367,20 +1478,30 @@ fn dirStatPathLinux( &statx, ); switch (linux.errno(rc)) { - .SUCCESS => return statFromLinux(&statx), - .INTR => continue, - .CANCELED => return error.Canceled, - - .ACCES => return error.AccessDenied, - .BADF => |err| return errnoBug(err), // File descriptor used after closed. - .FAULT => |err| return errnoBug(err), - .INVAL => |err| return errnoBug(err), - .LOOP => return error.SymLinkLoop, - .NAMETOOLONG => |err| return errnoBug(err), // Handled by pathToPosix() above. - .NOENT => return error.FileNotFound, - .NOTDIR => return error.NotDir, - .NOMEM => return error.SystemResources, - else => |err| return posix.unexpectedErrno(err), + .SUCCESS => { + current_thread.endSyscall(); + return statFromLinux(&statx); + }, + .INTR => { + try current_thread.checkCancel(); + continue; + }, + else => |e| { + current_thread.endSyscall(); + switch (e) { + .CANCELED => return error.Canceled, + .ACCES => return error.AccessDenied, + .BADF => |err| return errnoBug(err), // File descriptor used after closed. + .FAULT => |err| return errnoBug(err), + .INVAL => |err| return errnoBug(err), + .LOOP => return error.SymLinkLoop, + .NAMETOOLONG => |err| return errnoBug(err), // Handled by pathToPosix() above. + .NOENT => return error.FileNotFound, + .NOTDIR => return error.NotDir, + .NOMEM => return error.SystemResources, + else => |err| return posix.unexpectedErrno(err), + } + }, } } } @@ -1392,32 +1513,43 @@ fn dirStatPathPosix( options: Io.Dir.StatPathOptions, ) Io.Dir.StatPathError!Io.File.Stat { const t: *Threaded = @ptrCast(@alignCast(userdata)); + const current_thread = Thread.getCurrent(t); var path_buffer: [posix.PATH_MAX]u8 = undefined; const sub_path_posix = try pathToPosix(sub_path, &path_buffer); const flags: u32 = if (!options.follow_symlinks) posix.AT.SYMLINK_NOFOLLOW else 0; + try current_thread.beginSyscall(); while (true) { - try t.checkCancel(); var stat = std.mem.zeroes(posix.Stat); switch (posix.errno(fstatat_sym(dir.handle, sub_path_posix, &stat, flags))) { - .SUCCESS => return statFromPosix(&stat), - .INTR => continue, - .CANCELED => return error.Canceled, - - .INVAL => |err| return errnoBug(err), - .BADF => |err| return errnoBug(err), // File descriptor used after closed. - .NOMEM => return error.SystemResources, - .ACCES => return error.AccessDenied, - .PERM => return error.PermissionDenied, - .FAULT => |err| return errnoBug(err), - .NAMETOOLONG => return error.NameTooLong, - .LOOP => return error.SymLinkLoop, - .NOENT => return error.FileNotFound, - .NOTDIR => return error.FileNotFound, - .ILSEQ => return error.BadPathName, - else => |err| return posix.unexpectedErrno(err), + .SUCCESS => { + current_thread.endSyscall(); + return statFromPosix(&stat); + }, + .INTR => { + try current_thread.checkCancel(); + continue; + }, + else => |e| { + current_thread.endSyscall(); + switch (e) { + .CANCELED => return error.Canceled, + .INVAL => |err| return errnoBug(err), + .BADF => |err| return errnoBug(err), // File descriptor used after closed. + .NOMEM => return error.SystemResources, + .ACCES => return error.AccessDenied, + .PERM => return error.PermissionDenied, + .FAULT => |err| return errnoBug(err), + .NAMETOOLONG => return error.NameTooLong, + .LOOP => return error.SymLinkLoop, + .NOENT => return error.FileNotFound, + .NOTDIR => return error.FileNotFound, + .ILSEQ => return error.BadPathName, + else => |err| return posix.unexpectedErrno(err), + } + }, } } } @@ -1444,29 +1576,40 @@ fn dirStatPathWasi( ) Io.Dir.StatPathError!Io.File.Stat { if (builtin.link_libc) return dirStatPathPosix(userdata, dir, sub_path, options); const t: *Threaded = @ptrCast(@alignCast(userdata)); + const current_thread = Thread.getCurrent(t); const wasi = std.os.wasi; const flags: wasi.lookupflags_t = .{ .SYMLINK_FOLLOW = options.follow_symlinks, }; var stat: wasi.filestat_t = undefined; + try current_thread.beginSyscall(); while (true) { - try t.checkCancel(); switch (wasi.path_filestat_get(dir.handle, flags, sub_path.ptr, sub_path.len, &stat)) { - .SUCCESS => return statFromWasi(&stat), - .INTR => continue, - .CANCELED => return error.Canceled, - - .INVAL => |err| return errnoBug(err), - .BADF => |err| return errnoBug(err), // File descriptor used after closed. - .NOMEM => return error.SystemResources, - .ACCES => return error.AccessDenied, - .FAULT => |err| return errnoBug(err), - .NAMETOOLONG => return error.NameTooLong, - .NOENT => return error.FileNotFound, - .NOTDIR => return error.FileNotFound, - .NOTCAPABLE => return error.AccessDenied, - .ILSEQ => return error.BadPathName, - else => |err| return posix.unexpectedErrno(err), + .SUCCESS => { + current_thread.endSyscall(); + return statFromWasi(&stat); + }, + .INTR => { + try current_thread.checkCancel(); + continue; + }, + else => |e| { + current_thread.endSyscall(); + switch (e) { + .CANCELED => return error.Canceled, + .INVAL => |err| return errnoBug(err), + .BADF => |err| return errnoBug(err), // File descriptor used after closed. + .NOMEM => return error.SystemResources, + .ACCES => return error.AccessDenied, + .FAULT => |err| return errnoBug(err), + .NAMETOOLONG => return error.NameTooLong, + .NOENT => return error.FileNotFound, + .NOTDIR => return error.FileNotFound, + .NOTCAPABLE => return error.AccessDenied, + .ILSEQ => return error.BadPathName, + else => |err| return posix.unexpectedErrno(err), + } + }, } } } @@ -1480,31 +1623,44 @@ const fileStat = switch (native_os) { fn fileStatPosix(userdata: ?*anyopaque, file: Io.File) Io.File.StatError!Io.File.Stat { const t: *Threaded = @ptrCast(@alignCast(userdata)); + const current_thread = Thread.getCurrent(t); if (posix.Stat == void) return error.Streaming; + try current_thread.beginSyscall(); while (true) { - try t.checkCancel(); var stat = std.mem.zeroes(posix.Stat); switch (posix.errno(fstat_sym(file.handle, &stat))) { - .SUCCESS => return statFromPosix(&stat), - .INTR => continue, - .CANCELED => return error.Canceled, - - .INVAL => |err| return errnoBug(err), - .BADF => |err| return errnoBug(err), // File descriptor used after closed. - .NOMEM => return error.SystemResources, - .ACCES => return error.AccessDenied, - else => |err| return posix.unexpectedErrno(err), + .SUCCESS => { + current_thread.endSyscall(); + return statFromPosix(&stat); + }, + .INTR => { + try current_thread.checkCancel(); + continue; + }, + else => |e| { + current_thread.endSyscall(); + switch (e) { + .CANCELED => return error.Canceled, + .INVAL => |err| return errnoBug(err), + .BADF => |err| return errnoBug(err), // File descriptor used after closed. + .NOMEM => return error.SystemResources, + .ACCES => return error.AccessDenied, + else => |err| return posix.unexpectedErrno(err), + } + }, } } } fn fileStatLinux(userdata: ?*anyopaque, file: Io.File) Io.File.StatError!Io.File.Stat { const t: *Threaded = @ptrCast(@alignCast(userdata)); + const current_thread = Thread.getCurrent(t); const linux = std.os.linux; + + try current_thread.beginSyscall(); while (true) { - try t.checkCancel(); var statx = std.mem.zeroes(linux.Statx); const rc = linux.statx( file.handle, @@ -1514,27 +1670,38 @@ fn fileStatLinux(userdata: ?*anyopaque, file: Io.File) Io.File.StatError!Io.File &statx, ); switch (linux.errno(rc)) { - .SUCCESS => return statFromLinux(&statx), - .INTR => continue, - .CANCELED => return error.Canceled, - - .ACCES => |err| return errnoBug(err), - .BADF => |err| return errnoBug(err), // File descriptor used after closed. - .FAULT => |err| return errnoBug(err), - .INVAL => |err| return errnoBug(err), - .LOOP => |err| return errnoBug(err), - .NAMETOOLONG => |err| return errnoBug(err), - .NOENT => |err| return errnoBug(err), - .NOMEM => return error.SystemResources, - .NOTDIR => |err| return errnoBug(err), - else => |err| return posix.unexpectedErrno(err), + .SUCCESS => { + current_thread.endSyscall(); + return statFromLinux(&statx); + }, + .INTR => { + try current_thread.checkCancel(); + continue; + }, + else => |e| { + current_thread.endSyscall(); + switch (e) { + .CANCELED => return error.Canceled, + .ACCES => |err| return errnoBug(err), + .BADF => |err| return errnoBug(err), // File descriptor used after closed. + .FAULT => |err| return errnoBug(err), + .INVAL => |err| return errnoBug(err), + .LOOP => |err| return errnoBug(err), + .NAMETOOLONG => |err| return errnoBug(err), + .NOENT => |err| return errnoBug(err), + .NOMEM => return error.SystemResources, + .NOTDIR => |err| return errnoBug(err), + else => |err| return posix.unexpectedErrno(err), + } + }, } } } fn fileStatWindows(userdata: ?*anyopaque, file: Io.File) Io.File.StatError!Io.File.Stat { const t: *Threaded = @ptrCast(@alignCast(userdata)); - try t.checkCancel(); + const current_thread = Thread.getCurrent(t); + try current_thread.checkCancel(); var io_status_block: windows.IO_STATUS_BLOCK = undefined; var info: windows.FILE_ALL_INFORMATION = undefined; @@ -1581,21 +1748,34 @@ fn fileStatWindows(userdata: ?*anyopaque, file: Io.File) Io.File.StatError!Io.Fi fn fileStatWasi(userdata: ?*anyopaque, file: Io.File) Io.File.StatError!Io.File.Stat { if (builtin.link_libc) return fileStatPosix(userdata, file); + const t: *Threaded = @ptrCast(@alignCast(userdata)); + const current_thread = Thread.getCurrent(t); + + try current_thread.beginSyscall(); while (true) { - try t.checkCancel(); var stat: std.os.wasi.filestat_t = undefined; switch (std.os.wasi.fd_filestat_get(file.handle, &stat)) { - .SUCCESS => return statFromWasi(&stat), - .INTR => continue, - .CANCELED => return error.Canceled, - - .INVAL => |err| return errnoBug(err), - .BADF => |err| return errnoBug(err), // File descriptor used after closed. - .NOMEM => return error.SystemResources, - .ACCES => return error.AccessDenied, - .NOTCAPABLE => return error.AccessDenied, - else => |err| return posix.unexpectedErrno(err), + .SUCCESS => { + current_thread.endSyscall(); + return statFromWasi(&stat); + }, + .INTR => { + try current_thread.checkCancel(); + continue; + }, + else => |e| { + current_thread.endSyscall(); + switch (e) { + .CANCELED => return error.Canceled, + .INVAL => |err| return errnoBug(err), + .BADF => |err| return errnoBug(err), // File descriptor used after closed. + .NOMEM => return error.SystemResources, + .ACCES => return error.AccessDenied, + .NOTCAPABLE => return error.AccessDenied, + else => |err| return posix.unexpectedErrno(err), + } + }, } } } @@ -1613,6 +1793,7 @@ fn dirAccessPosix( options: Io.Dir.AccessOptions, ) Io.Dir.AccessError!void { const t: *Threaded = @ptrCast(@alignCast(userdata)); + const current_thread = Thread.getCurrent(t); var path_buffer: [posix.PATH_MAX]u8 = undefined; const sub_path_posix = try pathToPosix(sub_path, &path_buffer); @@ -1624,27 +1805,37 @@ fn dirAccessPosix( @as(u32, if (options.write) posix.W_OK else 0) | @as(u32, if (options.execute) posix.X_OK else 0); + try current_thread.beginSyscall(); while (true) { - try t.checkCancel(); switch (posix.errno(posix.system.faccessat(dir.handle, sub_path_posix, mode, flags))) { - .SUCCESS => return, - .INTR => continue, - .CANCELED => return error.Canceled, - - .ACCES => return error.AccessDenied, - .PERM => return error.PermissionDenied, - .ROFS => return error.ReadOnlyFileSystem, - .LOOP => return error.SymLinkLoop, - .TXTBSY => return error.FileBusy, - .NOTDIR => return error.FileNotFound, - .NOENT => return error.FileNotFound, - .NAMETOOLONG => return error.NameTooLong, - .INVAL => |err| return errnoBug(err), - .FAULT => |err| return errnoBug(err), - .IO => return error.InputOutput, - .NOMEM => return error.SystemResources, - .ILSEQ => return error.BadPathName, - else => |err| return posix.unexpectedErrno(err), + .SUCCESS => { + current_thread.endSyscall(); + return; + }, + .INTR => { + try current_thread.checkCancel(); + continue; + }, + else => |e| { + current_thread.endSyscall(); + switch (e) { + .CANCELED => return error.Canceled, + .ACCES => return error.AccessDenied, + .PERM => return error.PermissionDenied, + .ROFS => return error.ReadOnlyFileSystem, + .LOOP => return error.SymLinkLoop, + .TXTBSY => return error.FileBusy, + .NOTDIR => return error.FileNotFound, + .NOENT => return error.FileNotFound, + .NAMETOOLONG => return error.NameTooLong, + .INVAL => |err| return errnoBug(err), + .FAULT => |err| return errnoBug(err), + .IO => return error.InputOutput, + .NOMEM => return error.SystemResources, + .ILSEQ => return error.BadPathName, + else => |err| return posix.unexpectedErrno(err), + } + }, } } } @@ -1657,29 +1848,41 @@ fn dirAccessWasi( ) Io.Dir.AccessError!void { if (builtin.link_libc) return dirAccessPosix(userdata, dir, sub_path, options); const t: *Threaded = @ptrCast(@alignCast(userdata)); + const current_thread = Thread.getCurrent(t); const wasi = std.os.wasi; const flags: wasi.lookupflags_t = .{ .SYMLINK_FOLLOW = options.follow_symlinks, }; var stat: wasi.filestat_t = undefined; - while (true) { - try t.checkCancel(); - switch (wasi.path_filestat_get(dir.handle, flags, sub_path.ptr, sub_path.len, &stat)) { - .SUCCESS => break, - .INTR => continue, - .CANCELED => return error.Canceled, - .INVAL => |err| return errnoBug(err), - .BADF => |err| return errnoBug(err), // File descriptor used after closed. - .NOMEM => return error.SystemResources, - .ACCES => return error.AccessDenied, - .FAULT => |err| return errnoBug(err), - .NAMETOOLONG => return error.NameTooLong, - .NOENT => return error.FileNotFound, - .NOTDIR => return error.FileNotFound, - .NOTCAPABLE => return error.AccessDenied, - .ILSEQ => return error.BadPathName, - else => |err| return posix.unexpectedErrno(err), + try current_thread.beginSyscall(); + while (true) { + switch (wasi.path_filestat_get(dir.handle, flags, sub_path.ptr, sub_path.len, &stat)) { + .SUCCESS => { + current_thread.endSyscall(); + break; + }, + .INTR => { + try current_thread.checkCancel(); + continue; + }, + else => |e| { + current_thread.endSyscall(); + switch (e) { + .CANCELED => return error.Canceled, + .INVAL => |err| return errnoBug(err), + .BADF => |err| return errnoBug(err), // File descriptor used after closed. + .NOMEM => return error.SystemResources, + .ACCES => return error.AccessDenied, + .FAULT => |err| return errnoBug(err), + .NAMETOOLONG => return error.NameTooLong, + .NOENT => return error.FileNotFound, + .NOTDIR => return error.FileNotFound, + .NOTCAPABLE => return error.AccessDenied, + .ILSEQ => return error.BadPathName, + else => |err| return posix.unexpectedErrno(err), + } + }, } } @@ -1717,7 +1920,8 @@ fn dirAccessWindows( options: Io.Dir.AccessOptions, ) Io.Dir.AccessError!void { const t: *Threaded = @ptrCast(@alignCast(userdata)); - try t.checkCancel(); + const current_thread = Thread.getCurrent(t); + try current_thread.checkCancel(); _ = options; // TODO @@ -1768,6 +1972,7 @@ fn dirCreateFilePosix( flags: Io.File.CreateFlags, ) Io.File.OpenError!Io.File { const t: *Threaded = @ptrCast(@alignCast(userdata)); + const current_thread = Thread.getCurrent(t); var path_buffer: [posix.PATH_MAX]u8 = undefined; const sub_path_posix = try pathToPosix(sub_path, &path_buffer); @@ -1796,40 +2001,50 @@ fn dirCreateFilePosix( }, }; + try current_thread.beginSyscall(); const fd: posix.fd_t = while (true) { - try t.checkCancel(); const rc = openat_sym(dir.handle, sub_path_posix, os_flags, flags.mode); switch (posix.errno(rc)) { - .SUCCESS => break @intCast(rc), - .INTR => continue, - .CANCELED => return error.Canceled, - - .FAULT => |err| return errnoBug(err), - .INVAL => return error.BadPathName, - .BADF => |err| return errnoBug(err), // File descriptor used after closed. - .ACCES => return error.AccessDenied, - .FBIG => return error.FileTooBig, - .OVERFLOW => return error.FileTooBig, - .ISDIR => return error.IsDir, - .LOOP => return error.SymLinkLoop, - .MFILE => return error.ProcessFdQuotaExceeded, - .NAMETOOLONG => return error.NameTooLong, - .NFILE => return error.SystemFdQuotaExceeded, - .NODEV => return error.NoDevice, - .NOENT => return error.FileNotFound, - .SRCH => return error.ProcessNotFound, - .NOMEM => return error.SystemResources, - .NOSPC => return error.NoSpaceLeft, - .NOTDIR => return error.NotDir, - .PERM => return error.PermissionDenied, - .EXIST => return error.PathAlreadyExists, - .BUSY => return error.DeviceBusy, - .OPNOTSUPP => return error.FileLocksNotSupported, - .AGAIN => return error.WouldBlock, - .TXTBSY => return error.FileBusy, - .NXIO => return error.NoDevice, - .ILSEQ => return error.BadPathName, - else => |err| return posix.unexpectedErrno(err), + .SUCCESS => { + current_thread.endSyscall(); + break @intCast(rc); + }, + .INTR => { + try current_thread.checkCancel(); + continue; + }, + else => |e| { + current_thread.endSyscall(); + switch (e) { + .CANCELED => return error.Canceled, + .FAULT => |err| return errnoBug(err), + .INVAL => return error.BadPathName, + .BADF => |err| return errnoBug(err), // File descriptor used after closed. + .ACCES => return error.AccessDenied, + .FBIG => return error.FileTooBig, + .OVERFLOW => return error.FileTooBig, + .ISDIR => return error.IsDir, + .LOOP => return error.SymLinkLoop, + .MFILE => return error.ProcessFdQuotaExceeded, + .NAMETOOLONG => return error.NameTooLong, + .NFILE => return error.SystemFdQuotaExceeded, + .NODEV => return error.NoDevice, + .NOENT => return error.FileNotFound, + .SRCH => return error.ProcessNotFound, + .NOMEM => return error.SystemResources, + .NOSPC => return error.NoSpaceLeft, + .NOTDIR => return error.NotDir, + .PERM => return error.PermissionDenied, + .EXIST => return error.PathAlreadyExists, + .BUSY => return error.DeviceBusy, + .OPNOTSUPP => return error.FileLocksNotSupported, + .AGAIN => return error.WouldBlock, + .TXTBSY => return error.FileBusy, + .NXIO => return error.NoDevice, + .ILSEQ => return error.BadPathName, + else => |err| return posix.unexpectedErrno(err), + } + }, } }; errdefer posix.close(fd); @@ -1841,42 +2056,71 @@ fn dirCreateFilePosix( .shared => posix.LOCK.SH | lock_nonblocking, .exclusive => posix.LOCK.EX | lock_nonblocking, }; - while (true) { - try t.checkCancel(); - switch (posix.errno(posix.system.flock(fd, lock_flags))) { - .SUCCESS => break, - .INTR => continue, - .CANCELED => return error.Canceled, - .BADF => |err| return errnoBug(err), // File descriptor used after closed. - .INVAL => |err| return errnoBug(err), // invalid parameters - .NOLCK => return error.SystemResources, - .AGAIN => return error.WouldBlock, - .OPNOTSUPP => return error.FileLocksNotSupported, - else => |err| return posix.unexpectedErrno(err), + try current_thread.beginSyscall(); + while (true) { + switch (posix.errno(posix.system.flock(fd, lock_flags))) { + .SUCCESS => { + current_thread.endSyscall(); + break; + }, + .INTR => { + try current_thread.checkCancel(); + continue; + }, + else => |e| { + current_thread.endSyscall(); + switch (e) { + .CANCELED => return error.Canceled, + .BADF => |err| return errnoBug(err), // File descriptor used after closed. + .INVAL => |err| return errnoBug(err), // invalid parameters + .NOLCK => return error.SystemResources, + .AGAIN => return error.WouldBlock, + .OPNOTSUPP => return error.FileLocksNotSupported, + else => |err| return posix.unexpectedErrno(err), + } + }, } } } if (have_flock_open_flags and flags.lock_nonblocking) { + try current_thread.beginSyscall(); var fl_flags: usize = while (true) { - try t.checkCancel(); const rc = posix.system.fcntl(fd, posix.F.GETFL, @as(usize, 0)); switch (posix.errno(rc)) { - .SUCCESS => break @intCast(rc), - .INTR => continue, - .CANCELED => return error.Canceled, - else => |err| return posix.unexpectedErrno(err), + .SUCCESS => { + current_thread.endSyscall(); + break @intCast(rc); + }, + .INTR => { + try current_thread.checkCancel(); + continue; + }, + else => |err| { + current_thread.endSyscall(); + return posix.unexpectedErrno(err); + }, } }; + fl_flags |= @as(usize, 1 << @bitOffsetOf(posix.O, "NONBLOCK")); + + try current_thread.beginSyscall(); while (true) { - try t.checkCancel(); switch (posix.errno(posix.system.fcntl(fd, posix.F.SETFL, fl_flags))) { - .SUCCESS => break, - .INTR => continue, - .CANCELED => return error.Canceled, - else => |err| return posix.unexpectedErrno(err), + .SUCCESS => { + current_thread.endSyscall(); + break; + }, + .INTR => { + try current_thread.checkCancel(); + continue; + }, + else => |err| { + current_thread.endSyscall(); + return posix.unexpectedErrno(err); + }, } } } @@ -1892,7 +2136,8 @@ fn dirCreateFileWindows( ) Io.File.OpenError!Io.File { const w = windows; const t: *Threaded = @ptrCast(@alignCast(userdata)); - try t.checkCancel(); + const current_thread = Thread.getCurrent(t); + try current_thread.checkCancel(); const sub_path_w_array = try w.sliceToPrefixedFileW(dir.handle, sub_path); const sub_path_w = sub_path_w_array.span(); @@ -1939,6 +2184,7 @@ fn dirCreateFileWasi( flags: Io.File.CreateFlags, ) Io.File.OpenError!Io.File { const t: *Threaded = @ptrCast(@alignCast(userdata)); + const current_thread = Thread.getCurrent(t); const wasi = std.os.wasi; const lookup_flags: wasi.lookupflags_t = .{}; const oflags: wasi.oflags_t = .{ @@ -1966,35 +2212,45 @@ fn dirCreateFileWasi( }; const inheriting: wasi.rights_t = .{}; var fd: posix.fd_t = undefined; + try current_thread.beginSyscall(); while (true) { - try t.checkCancel(); switch (wasi.path_open(dir.handle, lookup_flags, sub_path.ptr, sub_path.len, oflags, base, inheriting, fdflags, &fd)) { - .SUCCESS => return .{ .handle = fd }, - .INTR => continue, - .CANCELED => return error.Canceled, - - .FAULT => |err| return errnoBug(err), - .INVAL => return error.BadPathName, - .BADF => |err| return errnoBug(err), // File descriptor used after closed. - .ACCES => return error.AccessDenied, - .FBIG => return error.FileTooBig, - .OVERFLOW => return error.FileTooBig, - .ISDIR => return error.IsDir, - .LOOP => return error.SymLinkLoop, - .MFILE => return error.ProcessFdQuotaExceeded, - .NAMETOOLONG => return error.NameTooLong, - .NFILE => return error.SystemFdQuotaExceeded, - .NODEV => return error.NoDevice, - .NOENT => return error.FileNotFound, - .NOMEM => return error.SystemResources, - .NOSPC => return error.NoSpaceLeft, - .NOTDIR => return error.NotDir, - .PERM => return error.PermissionDenied, - .EXIST => return error.PathAlreadyExists, - .BUSY => return error.DeviceBusy, - .NOTCAPABLE => return error.AccessDenied, - .ILSEQ => return error.BadPathName, - else => |err| return posix.unexpectedErrno(err), + .SUCCESS => { + current_thread.endSyscall(); + return .{ .handle = fd }; + }, + .INTR => { + try current_thread.checkCancel(); + continue; + }, + else => |e| { + current_thread.endSyscall(); + switch (e) { + .CANCELED => return error.Canceled, + .FAULT => |err| return errnoBug(err), + .INVAL => return error.BadPathName, + .BADF => |err| return errnoBug(err), // File descriptor used after closed. + .ACCES => return error.AccessDenied, + .FBIG => return error.FileTooBig, + .OVERFLOW => return error.FileTooBig, + .ISDIR => return error.IsDir, + .LOOP => return error.SymLinkLoop, + .MFILE => return error.ProcessFdQuotaExceeded, + .NAMETOOLONG => return error.NameTooLong, + .NFILE => return error.SystemFdQuotaExceeded, + .NODEV => return error.NoDevice, + .NOENT => return error.FileNotFound, + .NOMEM => return error.SystemResources, + .NOSPC => return error.NoSpaceLeft, + .NOTDIR => return error.NotDir, + .PERM => return error.PermissionDenied, + .EXIST => return error.PathAlreadyExists, + .BUSY => return error.DeviceBusy, + .NOTCAPABLE => return error.AccessDenied, + .ILSEQ => return error.BadPathName, + else => |err| return posix.unexpectedErrno(err), + } + }, } } } @@ -2012,6 +2268,7 @@ fn dirOpenFilePosix( flags: Io.File.OpenFlags, ) Io.File.OpenError!Io.File { const t: *Threaded = @ptrCast(@alignCast(userdata)); + const current_thread = Thread.getCurrent(t); var path_buffer: [posix.PATH_MAX]u8 = undefined; const sub_path_posix = try pathToPosix(sub_path, &path_buffer); @@ -2048,40 +2305,50 @@ fn dirOpenFilePosix( }, }; + try current_thread.beginSyscall(); const fd: posix.fd_t = while (true) { - try t.checkCancel(); const rc = openat_sym(dir.handle, sub_path_posix, os_flags, @as(posix.mode_t, 0)); switch (posix.errno(rc)) { - .SUCCESS => break @intCast(rc), - .INTR => continue, - .CANCELED => return error.Canceled, - - .FAULT => |err| return errnoBug(err), - .INVAL => return error.BadPathName, - .BADF => |err| return errnoBug(err), // File descriptor used after closed. - .ACCES => return error.AccessDenied, - .FBIG => return error.FileTooBig, - .OVERFLOW => return error.FileTooBig, - .ISDIR => return error.IsDir, - .LOOP => return error.SymLinkLoop, - .MFILE => return error.ProcessFdQuotaExceeded, - .NAMETOOLONG => return error.NameTooLong, - .NFILE => return error.SystemFdQuotaExceeded, - .NODEV => return error.NoDevice, - .NOENT => return error.FileNotFound, - .SRCH => return error.ProcessNotFound, - .NOMEM => return error.SystemResources, - .NOSPC => return error.NoSpaceLeft, - .NOTDIR => return error.NotDir, - .PERM => return error.PermissionDenied, - .EXIST => return error.PathAlreadyExists, - .BUSY => return error.DeviceBusy, - .OPNOTSUPP => return error.FileLocksNotSupported, - .AGAIN => return error.WouldBlock, - .TXTBSY => return error.FileBusy, - .NXIO => return error.NoDevice, - .ILSEQ => return error.BadPathName, - else => |err| return posix.unexpectedErrno(err), + .SUCCESS => { + current_thread.endSyscall(); + break @intCast(rc); + }, + .INTR => { + try current_thread.checkCancel(); + continue; + }, + else => |e| { + current_thread.endSyscall(); + switch (e) { + .CANCELED => return error.Canceled, + .FAULT => |err| return errnoBug(err), + .INVAL => return error.BadPathName, + .BADF => |err| return errnoBug(err), // File descriptor used after closed. + .ACCES => return error.AccessDenied, + .FBIG => return error.FileTooBig, + .OVERFLOW => return error.FileTooBig, + .ISDIR => return error.IsDir, + .LOOP => return error.SymLinkLoop, + .MFILE => return error.ProcessFdQuotaExceeded, + .NAMETOOLONG => return error.NameTooLong, + .NFILE => return error.SystemFdQuotaExceeded, + .NODEV => return error.NoDevice, + .NOENT => return error.FileNotFound, + .SRCH => return error.ProcessNotFound, + .NOMEM => return error.SystemResources, + .NOSPC => return error.NoSpaceLeft, + .NOTDIR => return error.NotDir, + .PERM => return error.PermissionDenied, + .EXIST => return error.PathAlreadyExists, + .BUSY => return error.DeviceBusy, + .OPNOTSUPP => return error.FileLocksNotSupported, + .AGAIN => return error.WouldBlock, + .TXTBSY => return error.FileBusy, + .NXIO => return error.NoDevice, + .ILSEQ => return error.BadPathName, + else => |err| return posix.unexpectedErrno(err), + } + }, } }; errdefer posix.close(fd); @@ -2093,42 +2360,70 @@ fn dirOpenFilePosix( .shared => posix.LOCK.SH | lock_nonblocking, .exclusive => posix.LOCK.EX | lock_nonblocking, }; + try current_thread.beginSyscall(); while (true) { - try t.checkCancel(); switch (posix.errno(posix.system.flock(fd, lock_flags))) { - .SUCCESS => break, - .INTR => continue, - .CANCELED => return error.Canceled, - - .BADF => |err| return errnoBug(err), // File descriptor used after closed. - .INVAL => |err| return errnoBug(err), // invalid parameters - .NOLCK => return error.SystemResources, - .AGAIN => return error.WouldBlock, - .OPNOTSUPP => return error.FileLocksNotSupported, - else => |err| return posix.unexpectedErrno(err), + .SUCCESS => { + current_thread.endSyscall(); + break; + }, + .INTR => { + try current_thread.checkCancel(); + continue; + }, + else => |e| { + current_thread.endSyscall(); + switch (e) { + .CANCELED => return error.Canceled, + .BADF => |err| return errnoBug(err), // File descriptor used after closed. + .INVAL => |err| return errnoBug(err), // invalid parameters + .NOLCK => return error.SystemResources, + .AGAIN => return error.WouldBlock, + .OPNOTSUPP => return error.FileLocksNotSupported, + else => |err| return posix.unexpectedErrno(err), + } + }, } } } if (have_flock_open_flags and flags.lock_nonblocking) { + try current_thread.beginSyscall(); var fl_flags: usize = while (true) { - try t.checkCancel(); const rc = posix.system.fcntl(fd, posix.F.GETFL, @as(usize, 0)); switch (posix.errno(rc)) { - .SUCCESS => break @intCast(rc), - .INTR => continue, - .CANCELED => return error.Canceled, - else => |err| return posix.unexpectedErrno(err), + .SUCCESS => { + current_thread.endSyscall(); + break @intCast(rc); + }, + .INTR => { + try current_thread.checkCancel(); + continue; + }, + else => |err| { + current_thread.endSyscall(); + return posix.unexpectedErrno(err); + }, } }; + fl_flags |= @as(usize, 1 << @bitOffsetOf(posix.O, "NONBLOCK")); + + try current_thread.beginSyscall(); while (true) { - try t.checkCancel(); switch (posix.errno(posix.system.fcntl(fd, posix.F.SETFL, fl_flags))) { - .SUCCESS => break, - .INTR => continue, - .CANCELED => return error.Canceled, - else => |err| return posix.unexpectedErrno(err), + .SUCCESS => { + current_thread.endSyscall(); + break; + }, + .INTR => { + try current_thread.checkCancel(); + continue; + }, + else => |err| { + current_thread.endSyscall(); + return posix.unexpectedErrno(err); + }, } } } @@ -2158,7 +2453,7 @@ pub fn dirOpenFileWtf16( if (std.mem.eql(u16, sub_path_w, &.{'.'})) return error.IsDir; if (std.mem.eql(u16, sub_path_w, &.{ '.', '.' })) return error.IsDir; const path_len_bytes = std.math.cast(u16, sub_path_w.len * 2) orelse return error.NameTooLong; - + const current_thread = Thread.getCurrent(t); const w = windows; var nt_name: w.UNICODE_STRING = .{ @@ -2187,7 +2482,7 @@ pub fn dirOpenFileWtf16( var attempt: u5 = 0; const handle = while (true) { - try t.checkCancel(); + try current_thread.checkCancel(); var result: w.HANDLE = undefined; const rc = w.ntdll.NtCreateFile( @@ -2281,6 +2576,7 @@ fn dirOpenFileWasi( ) Io.File.OpenError!Io.File { if (builtin.link_libc) return dirOpenFilePosix(userdata, dir, sub_path, flags); const t: *Threaded = @ptrCast(@alignCast(userdata)); + const current_thread = Thread.getCurrent(t); const wasi = std.os.wasi; var base: std.os.wasi.rights_t = .{}; // POLL_FD_READWRITE only grants extra rights if the corresponding FD_READ and/or FD_WRITE @@ -2310,33 +2606,44 @@ fn dirOpenFileWasi( const inheriting: wasi.rights_t = .{}; const fdflags: wasi.fdflags_t = .{}; var fd: posix.fd_t = undefined; + try current_thread.beginSyscall(); while (true) { - try t.checkCancel(); switch (wasi.path_open(dir.handle, lookup_flags, sub_path.ptr, sub_path.len, oflags, base, inheriting, fdflags, &fd)) { - .SUCCESS => return .{ .handle = fd }, - .INTR => continue, - .CANCELED => return error.Canceled, - - .FAULT => |err| return errnoBug(err), - .BADF => |err| return errnoBug(err), // File descriptor used after closed. - .ACCES => return error.AccessDenied, - .FBIG => return error.FileTooBig, - .OVERFLOW => return error.FileTooBig, - .ISDIR => return error.IsDir, - .LOOP => return error.SymLinkLoop, - .MFILE => return error.ProcessFdQuotaExceeded, - .NFILE => return error.SystemFdQuotaExceeded, - .NODEV => return error.NoDevice, - .NOENT => return error.FileNotFound, - .NOMEM => return error.SystemResources, - .NOTDIR => return error.NotDir, - .PERM => return error.PermissionDenied, - .BUSY => return error.DeviceBusy, - .NOTCAPABLE => return error.AccessDenied, - .NAMETOOLONG => return error.NameTooLong, - .INVAL => return error.BadPathName, - .ILSEQ => return error.BadPathName, - else => |err| return posix.unexpectedErrno(err), + .SUCCESS => { + errdefer posix.close(fd); + current_thread.endSyscall(); + return .{ .handle = fd }; + }, + .INTR => { + try current_thread.checkCancel(); + continue; + }, + else => |e| { + current_thread.endSyscall(); + switch (e) { + .CANCELED => return error.Canceled, + .FAULT => |err| return errnoBug(err), + .BADF => |err| return errnoBug(err), // File descriptor used after closed. + .ACCES => return error.AccessDenied, + .FBIG => return error.FileTooBig, + .OVERFLOW => return error.FileTooBig, + .ISDIR => return error.IsDir, + .LOOP => return error.SymLinkLoop, + .MFILE => return error.ProcessFdQuotaExceeded, + .NFILE => return error.SystemFdQuotaExceeded, + .NODEV => return error.NoDevice, + .NOENT => return error.FileNotFound, + .NOMEM => return error.SystemResources, + .NOTDIR => return error.NotDir, + .PERM => return error.PermissionDenied, + .BUSY => return error.DeviceBusy, + .NOTCAPABLE => return error.AccessDenied, + .NAMETOOLONG => return error.NameTooLong, + .INVAL => return error.BadPathName, + .ILSEQ => return error.BadPathName, + else => |err| return posix.unexpectedErrno(err), + } + }, } } } @@ -2361,6 +2668,8 @@ fn dirOpenDirPosix( return dirOpenDirWindows(t, dir, sub_path_w.span(), options); } + const current_thread = Thread.getCurrent(t); + var path_buffer: [posix.PATH_MAX]u8 = undefined; const sub_path_posix = try pathToPosix(sub_path, &path_buffer); @@ -2381,31 +2690,41 @@ fn dirOpenDirPosix( if (@hasField(posix.O, "PATH") and !options.iterate) flags.PATH = true; + try current_thread.beginSyscall(); while (true) { - try t.checkCancel(); const rc = openat_sym(dir.handle, sub_path_posix, flags, @as(usize, 0)); switch (posix.errno(rc)) { - .SUCCESS => return .{ .handle = @intCast(rc) }, - .INTR => continue, - .CANCELED => return error.Canceled, - - .FAULT => |err| return errnoBug(err), - .INVAL => return error.BadPathName, - .BADF => |err| return errnoBug(err), // File descriptor used after closed. - .ACCES => return error.AccessDenied, - .LOOP => return error.SymLinkLoop, - .MFILE => return error.ProcessFdQuotaExceeded, - .NAMETOOLONG => return error.NameTooLong, - .NFILE => return error.SystemFdQuotaExceeded, - .NODEV => return error.NoDevice, - .NOENT => return error.FileNotFound, - .NOMEM => return error.SystemResources, - .NOTDIR => return error.NotDir, - .PERM => return error.PermissionDenied, - .BUSY => return error.DeviceBusy, - .NXIO => return error.NoDevice, - .ILSEQ => return error.BadPathName, - else => |err| return posix.unexpectedErrno(err), + .SUCCESS => { + current_thread.endSyscall(); + return .{ .handle = @intCast(rc) }; + }, + .INTR => { + try current_thread.checkCancel(); + continue; + }, + else => |e| { + current_thread.endSyscall(); + switch (e) { + .CANCELED => return error.Canceled, + .FAULT => |err| return errnoBug(err), + .INVAL => return error.BadPathName, + .BADF => |err| return errnoBug(err), // File descriptor used after closed. + .ACCES => return error.AccessDenied, + .LOOP => return error.SymLinkLoop, + .MFILE => return error.ProcessFdQuotaExceeded, + .NAMETOOLONG => return error.NameTooLong, + .NFILE => return error.SystemFdQuotaExceeded, + .NODEV => return error.NoDevice, + .NOENT => return error.FileNotFound, + .NOMEM => return error.SystemResources, + .NOTDIR => return error.NotDir, + .PERM => return error.PermissionDenied, + .BUSY => return error.DeviceBusy, + .NXIO => return error.NoDevice, + .ILSEQ => return error.BadPathName, + else => |err| return posix.unexpectedErrno(err), + } + }, } } } @@ -2417,34 +2736,46 @@ fn dirOpenDirHaiku( options: Io.Dir.OpenOptions, ) Io.Dir.OpenError!Io.Dir { const t: *Threaded = @ptrCast(@alignCast(userdata)); + const current_thread = Thread.getCurrent(t); var path_buffer: [posix.PATH_MAX]u8 = undefined; const sub_path_posix = try pathToPosix(sub_path, &path_buffer); _ = options; + try current_thread.beginSyscall(); while (true) { - try t.checkCancel(); const rc = posix.system._kern_open_dir(dir.handle, sub_path_posix); - if (rc >= 0) return .{ .handle = rc }; + if (rc >= 0) { + current_thread.endSyscall(); + return .{ .handle = rc }; + } switch (@as(posix.E, @enumFromInt(rc))) { - .INTR => continue, - .CANCELED => return error.Canceled, - .FAULT => |err| return errnoBug(err), - .INVAL => |err| return errnoBug(err), - .BADF => |err| return errnoBug(err), // File descriptor used after closed. - .ACCES => return error.AccessDenied, - .LOOP => return error.SymLinkLoop, - .MFILE => return error.ProcessFdQuotaExceeded, - .NAMETOOLONG => return error.NameTooLong, - .NFILE => return error.SystemFdQuotaExceeded, - .NODEV => return error.NoDevice, - .NOENT => return error.FileNotFound, - .NOMEM => return error.SystemResources, - .NOTDIR => return error.NotDir, - .PERM => return error.PermissionDenied, - .BUSY => return error.DeviceBusy, - else => |err| return posix.unexpectedErrno(err), + .INTR => { + try current_thread.checkCancel(); + continue; + }, + else => |e| { + current_thread.endSyscall(); + switch (e) { + .CANCELED => return error.Canceled, + .FAULT => |err| return errnoBug(err), + .INVAL => |err| return errnoBug(err), + .BADF => |err| return errnoBug(err), // File descriptor used after closed. + .ACCES => return error.AccessDenied, + .LOOP => return error.SymLinkLoop, + .MFILE => return error.ProcessFdQuotaExceeded, + .NAMETOOLONG => return error.NameTooLong, + .NFILE => return error.SystemFdQuotaExceeded, + .NODEV => return error.NoDevice, + .NOENT => return error.FileNotFound, + .NOMEM => return error.SystemResources, + .NOTDIR => return error.NotDir, + .PERM => return error.PermissionDenied, + .BUSY => return error.DeviceBusy, + else => |err| return posix.unexpectedErrno(err), + } + }, } } } @@ -2455,6 +2786,7 @@ pub fn dirOpenDirWindows( sub_path_w: [:0]const u16, options: Io.Dir.OpenOptions, ) Io.Dir.OpenError!Io.Dir { + const current_thread = Thread.getCurrent(t); const w = windows; // TODO remove some of these flags if options.access_sub_paths is false const base_flags = w.STANDARD_RIGHTS_READ | w.FILE_READ_ATTRIBUTES | w.FILE_READ_EA | @@ -2478,7 +2810,7 @@ pub fn dirOpenDirWindows( const open_reparse_point: w.DWORD = if (!options.follow_symlinks) w.FILE_OPEN_REPARSE_POINT else 0x0; var io_status_block: w.IO_STATUS_BLOCK = undefined; var result: Io.Dir = .{ .handle = undefined }; - try t.checkCancel(); + try current_thread.checkCancel(); const rc = w.ntdll.NtCreateFile( &result.handle, access_mask, @@ -2527,6 +2859,7 @@ fn dirOpenDirWasi( ) Io.Dir.OpenError!Io.Dir { if (builtin.link_libc) return dirOpenDirPosix(userdata, dir, sub_path, options); const t: *Threaded = @ptrCast(@alignCast(userdata)); + const current_thread = Thread.getCurrent(t); const wasi = std.os.wasi; var base: std.os.wasi.rights_t = .{ @@ -2556,31 +2889,40 @@ fn dirOpenDirWasi( const oflags: wasi.oflags_t = .{ .DIRECTORY = true }; const fdflags: wasi.fdflags_t = .{}; var fd: posix.fd_t = undefined; - + try current_thread.beginSyscall(); while (true) { - try t.checkCancel(); switch (wasi.path_open(dir.handle, lookup_flags, sub_path.ptr, sub_path.len, oflags, base, base, fdflags, &fd)) { - .SUCCESS => return .{ .handle = fd }, - .INTR => continue, - .CANCELED => return error.Canceled, - - .FAULT => |err| return errnoBug(err), - .INVAL => return error.BadPathName, - .BADF => |err| return errnoBug(err), // File descriptor used after closed. - .ACCES => return error.AccessDenied, - .LOOP => return error.SymLinkLoop, - .MFILE => return error.ProcessFdQuotaExceeded, - .NAMETOOLONG => return error.NameTooLong, - .NFILE => return error.SystemFdQuotaExceeded, - .NODEV => return error.NoDevice, - .NOENT => return error.FileNotFound, - .NOMEM => return error.SystemResources, - .NOTDIR => return error.NotDir, - .PERM => return error.PermissionDenied, - .BUSY => return error.DeviceBusy, - .NOTCAPABLE => return error.AccessDenied, - .ILSEQ => return error.BadPathName, - else => |err| return posix.unexpectedErrno(err), + .SUCCESS => { + current_thread.endSyscall(); + return .{ .handle = fd }; + }, + .INTR => { + try current_thread.checkCancel(); + continue; + }, + else => |e| { + current_thread.endSyscall(); + switch (e) { + .CANCELED => return error.Canceled, + .FAULT => |err| return errnoBug(err), + .INVAL => return error.BadPathName, + .BADF => |err| return errnoBug(err), // File descriptor used after closed. + .ACCES => return error.AccessDenied, + .LOOP => return error.SymLinkLoop, + .MFILE => return error.ProcessFdQuotaExceeded, + .NAMETOOLONG => return error.NameTooLong, + .NFILE => return error.SystemFdQuotaExceeded, + .NODEV => return error.NoDevice, + .NOENT => return error.FileNotFound, + .NOMEM => return error.SystemResources, + .NOTDIR => return error.NotDir, + .PERM => return error.PermissionDenied, + .BUSY => return error.DeviceBusy, + .NOTCAPABLE => return error.AccessDenied, + .ILSEQ => return error.BadPathName, + else => |err| return posix.unexpectedErrno(err), + } + }, } } } @@ -2598,6 +2940,7 @@ const fileReadStreaming = switch (native_os) { fn fileReadStreamingPosix(userdata: ?*anyopaque, file: Io.File, data: [][]u8) Io.File.Reader.Error!usize { const t: *Threaded = @ptrCast(@alignCast(userdata)); + const current_thread = Thread.getCurrent(t); var iovecs_buffer: [max_iovecs_len]posix.iovec = undefined; var i: usize = 0; @@ -2611,59 +2954,82 @@ fn fileReadStreamingPosix(userdata: ?*anyopaque, file: Io.File, data: [][]u8) Io const dest = iovecs_buffer[0..i]; assert(dest[0].len > 0); - if (native_os == .wasi and !builtin.link_libc) while (true) { - try t.checkCancel(); - var nread: usize = undefined; - switch (std.os.wasi.fd_read(file.handle, dest.ptr, dest.len, &nread)) { - .SUCCESS => return nread, - .INTR => continue, - .CANCELED => return error.Canceled, - - .INVAL => |err| return errnoBug(err), - .FAULT => |err| return errnoBug(err), - .BADF => return error.NotOpenForReading, // File operation on directory. - .IO => return error.InputOutput, - .ISDIR => return error.IsDir, - .NOBUFS => return error.SystemResources, - .NOMEM => return error.SystemResources, - .NOTCONN => return error.SocketUnconnected, - .CONNRESET => return error.ConnectionResetByPeer, - .TIMEDOUT => return error.Timeout, - .NOTCAPABLE => return error.AccessDenied, - else => |err| return posix.unexpectedErrno(err), + if (native_os == .wasi and !builtin.link_libc) { + try current_thread.beginSyscall(); + while (true) { + var nread: usize = undefined; + switch (std.os.wasi.fd_read(file.handle, dest.ptr, dest.len, &nread)) { + .SUCCESS => { + current_thread.endSyscall(); + return nread; + }, + .INTR => { + try current_thread.checkCancel(); + continue; + }, + else => |e| { + current_thread.endSyscall(); + switch (e) { + .CANCELED => return error.Canceled, + .INVAL => |err| return errnoBug(err), + .FAULT => |err| return errnoBug(err), + .BADF => return error.NotOpenForReading, // File operation on directory. + .IO => return error.InputOutput, + .ISDIR => return error.IsDir, + .NOBUFS => return error.SystemResources, + .NOMEM => return error.SystemResources, + .NOTCONN => return error.SocketUnconnected, + .CONNRESET => return error.ConnectionResetByPeer, + .TIMEDOUT => return error.Timeout, + .NOTCAPABLE => return error.AccessDenied, + else => |err| return posix.unexpectedErrno(err), + } + }, + } } - }; + } + try current_thread.beginSyscall(); while (true) { - try t.checkCancel(); const rc = posix.system.readv(file.handle, dest.ptr, @intCast(dest.len)); switch (posix.errno(rc)) { - .SUCCESS => return @intCast(rc), - .INTR => continue, - .CANCELED => return error.Canceled, - - .INVAL => |err| return errnoBug(err), - .FAULT => |err| return errnoBug(err), - .SRCH => return error.ProcessNotFound, - .AGAIN => return error.WouldBlock, - .BADF => |err| { - if (native_os == .wasi) return error.NotOpenForReading; // File operation on directory. - return errnoBug(err); // File descriptor used after closed. + .SUCCESS => { + current_thread.endSyscall(); + return @intCast(rc); + }, + .INTR => { + try current_thread.checkCancel(); + continue; + }, + else => |e| { + current_thread.endSyscall(); + switch (e) { + .CANCELED => return error.Canceled, + .INVAL => |err| return errnoBug(err), + .FAULT => |err| return errnoBug(err), + .SRCH => return error.ProcessNotFound, + .AGAIN => return error.WouldBlock, + .BADF => |err| { + if (native_os == .wasi) return error.NotOpenForReading; // File operation on directory. + return errnoBug(err); // File descriptor used after closed. + }, + .IO => return error.InputOutput, + .ISDIR => return error.IsDir, + .NOBUFS => return error.SystemResources, + .NOMEM => return error.SystemResources, + .NOTCONN => return error.SocketUnconnected, + .CONNRESET => return error.ConnectionResetByPeer, + .TIMEDOUT => return error.Timeout, + else => |err| return posix.unexpectedErrno(err), + } }, - .IO => return error.InputOutput, - .ISDIR => return error.IsDir, - .NOBUFS => return error.SystemResources, - .NOMEM => return error.SystemResources, - .NOTCONN => return error.SocketUnconnected, - .CONNRESET => return error.ConnectionResetByPeer, - .TIMEDOUT => return error.Timeout, - else => |err| return posix.unexpectedErrno(err), } } } fn fileReadStreamingWindows(userdata: ?*anyopaque, file: Io.File, data: [][]u8) Io.File.Reader.Error!usize { const t: *Threaded = @ptrCast(@alignCast(userdata)); + const current_thread = Thread.getCurrent(t); const DWORD = windows.DWORD; var index: usize = 0; @@ -2672,7 +3038,7 @@ fn fileReadStreamingWindows(userdata: ?*anyopaque, file: Io.File, data: [][]u8) const want_read_count: DWORD = @min(std.math.maxInt(DWORD), buffer.len); while (true) { - try t.checkCancel(); + try current_thread.checkCancel(); var n: DWORD = undefined; if (windows.kernel32.ReadFile(file.handle, buffer.ptr, want_read_count, &n, null) != 0) return n; @@ -2692,6 +3058,7 @@ fn fileReadStreamingWindows(userdata: ?*anyopaque, file: Io.File, data: [][]u8) fn fileReadPositionalPosix(userdata: ?*anyopaque, file: Io.File, data: [][]u8, offset: u64) Io.File.ReadPositionalError!usize { const t: *Threaded = @ptrCast(@alignCast(userdata)); + const current_thread = Thread.getCurrent(t); if (!have_preadv) @compileError("TODO"); @@ -2707,60 +3074,82 @@ fn fileReadPositionalPosix(userdata: ?*anyopaque, file: Io.File, data: [][]u8, o const dest = iovecs_buffer[0..i]; assert(dest[0].len > 0); - if (native_os == .wasi and !builtin.link_libc) while (true) { - try t.checkCancel(); - var nread: usize = undefined; - switch (std.os.wasi.fd_pread(file.handle, dest.ptr, dest.len, offset, &nread)) { - .SUCCESS => return nread, - .INTR => continue, - .CANCELED => return error.Canceled, - - .INVAL => |err| return errnoBug(err), - .FAULT => |err| return errnoBug(err), - .AGAIN => |err| return errnoBug(err), - .BADF => return error.NotOpenForReading, // File operation on directory. - .IO => return error.InputOutput, - .ISDIR => return error.IsDir, - .NOBUFS => return error.SystemResources, - .NOMEM => return error.SystemResources, - .NOTCONN => return error.SocketUnconnected, - .CONNRESET => return error.ConnectionResetByPeer, - .TIMEDOUT => return error.Timeout, - .NXIO => return error.Unseekable, - .SPIPE => return error.Unseekable, - .OVERFLOW => return error.Unseekable, - .NOTCAPABLE => return error.AccessDenied, - else => |err| return posix.unexpectedErrno(err), + if (native_os == .wasi and !builtin.link_libc) { + try current_thread.beginSyscall(); + while (true) { + var nread: usize = undefined; + switch (std.os.wasi.fd_pread(file.handle, dest.ptr, dest.len, offset, &nread)) { + .SUCCESS => { + current_thread.endSyscall(); + return nread; + }, + .INTR => { + try current_thread.checkCancel(); + continue; + }, + else => |e| { + current_thread.endSyscall(); + switch (e) { + .CANCELED => return error.Canceled, + .INVAL => |err| return errnoBug(err), + .FAULT => |err| return errnoBug(err), + .AGAIN => |err| return errnoBug(err), + .BADF => return error.NotOpenForReading, // File operation on directory. + .IO => return error.InputOutput, + .ISDIR => return error.IsDir, + .NOBUFS => return error.SystemResources, + .NOMEM => return error.SystemResources, + .NOTCONN => return error.SocketUnconnected, + .CONNRESET => return error.ConnectionResetByPeer, + .TIMEDOUT => return error.Timeout, + .NXIO => return error.Unseekable, + .SPIPE => return error.Unseekable, + .OVERFLOW => return error.Unseekable, + .NOTCAPABLE => return error.AccessDenied, + else => |err| return posix.unexpectedErrno(err), + } + }, + } } - }; + } + try current_thread.beginSyscall(); while (true) { - try t.checkCancel(); const rc = preadv_sym(file.handle, dest.ptr, @intCast(dest.len), @bitCast(offset)); switch (posix.errno(rc)) { - .SUCCESS => return @bitCast(rc), - .INTR => continue, - .CANCELED => return error.Canceled, - - .INVAL => |err| return errnoBug(err), - .FAULT => |err| return errnoBug(err), - .SRCH => return error.ProcessNotFound, - .AGAIN => return error.WouldBlock, - .BADF => |err| { - if (native_os == .wasi) return error.NotOpenForReading; // File operation on directory. - return errnoBug(err); // File descriptor used after closed. + .SUCCESS => { + current_thread.endSyscall(); + return @bitCast(rc); + }, + .INTR => { + try current_thread.checkCancel(); + continue; + }, + else => |e| { + current_thread.endSyscall(); + switch (e) { + .CANCELED => return error.Canceled, + .INVAL => |err| return errnoBug(err), + .FAULT => |err| return errnoBug(err), + .SRCH => return error.ProcessNotFound, + .AGAIN => return error.WouldBlock, + .BADF => |err| { + if (native_os == .wasi) return error.NotOpenForReading; // File operation on directory. + return errnoBug(err); // File descriptor used after closed. + }, + .IO => return error.InputOutput, + .ISDIR => return error.IsDir, + .NOBUFS => return error.SystemResources, + .NOMEM => return error.SystemResources, + .NOTCONN => return error.SocketUnconnected, + .CONNRESET => return error.ConnectionResetByPeer, + .TIMEDOUT => return error.Timeout, + .NXIO => return error.Unseekable, + .SPIPE => return error.Unseekable, + .OVERFLOW => return error.Unseekable, + else => |err| return posix.unexpectedErrno(err), + } }, - .IO => return error.InputOutput, - .ISDIR => return error.IsDir, - .NOBUFS => return error.SystemResources, - .NOMEM => return error.SystemResources, - .NOTCONN => return error.SocketUnconnected, - .CONNRESET => return error.ConnectionResetByPeer, - .TIMEDOUT => return error.Timeout, - .NXIO => return error.Unseekable, - .SPIPE => return error.Unseekable, - .OVERFLOW => return error.Unseekable, - else => |err| return posix.unexpectedErrno(err), } } } @@ -2772,6 +3161,7 @@ const fileReadPositional = switch (native_os) { fn fileReadPositionalWindows(userdata: ?*anyopaque, file: Io.File, data: [][]u8, offset: u64) Io.File.ReadPositionalError!usize { const t: *Threaded = @ptrCast(@alignCast(userdata)); + const current_thread = Thread.getCurrent(t); const DWORD = windows.DWORD; @@ -2793,7 +3183,7 @@ fn fileReadPositionalWindows(userdata: ?*anyopaque, file: Io.File, data: [][]u8, }; while (true) { - try t.checkCancel(); + try current_thread.checkCancel(); var n: DWORD = undefined; if (windows.kernel32.ReadFile(file.handle, buffer.ptr, want_read_count, &n, &overlapped) != 0) return n; @@ -2813,8 +3203,7 @@ fn fileReadPositionalWindows(userdata: ?*anyopaque, file: Io.File, data: [][]u8, fn fileSeekBy(userdata: ?*anyopaque, file: Io.File, offset: i64) Io.File.SeekError!void { const t: *Threaded = @ptrCast(@alignCast(userdata)); - try t.checkCancel(); - + _ = t; _ = file; _ = offset; @panic("TODO implement fileSeekBy"); @@ -2822,63 +3211,96 @@ fn fileSeekBy(userdata: ?*anyopaque, file: Io.File, offset: i64) Io.File.SeekErr fn fileSeekTo(userdata: ?*anyopaque, file: Io.File, offset: u64) Io.File.SeekError!void { const t: *Threaded = @ptrCast(@alignCast(userdata)); + const current_thread = Thread.getCurrent(t); const fd = file.handle; - if (native_os == .linux and !builtin.link_libc and @sizeOf(usize) == 4) while (true) { - try t.checkCancel(); - var result: u64 = undefined; - switch (posix.errno(posix.system.llseek(fd, offset, &result, posix.SEEK.SET))) { - .SUCCESS => return, - .INTR => continue, - .CANCELED => return error.Canceled, - - .BADF => |err| return errnoBug(err), // File descriptor used after closed. - .INVAL => return error.Unseekable, - .OVERFLOW => return error.Unseekable, - .SPIPE => return error.Unseekable, - .NXIO => return error.Unseekable, - else => |err| return posix.unexpectedErrno(err), + if (native_os == .linux and !builtin.link_libc and @sizeOf(usize) == 4) { + try current_thread.beginSyscall(); + while (true) { + var result: u64 = undefined; + switch (posix.errno(posix.system.llseek(fd, offset, &result, posix.SEEK.SET))) { + .SUCCESS => { + current_thread.endSyscall(); + return; + }, + .INTR => { + try current_thread.checkCancel(); + continue; + }, + else => |e| { + current_thread.endSyscall(); + switch (e) { + .CANCELED => return error.Canceled, + .BADF => |err| return errnoBug(err), // File descriptor used after closed. + .INVAL => return error.Unseekable, + .OVERFLOW => return error.Unseekable, + .SPIPE => return error.Unseekable, + .NXIO => return error.Unseekable, + else => |err| return posix.unexpectedErrno(err), + } + }, + } } - }; + } if (native_os == .windows) { - try t.checkCancel(); + try current_thread.checkCancel(); return windows.SetFilePointerEx_BEGIN(fd, offset); } if (native_os == .wasi and !builtin.link_libc) while (true) { - try t.checkCancel(); var new_offset: std.os.wasi.filesize_t = undefined; + try current_thread.beginSyscall(); switch (std.os.wasi.fd_seek(fd, @bitCast(offset), .SET, &new_offset)) { - .SUCCESS => return, - .INTR => continue, - .CANCELED => return error.Canceled, - - .BADF => |err| return errnoBug(err), // File descriptor used after closed. - .INVAL => return error.Unseekable, - .OVERFLOW => return error.Unseekable, - .SPIPE => return error.Unseekable, - .NXIO => return error.Unseekable, - .NOTCAPABLE => return error.AccessDenied, - else => |err| return posix.unexpectedErrno(err), + .SUCCESS => { + current_thread.endSyscall(); + return; + }, + .INTR => { + try current_thread.checkCancel(); + continue; + }, + else => |e| { + current_thread.endSyscall(); + switch (e) { + .CANCELED => return error.Canceled, + .BADF => |err| return errnoBug(err), // File descriptor used after closed. + .INVAL => return error.Unseekable, + .OVERFLOW => return error.Unseekable, + .SPIPE => return error.Unseekable, + .NXIO => return error.Unseekable, + .NOTCAPABLE => return error.AccessDenied, + else => |err| return posix.unexpectedErrno(err), + } + }, } }; if (posix.SEEK == void) return error.Unseekable; + try current_thread.beginSyscall(); while (true) { - try t.checkCancel(); switch (posix.errno(lseek_sym(fd, @bitCast(offset), posix.SEEK.SET))) { - .SUCCESS => return, - .INTR => continue, - .CANCELED => return error.Canceled, - - .BADF => |err| return errnoBug(err), // File descriptor used after closed. - .INVAL => return error.Unseekable, - .OVERFLOW => return error.Unseekable, - .SPIPE => return error.Unseekable, - .NXIO => return error.Unseekable, - else => |err| return posix.unexpectedErrno(err), + .SUCCESS => { + current_thread.endSyscall(); + return; + }, + .INTR => { + try current_thread.checkCancel(); + continue; + }, + else => |e| { + current_thread.endSyscall(); + switch (e) { + .CANCELED => return error.Canceled, + .BADF => |err| return errnoBug(err), // File descriptor used after closed. + .INVAL => return error.Unseekable, + .OVERFLOW => return error.Unseekable, + .SPIPE => return error.Unseekable, + .NXIO => return error.Unseekable, + else => |err| return posix.unexpectedErrno(err), + } + }, } } } @@ -2907,8 +3329,8 @@ fn fileWritePositional( offset: u64, ) Io.File.WritePositionalError!usize { const t: *Threaded = @ptrCast(@alignCast(userdata)); + _ = t; while (true) { - try t.checkCancel(); _ = file; _ = buffer; _ = offset; @@ -2918,8 +3340,8 @@ fn fileWritePositional( fn fileWriteStreaming(userdata: ?*anyopaque, file: Io.File, buffer: [][]const u8) Io.File.WriteStreamingError!usize { const t: *Threaded = @ptrCast(@alignCast(userdata)); + _ = t; while (true) { - try t.checkCancel(); _ = file; _ = buffer; @panic("TODO implement fileWriteStreaming"); @@ -2997,6 +3419,7 @@ const sleep = switch (native_os) { fn sleepLinux(userdata: ?*anyopaque, timeout: Io.Timeout) Io.SleepError!void { const t: *Threaded = @ptrCast(@alignCast(userdata)); + const current_thread = Thread.getCurrent(t); const clock_id: posix.clockid_t = clockToPosix(switch (timeout) { .none => .awake, .duration => |d| d.clock, @@ -3008,25 +3431,37 @@ fn sleepLinux(userdata: ?*anyopaque, timeout: Io.Timeout) Io.SleepError!void { .deadline => |deadline| deadline.raw.nanoseconds, }; var timespec: posix.timespec = timestampToPosix(deadline_nanoseconds); + try current_thread.beginSyscall(); while (true) { - try t.checkCancel(); switch (std.os.linux.errno(std.os.linux.clock_nanosleep(clock_id, .{ .ABSTIME = switch (timeout) { .none, .duration => false, .deadline => true, } }, ×pec, ×pec))) { - .SUCCESS => return, - .INTR => continue, - .CANCELED => return error.Canceled, - .INVAL => return error.UnsupportedClock, - else => |err| return posix.unexpectedErrno(err), + .SUCCESS => { + current_thread.endSyscall(); + return; + }, + .INTR => { + try current_thread.checkCancel(); + continue; + }, + else => |e| { + current_thread.endSyscall(); + switch (e) { + .CANCELED => return error.Canceled, + .INVAL => return error.UnsupportedClock, + else => |err| return posix.unexpectedErrno(err), + } + }, } } } fn sleepWindows(userdata: ?*anyopaque, timeout: Io.Timeout) Io.SleepError!void { const t: *Threaded = @ptrCast(@alignCast(userdata)); + const current_thread = Thread.getCurrent(t); const t_io = ioBasic(t); - try t.checkCancel(); + try current_thread.checkCancel(); const ms = ms: { const d = (try timeout.toDurationFromNow(t_io)) orelse break :ms std.math.maxInt(windows.DWORD); @@ -3038,9 +3473,8 @@ fn sleepWindows(userdata: ?*anyopaque, timeout: Io.Timeout) Io.SleepError!void { fn sleepWasi(userdata: ?*anyopaque, timeout: Io.Timeout) Io.SleepError!void { const t: *Threaded = @ptrCast(@alignCast(userdata)); + const current_thread = Thread.getCurrent(t); const t_io = ioBasic(t); - try t.checkCancel(); - const w = std.os.wasi; const clock: w.subscription_clock_t = if (try timeout.toDurationFromNow(t_io)) |d| .{ @@ -3063,11 +3497,14 @@ fn sleepWasi(userdata: ?*anyopaque, timeout: Io.Timeout) Io.SleepError!void { }; var event: w.event_t = undefined; var nevents: usize = undefined; + try current_thread.beginSyscall(); _ = w.poll_oneoff(&in, &event, 1, &nevents); + current_thread.endSyscall(); } fn sleepPosix(userdata: ?*anyopaque, timeout: Io.Timeout) Io.SleepError!void { const t: *Threaded = @ptrCast(@alignCast(userdata)); + const current_thread = Thread.getCurrent(t); const t_io = ioBasic(t); const sec_type = @typeInfo(posix.timespec).@"struct".fields[0].type; const nsec_type = @typeInfo(posix.timespec).@"struct".fields[1].type; @@ -3079,12 +3516,18 @@ fn sleepPosix(userdata: ?*anyopaque, timeout: Io.Timeout) Io.SleepError!void { }; break :t timestampToPosix(d.raw.toNanoseconds()); }; + try current_thread.beginSyscall(); while (true) { - try t.checkCancel(); switch (posix.errno(posix.system.nanosleep(×pec, ×pec))) { - .INTR => continue, - .CANCELED => return error.Canceled, - else => return, // This prong handles success as well as unexpected errors. + .INTR => { + try current_thread.checkCancel(); + continue; + }, + else => { + // This prong handles success as well as unexpected errors. + current_thread.endSyscall(); + return; + }, } } } @@ -3127,34 +3570,48 @@ fn netListenIpPosix( ) IpAddress.ListenError!net.Server { if (!have_networking) return error.NetworkDown; const t: *Threaded = @ptrCast(@alignCast(userdata)); + const current_thread = Thread.getCurrent(t); const family = posixAddressFamily(&address); - const socket_fd = try openSocketPosix(t, family, .{ + const socket_fd = try openSocketPosix(current_thread, family, .{ .mode = options.mode, .protocol = options.protocol, }); errdefer posix.close(socket_fd); if (options.reuse_address) { - try setSocketOption(t, socket_fd, posix.SOL.SOCKET, posix.SO.REUSEADDR, 1); + try setSocketOption(current_thread, socket_fd, posix.SOL.SOCKET, posix.SO.REUSEADDR, 1); if (@hasDecl(posix.SO, "REUSEPORT")) - try setSocketOption(t, socket_fd, posix.SOL.SOCKET, posix.SO.REUSEPORT, 1); + try setSocketOption(current_thread, socket_fd, posix.SOL.SOCKET, posix.SO.REUSEPORT, 1); } var storage: PosixAddress = undefined; var addr_len = addressToPosix(&address, &storage); - try posixBind(t, socket_fd, &storage.any, addr_len); + try posixBind(current_thread, socket_fd, &storage.any, addr_len); + try current_thread.beginSyscall(); while (true) { - try t.checkCancel(); switch (posix.errno(posix.system.listen(socket_fd, options.kernel_backlog))) { - .SUCCESS => break, - .ADDRINUSE => return error.AddressInUse, - .BADF => |err| return errnoBug(err), // File descriptor used after closed. - else => |err| return posix.unexpectedErrno(err), + .SUCCESS => { + current_thread.endSyscall(); + break; + }, + .INTR => { + try current_thread.checkCancel(); + continue; + }, + else => |e| { + current_thread.endSyscall(); + switch (e) { + .CANCELED => return error.Canceled, + .ADDRINUSE => return error.AddressInUse, + .BADF => |err| return errnoBug(err), // File descriptor used after closed. + else => |err| return posix.unexpectedErrno(err), + } + }, } } - try posixGetSockName(t, socket_fd, &storage.any, &addr_len); + try posixGetSockName(current_thread, socket_fd, &storage.any, &addr_len); return .{ .socket = .{ .handle = socket_fd, @@ -3170,8 +3627,9 @@ fn netListenIpWindows( ) IpAddress.ListenError!net.Server { if (!have_networking) return error.NetworkDown; const t: *Threaded = @ptrCast(@alignCast(userdata)); + const current_thread = Thread.getCurrent(t); const family = posixAddressFamily(&address); - const socket_handle = try openSocketWsa(t, family, .{ + const socket_handle = try openSocketWsa(t, current_thread, family, .{ .mode = options.mode, .protocol = options.protocol, }); @@ -3183,52 +3641,73 @@ fn netListenIpWindows( var storage: WsaAddress = undefined; var addr_len = addressToWsa(&address, &storage); + try current_thread.beginSyscall(); while (true) { - try t.checkCancel(); const rc = ws2_32.bind(socket_handle, &storage.any, addr_len); if (rc != ws2_32.SOCKET_ERROR) break; switch (ws2_32.WSAGetLastError()) { - .EINTR => continue, - .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled, - .NOTINITIALISED => { - try initializeWsa(t); + .EINTR => { + try current_thread.checkCancel(); continue; }, - .EADDRINUSE => return error.AddressInUse, - .EADDRNOTAVAIL => return error.AddressUnavailable, - .ENOTSOCK => |err| return wsaErrorBug(err), - .EFAULT => |err| return wsaErrorBug(err), - .EINVAL => |err| return wsaErrorBug(err), - .ENOBUFS => return error.SystemResources, - .ENETDOWN => return error.NetworkDown, - else => |err| return windows.unexpectedWSAError(err), + .NOTINITIALISED => { + try initializeWsa(t); + try current_thread.checkCancel(); + continue; + }, + else => |e| { + current_thread.endSyscall(); + switch (e) { + .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled, + .EADDRINUSE => return error.AddressInUse, + .EADDRNOTAVAIL => return error.AddressUnavailable, + .ENOTSOCK => |err| return wsaErrorBug(err), + .EFAULT => |err| return wsaErrorBug(err), + .EINVAL => |err| return wsaErrorBug(err), + .ENOBUFS => return error.SystemResources, + .ENETDOWN => return error.NetworkDown, + else => |err| return windows.unexpectedWSAError(err), + } + }, } } + try current_thread.checkCancel(); while (true) { - try t.checkCancel(); const rc = ws2_32.listen(socket_handle, options.kernel_backlog); - if (rc != ws2_32.SOCKET_ERROR) break; + if (rc != ws2_32.SOCKET_ERROR) { + current_thread.endSyscall(); + break; + } switch (ws2_32.WSAGetLastError()) { - .EINTR => continue, - .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled, - .NOTINITIALISED => { - try initializeWsa(t); + .EINTR => { + try current_thread.checkCancel(); continue; }, - .ENETDOWN => return error.NetworkDown, - .EADDRINUSE => return error.AddressInUse, - .EISCONN => |err| return wsaErrorBug(err), - .EINVAL => |err| return wsaErrorBug(err), - .EMFILE, .ENOBUFS => return error.SystemResources, - .ENOTSOCK => |err| return wsaErrorBug(err), - .EOPNOTSUPP => |err| return wsaErrorBug(err), - .EINPROGRESS => |err| return wsaErrorBug(err), - else => |err| return windows.unexpectedWSAError(err), + .NOTINITIALISED => { + try initializeWsa(t); + try current_thread.checkCancel(); + continue; + }, + else => |e| { + current_thread.endSyscall(); + switch (e) { + .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled, + .ENETDOWN => return error.NetworkDown, + .EADDRINUSE => return error.AddressInUse, + .EISCONN => |err| return wsaErrorBug(err), + .EINVAL => |err| return wsaErrorBug(err), + .EMFILE, .ENOBUFS => return error.SystemResources, + .ENOTSOCK => |err| return wsaErrorBug(err), + .EOPNOTSUPP => |err| return wsaErrorBug(err), + .EINPROGRESS => |err| return wsaErrorBug(err), + else => |err| return windows.unexpectedWSAError(err), + } + }, } } - try wsaGetSockName(t, socket_handle, &storage.any, &addr_len); + try wsaGetSockName(t, current_thread, socket_handle, &storage.any, &addr_len); return .{ .socket = .{ @@ -3256,7 +3735,8 @@ fn netListenUnixPosix( ) net.UnixAddress.ListenError!net.Socket.Handle { if (!net.has_unix_sockets) return error.AddressFamilyUnsupported; const t: *Threaded = @ptrCast(@alignCast(userdata)); - const socket_fd = openSocketPosix(t, posix.AF.UNIX, .{ .mode = .stream }) catch |err| switch (err) { + const current_thread = Thread.getCurrent(t); + const socket_fd = openSocketPosix(current_thread, posix.AF.UNIX, .{ .mode = .stream }) catch |err| switch (err) { error.ProtocolUnsupportedBySystem => return error.AddressFamilyUnsupported, error.ProtocolUnsupportedByAddressFamily => return error.AddressFamilyUnsupported, error.SocketModeUnsupported => return error.AddressFamilyUnsupported, @@ -3267,15 +3747,28 @@ fn netListenUnixPosix( var storage: UnixAddress = undefined; const addr_len = addressUnixToPosix(address, &storage); - try posixBindUnix(t, socket_fd, &storage.any, addr_len); + try posixBindUnix(current_thread, socket_fd, &storage.any, addr_len); + try current_thread.beginSyscall(); while (true) { - try t.checkCancel(); switch (posix.errno(posix.system.listen(socket_fd, options.kernel_backlog))) { - .SUCCESS => break, - .ADDRINUSE => return error.AddressInUse, - .BADF => |err| return errnoBug(err), // File descriptor used after closed. - else => |err| return posix.unexpectedErrno(err), + .SUCCESS => { + current_thread.endSyscall(); + break; + }, + .INTR => { + try current_thread.checkCancel(); + continue; + }, + else => |e| { + current_thread.endSyscall(); + switch (e) { + .CANCELED => return error.Canceled, + .ADDRINUSE => return error.AddressInUse, + .BADF => |err| return errnoBug(err), // File descriptor used after closed. + else => |err| return posix.unexpectedErrno(err), + } + }, } } @@ -3289,8 +3782,9 @@ fn netListenUnixWindows( ) net.UnixAddress.ListenError!net.Socket.Handle { if (!net.has_unix_sockets) return error.AddressFamilyUnsupported; const t: *Threaded = @ptrCast(@alignCast(userdata)); + const current_thread = Thread.getCurrent(t); - const socket_handle = openSocketWsa(t, posix.AF.UNIX, .{ .mode = .stream }) catch |err| switch (err) { + const socket_handle = openSocketWsa(t, current_thread, posix.AF.UNIX, .{ .mode = .stream }) catch |err| switch (err) { error.ProtocolUnsupportedByAddressFamily => return error.AddressFamilyUnsupported, else => |e| return e, }; @@ -3299,52 +3793,67 @@ fn netListenUnixWindows( var storage: WsaAddress = undefined; const addr_len = addressUnixToWsa(address, &storage); + try current_thread.beginSyscall(); while (true) { - try t.checkCancel(); const rc = ws2_32.bind(socket_handle, &storage.any, addr_len); if (rc != ws2_32.SOCKET_ERROR) break; switch (ws2_32.WSAGetLastError()) { - .EINTR => continue, - .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled, - .NOTINITIALISED => { - try initializeWsa(t); + .EINTR => { + try current_thread.checkCancel(); continue; }, - .EADDRINUSE => return error.AddressInUse, - .EADDRNOTAVAIL => return error.AddressUnavailable, - .ENOTSOCK => |err| return wsaErrorBug(err), - .EFAULT => |err| return wsaErrorBug(err), - .EINVAL => |err| return wsaErrorBug(err), - .ENOBUFS => return error.SystemResources, - .ENETDOWN => return error.NetworkDown, - else => |err| return windows.unexpectedWSAError(err), + .NOTINITIALISED => { + try initializeWsa(t); + try current_thread.checkCancel(); + continue; + }, + else => |e| { + current_thread.endSyscall(); + switch (e) { + .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled, + .EADDRINUSE => return error.AddressInUse, + .EADDRNOTAVAIL => return error.AddressUnavailable, + .ENOTSOCK => |err| return wsaErrorBug(err), + .EFAULT => |err| return wsaErrorBug(err), + .EINVAL => |err| return wsaErrorBug(err), + .ENOBUFS => return error.SystemResources, + .ENETDOWN => return error.NetworkDown, + else => |err| return windows.unexpectedWSAError(err), + } + }, } } while (true) { - try t.checkCancel(); + try current_thread.checkCancel(); const rc = ws2_32.listen(socket_handle, options.kernel_backlog); - if (rc != ws2_32.SOCKET_ERROR) break; + if (rc != ws2_32.SOCKET_ERROR) { + current_thread.endSyscall(); + return socket_handle; + } switch (ws2_32.WSAGetLastError()) { .EINTR => continue, - .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled, .NOTINITIALISED => { try initializeWsa(t); continue; }, - .ENETDOWN => return error.NetworkDown, - .EADDRINUSE => return error.AddressInUse, - .EISCONN => |err| return wsaErrorBug(err), - .EINVAL => |err| return wsaErrorBug(err), - .EMFILE, .ENOBUFS => return error.SystemResources, - .ENOTSOCK => |err| return wsaErrorBug(err), - .EOPNOTSUPP => |err| return wsaErrorBug(err), - .EINPROGRESS => |err| return wsaErrorBug(err), - else => |err| return windows.unexpectedWSAError(err), + else => |e| { + current_thread.endSyscall(); + switch (e) { + .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled, + .ENETDOWN => return error.NetworkDown, + .EADDRINUSE => return error.AddressInUse, + .EISCONN => |err| return wsaErrorBug(err), + .EINVAL => |err| return wsaErrorBug(err), + .EMFILE, .ENOBUFS => return error.SystemResources, + .ENOTSOCK => |err| return wsaErrorBug(err), + .EOPNOTSUPP => |err| return wsaErrorBug(err), + .EINPROGRESS => |err| return wsaErrorBug(err), + else => |err| return windows.unexpectedWSAError(err), + } + }, } } - - return socket_handle; } fn netListenUnixUnavailable( @@ -3358,172 +3867,275 @@ fn netListenUnixUnavailable( return error.AddressFamilyUnsupported; } -fn posixBindUnix(t: *Threaded, fd: posix.socket_t, addr: *const posix.sockaddr, addr_len: posix.socklen_t) !void { +fn posixBindUnix( + current_thread: *Thread, + fd: posix.socket_t, + addr: *const posix.sockaddr, + addr_len: posix.socklen_t, +) !void { + try current_thread.beginSyscall(); while (true) { - try t.checkCancel(); switch (posix.errno(posix.system.bind(fd, addr, addr_len))) { - .SUCCESS => break, - .INTR => continue, - .CANCELED => return error.Canceled, - - .ACCES => return error.AccessDenied, - .ADDRINUSE => return error.AddressInUse, - .AFNOSUPPORT => return error.AddressFamilyUnsupported, - .ADDRNOTAVAIL => return error.AddressUnavailable, - .NOMEM => return error.SystemResources, - - .LOOP => return error.SymLinkLoop, - .NOENT => return error.FileNotFound, - .NOTDIR => return error.NotDir, - .ROFS => return error.ReadOnlyFileSystem, - .PERM => return error.PermissionDenied, - - .BADF => |err| return errnoBug(err), // File descriptor used after closed. - .INVAL => |err| return errnoBug(err), // invalid parameters - .NOTSOCK => |err| return errnoBug(err), // invalid `sockfd` - .FAULT => |err| return errnoBug(err), // invalid `addr` pointer - .NAMETOOLONG => |err| return errnoBug(err), - else => |err| return posix.unexpectedErrno(err), - } - } -} - -fn posixBind(t: *Threaded, socket_fd: posix.socket_t, addr: *const posix.sockaddr, addr_len: posix.socklen_t) !void { - while (true) { - try t.checkCancel(); - switch (posix.errno(posix.system.bind(socket_fd, addr, addr_len))) { - .SUCCESS => break, - .INTR => continue, - .CANCELED => return error.Canceled, - - .ADDRINUSE => return error.AddressInUse, - .BADF => |err| return errnoBug(err), // File descriptor used after closed. - .INVAL => |err| return errnoBug(err), // invalid parameters - .NOTSOCK => |err| return errnoBug(err), // invalid `sockfd` - .AFNOSUPPORT => return error.AddressFamilyUnsupported, - .ADDRNOTAVAIL => return error.AddressUnavailable, - .FAULT => |err| return errnoBug(err), // invalid `addr` pointer - .NOMEM => return error.SystemResources, - else => |err| return posix.unexpectedErrno(err), - } - } -} - -fn posixConnect(t: *Threaded, socket_fd: posix.socket_t, addr: *const posix.sockaddr, addr_len: posix.socklen_t) !void { - while (true) { - try t.checkCancel(); - switch (posix.errno(posix.system.connect(socket_fd, addr, addr_len))) { - .SUCCESS => return, - .INTR => continue, - .CANCELED => return error.Canceled, - - .ADDRNOTAVAIL => return error.AddressUnavailable, - .AFNOSUPPORT => return error.AddressFamilyUnsupported, - .AGAIN, .INPROGRESS => return error.WouldBlock, - .ALREADY => return error.ConnectionPending, - .BADF => |err| return errnoBug(err), // File descriptor used after closed. - .CONNREFUSED => return error.ConnectionRefused, - .CONNRESET => return error.ConnectionResetByPeer, - .FAULT => |err| return errnoBug(err), - .ISCONN => |err| return errnoBug(err), - .HOSTUNREACH => return error.HostUnreachable, - .NETUNREACH => return error.NetworkUnreachable, - .NOTSOCK => |err| return errnoBug(err), - .PROTOTYPE => |err| return errnoBug(err), - .TIMEDOUT => return error.Timeout, - .CONNABORTED => |err| return errnoBug(err), - .ACCES => return error.AccessDenied, - .PERM => |err| return errnoBug(err), - .NOENT => |err| return errnoBug(err), - .NETDOWN => return error.NetworkDown, - else => |err| return posix.unexpectedErrno(err), - } - } -} - -fn posixConnectUnix(t: *Threaded, fd: posix.socket_t, addr: *const posix.sockaddr, addr_len: posix.socklen_t) !void { - while (true) { - try t.checkCancel(); - switch (posix.errno(posix.system.connect(fd, addr, addr_len))) { - .SUCCESS => return, - .INTR => continue, - .CANCELED => return error.Canceled, - - .AFNOSUPPORT => return error.AddressFamilyUnsupported, - .AGAIN => return error.WouldBlock, - .INPROGRESS => return error.WouldBlock, - .ACCES => return error.AccessDenied, - - .LOOP => return error.SymLinkLoop, - .NOENT => return error.FileNotFound, - .NOTDIR => return error.NotDir, - .ROFS => return error.ReadOnlyFileSystem, - .PERM => return error.PermissionDenied, - - .BADF => |err| return errnoBug(err), // File descriptor used after closed. - .CONNABORTED => |err| return errnoBug(err), - .FAULT => |err| return errnoBug(err), - .ISCONN => |err| return errnoBug(err), - .NOTSOCK => |err| return errnoBug(err), - .PROTOTYPE => |err| return errnoBug(err), - else => |err| return posix.unexpectedErrno(err), - } - } -} - -fn posixGetSockName(t: *Threaded, socket_fd: posix.fd_t, addr: *posix.sockaddr, addr_len: *posix.socklen_t) !void { - while (true) { - try t.checkCancel(); - switch (posix.errno(posix.system.getsockname(socket_fd, addr, addr_len))) { - .SUCCESS => break, - .INTR => continue, - .CANCELED => return error.Canceled, - - .BADF => |err| return errnoBug(err), // File descriptor used after closed. - .FAULT => |err| return errnoBug(err), - .INVAL => |err| return errnoBug(err), // invalid parameters - .NOTSOCK => |err| return errnoBug(err), // always a race condition - .NOBUFS => return error.SystemResources, - else => |err| return posix.unexpectedErrno(err), - } - } -} - -fn wsaGetSockName(t: *Threaded, handle: ws2_32.SOCKET, addr: *ws2_32.sockaddr, addr_len: *i32) !void { - while (true) { - try t.checkCancel(); - const rc = ws2_32.getsockname(handle, addr, addr_len); - if (rc != ws2_32.SOCKET_ERROR) break; - switch (ws2_32.WSAGetLastError()) { - .EINTR => continue, - .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled, - .NOTINITIALISED => { - try initializeWsa(t); + .SUCCESS => { + current_thread.endSyscall(); + break; + }, + .INTR => { + try current_thread.checkCancel(); continue; }, - .ENETDOWN => return error.NetworkDown, - .EFAULT => |err| return wsaErrorBug(err), - .ENOTSOCK => |err| return wsaErrorBug(err), - .EINVAL => |err| return wsaErrorBug(err), - else => |err| return windows.unexpectedWSAError(err), + else => |e| { + current_thread.endSyscall(); + switch (e) { + .CANCELED => return error.Canceled, + .ACCES => return error.AccessDenied, + .ADDRINUSE => return error.AddressInUse, + .AFNOSUPPORT => return error.AddressFamilyUnsupported, + .ADDRNOTAVAIL => return error.AddressUnavailable, + .NOMEM => return error.SystemResources, + + .LOOP => return error.SymLinkLoop, + .NOENT => return error.FileNotFound, + .NOTDIR => return error.NotDir, + .ROFS => return error.ReadOnlyFileSystem, + .PERM => return error.PermissionDenied, + + .BADF => |err| return errnoBug(err), // File descriptor used after closed. + .INVAL => |err| return errnoBug(err), // invalid parameters + .NOTSOCK => |err| return errnoBug(err), // invalid `sockfd` + .FAULT => |err| return errnoBug(err), // invalid `addr` pointer + .NAMETOOLONG => |err| return errnoBug(err), + else => |err| return posix.unexpectedErrno(err), + } + }, } } } -fn setSocketOption(t: *Threaded, fd: posix.fd_t, level: i32, opt_name: u32, option: u32) !void { - const o: []const u8 = @ptrCast(&option); +fn posixBind( + current_thread: *Thread, + socket_fd: posix.socket_t, + addr: *const posix.sockaddr, + addr_len: posix.socklen_t, +) !void { + try current_thread.beginSyscall(); while (true) { - try t.checkCancel(); - switch (posix.errno(posix.system.setsockopt(fd, level, opt_name, o.ptr, @intCast(o.len)))) { - .SUCCESS => return, - .INTR => continue, - .CANCELED => return error.Canceled, + switch (posix.errno(posix.system.bind(socket_fd, addr, addr_len))) { + .SUCCESS => { + current_thread.endSyscall(); + break; + }, + .INTR => { + try current_thread.checkCancel(); + continue; + }, + else => |e| { + current_thread.endSyscall(); + switch (e) { + .CANCELED => return error.Canceled, + .ADDRINUSE => return error.AddressInUse, + .BADF => |err| return errnoBug(err), // File descriptor used after closed. + .INVAL => |err| return errnoBug(err), // invalid parameters + .NOTSOCK => |err| return errnoBug(err), // invalid `sockfd` + .AFNOSUPPORT => return error.AddressFamilyUnsupported, + .ADDRNOTAVAIL => return error.AddressUnavailable, + .FAULT => |err| return errnoBug(err), // invalid `addr` pointer + .NOMEM => return error.SystemResources, + else => |err| return posix.unexpectedErrno(err), + } + }, + } + } +} - .BADF => |err| return errnoBug(err), // File descriptor used after closed. - .NOTSOCK => |err| return errnoBug(err), - .INVAL => |err| return errnoBug(err), - .FAULT => |err| return errnoBug(err), - else => |err| return posix.unexpectedErrno(err), +fn posixConnect( + current_thread: *Thread, + socket_fd: posix.socket_t, + addr: *const posix.sockaddr, + addr_len: posix.socklen_t, +) !void { + try current_thread.beginSyscall(); + while (true) { + switch (posix.errno(posix.system.connect(socket_fd, addr, addr_len))) { + .SUCCESS => { + current_thread.endSyscall(); + return; + }, + .INTR => { + try current_thread.checkCancel(); + continue; + }, + else => |e| { + current_thread.endSyscall(); + switch (e) { + .CANCELED => return error.Canceled, + .ADDRNOTAVAIL => return error.AddressUnavailable, + .AFNOSUPPORT => return error.AddressFamilyUnsupported, + .AGAIN, .INPROGRESS => return error.WouldBlock, + .ALREADY => return error.ConnectionPending, + .BADF => |err| return errnoBug(err), // File descriptor used after closed. + .CONNREFUSED => return error.ConnectionRefused, + .CONNRESET => return error.ConnectionResetByPeer, + .FAULT => |err| return errnoBug(err), + .ISCONN => |err| return errnoBug(err), + .HOSTUNREACH => return error.HostUnreachable, + .NETUNREACH => return error.NetworkUnreachable, + .NOTSOCK => |err| return errnoBug(err), + .PROTOTYPE => |err| return errnoBug(err), + .TIMEDOUT => return error.Timeout, + .CONNABORTED => |err| return errnoBug(err), + .ACCES => return error.AccessDenied, + .PERM => |err| return errnoBug(err), + .NOENT => |err| return errnoBug(err), + .NETDOWN => return error.NetworkDown, + else => |err| return posix.unexpectedErrno(err), + } + }, + } + } +} + +fn posixConnectUnix( + current_thread: *Thread, + fd: posix.socket_t, + addr: *const posix.sockaddr, + addr_len: posix.socklen_t, +) !void { + try current_thread.beginSyscall(); + while (true) { + switch (posix.errno(posix.system.connect(fd, addr, addr_len))) { + .SUCCESS => { + current_thread.endSyscall(); + return; + }, + .INTR => { + try current_thread.checkCancel(); + continue; + }, + else => |e| { + current_thread.endSyscall(); + switch (e) { + .CANCELED => return error.Canceled, + .AFNOSUPPORT => return error.AddressFamilyUnsupported, + .AGAIN => return error.WouldBlock, + .INPROGRESS => return error.WouldBlock, + .ACCES => return error.AccessDenied, + + .LOOP => return error.SymLinkLoop, + .NOENT => return error.FileNotFound, + .NOTDIR => return error.NotDir, + .ROFS => return error.ReadOnlyFileSystem, + .PERM => return error.PermissionDenied, + + .BADF => |err| return errnoBug(err), // File descriptor used after closed. + .CONNABORTED => |err| return errnoBug(err), + .FAULT => |err| return errnoBug(err), + .ISCONN => |err| return errnoBug(err), + .NOTSOCK => |err| return errnoBug(err), + .PROTOTYPE => |err| return errnoBug(err), + else => |err| return posix.unexpectedErrno(err), + } + }, + } + } +} + +fn posixGetSockName( + current_thread: *Thread, + socket_fd: posix.fd_t, + addr: *posix.sockaddr, + addr_len: *posix.socklen_t, +) !void { + try current_thread.beginSyscall(); + while (true) { + switch (posix.errno(posix.system.getsockname(socket_fd, addr, addr_len))) { + .SUCCESS => { + current_thread.endSyscall(); + break; + }, + .INTR => { + try current_thread.checkCancel(); + continue; + }, + else => |e| { + current_thread.endSyscall(); + switch (e) { + .CANCELED => return error.Canceled, + .BADF => |err| return errnoBug(err), // File descriptor used after closed. + .FAULT => |err| return errnoBug(err), + .INVAL => |err| return errnoBug(err), // invalid parameters + .NOTSOCK => |err| return errnoBug(err), // always a race condition + .NOBUFS => return error.SystemResources, + else => |err| return posix.unexpectedErrno(err), + } + }, + } + } +} + +fn wsaGetSockName( + t: *Threaded, + current_thread: *Thread, + handle: ws2_32.SOCKET, + addr: *ws2_32.sockaddr, + addr_len: *i32, +) !void { + try current_thread.beginSyscall(); + while (true) { + const rc = ws2_32.getsockname(handle, addr, addr_len); + if (rc != ws2_32.SOCKET_ERROR) { + current_thread.endSyscall(); + return; + } + switch (ws2_32.WSAGetLastError()) { + .EINTR => { + try current_thread.checkCancel(); + continue; + }, + .NOTINITIALISED => { + try initializeWsa(t); + try current_thread.checkCancel(); + continue; + }, + else => |e| { + current_thread.endSyscall(); + switch (e) { + .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled, + .ENETDOWN => return error.NetworkDown, + .EFAULT => |err| return wsaErrorBug(err), + .ENOTSOCK => |err| return wsaErrorBug(err), + .EINVAL => |err| return wsaErrorBug(err), + else => |err| return windows.unexpectedWSAError(err), + } + }, + } + } +} + +fn setSocketOption(current_thread: *Thread, fd: posix.fd_t, level: i32, opt_name: u32, option: u32) !void { + const o: []const u8 = @ptrCast(&option); + try current_thread.beginSyscall(); + while (true) { + switch (posix.errno(posix.system.setsockopt(fd, level, opt_name, o.ptr, @intCast(o.len)))) { + .SUCCESS => { + current_thread.endSyscall(); + return; + }, + .INTR => { + try current_thread.checkCancel(); + continue; + }, + else => |e| { + current_thread.endSyscall(); + switch (e) { + .CANCELED => return error.Canceled, + .BADF => |err| return errnoBug(err), // File descriptor used after closed. + .NOTSOCK => |err| return errnoBug(err), + .INVAL => |err| return errnoBug(err), + .FAULT => |err| return errnoBug(err), + else => |err| return posix.unexpectedErrno(err), + } + }, } } } @@ -3557,16 +4169,17 @@ fn netConnectIpPosix( if (!have_networking) return error.NetworkDown; if (options.timeout != .none) @panic("TODO implement netConnectIpPosix with timeout"); const t: *Threaded = @ptrCast(@alignCast(userdata)); + const current_thread = Thread.getCurrent(t); const family = posixAddressFamily(address); - const socket_fd = try openSocketPosix(t, family, .{ + const socket_fd = try openSocketPosix(current_thread, family, .{ .mode = options.mode, .protocol = options.protocol, }); errdefer posix.close(socket_fd); var storage: PosixAddress = undefined; var addr_len = addressToPosix(address, &storage); - try posixConnect(t, socket_fd, &storage.any, addr_len); - try posixGetSockName(t, socket_fd, &storage.any, &addr_len); + try posixConnect(current_thread, socket_fd, &storage.any, addr_len); + try posixGetSockName(current_thread, socket_fd, &storage.any, &addr_len); return .{ .socket = .{ .handle = socket_fd, .address = addressFromPosix(&storage), @@ -3581,8 +4194,9 @@ fn netConnectIpWindows( if (!have_networking) return error.NetworkDown; if (options.timeout != .none) @panic("TODO implement netConnectIpWindows with timeout"); const t: *Threaded = @ptrCast(@alignCast(userdata)); + const current_thread = Thread.getCurrent(t); const family = posixAddressFamily(address); - const socket_handle = try openSocketWsa(t, family, .{ + const socket_handle = try openSocketWsa(t, current_thread, family, .{ .mode = options.mode, .protocol = options.protocol, }); @@ -3591,36 +4205,48 @@ fn netConnectIpWindows( var storage: WsaAddress = undefined; var addr_len = addressToWsa(address, &storage); + try current_thread.beginSyscall(); while (true) { const rc = ws2_32.connect(socket_handle, &storage.any, addr_len); - if (rc != ws2_32.SOCKET_ERROR) break; + if (rc != ws2_32.SOCKET_ERROR) { + current_thread.endSyscall(); + break; + } switch (ws2_32.WSAGetLastError()) { - .EINTR => continue, - .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled, - .NOTINITIALISED => { - try initializeWsa(t); + .EINTR => { + try current_thread.checkCancel(); continue; }, - - .EADDRNOTAVAIL => return error.AddressUnavailable, - .ECONNREFUSED => return error.ConnectionRefused, - .ECONNRESET => return error.ConnectionResetByPeer, - .ETIMEDOUT => return error.Timeout, - .EHOSTUNREACH => return error.HostUnreachable, - .ENETUNREACH => return error.NetworkUnreachable, - .EFAULT => |err| return wsaErrorBug(err), - .EINVAL => |err| return wsaErrorBug(err), - .EISCONN => |err| return wsaErrorBug(err), - .ENOTSOCK => |err| return wsaErrorBug(err), - .EWOULDBLOCK => return error.WouldBlock, - .EACCES => return error.AccessDenied, - .ENOBUFS => return error.SystemResources, - .EAFNOSUPPORT => return error.AddressFamilyUnsupported, - else => |err| return windows.unexpectedWSAError(err), + .NOTINITIALISED => { + try initializeWsa(t); + try current_thread.checkCancel(); + continue; + }, + else => |e| { + current_thread.endSyscall(); + switch (e) { + .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled, + .EADDRNOTAVAIL => return error.AddressUnavailable, + .ECONNREFUSED => return error.ConnectionRefused, + .ECONNRESET => return error.ConnectionResetByPeer, + .ETIMEDOUT => return error.Timeout, + .EHOSTUNREACH => return error.HostUnreachable, + .ENETUNREACH => return error.NetworkUnreachable, + .EFAULT => |err| return wsaErrorBug(err), + .EINVAL => |err| return wsaErrorBug(err), + .EISCONN => |err| return wsaErrorBug(err), + .ENOTSOCK => |err| return wsaErrorBug(err), + .EWOULDBLOCK => return error.WouldBlock, + .EACCES => return error.AccessDenied, + .ENOBUFS => return error.SystemResources, + .EAFNOSUPPORT => return error.AddressFamilyUnsupported, + else => |err| return windows.unexpectedWSAError(err), + } + }, } } - try wsaGetSockName(t, socket_handle, &storage.any, &addr_len); + try wsaGetSockName(t, current_thread, socket_handle, &storage.any, &addr_len); return .{ .socket = .{ .handle = socket_handle, @@ -3645,14 +4271,15 @@ fn netConnectUnixPosix( ) net.UnixAddress.ConnectError!net.Socket.Handle { if (!net.has_unix_sockets) return error.AddressFamilyUnsupported; const t: *Threaded = @ptrCast(@alignCast(userdata)); - const socket_fd = openSocketPosix(t, posix.AF.UNIX, .{ .mode = .stream }) catch |err| switch (err) { + const current_thread = Thread.getCurrent(t); + const socket_fd = openSocketPosix(current_thread, posix.AF.UNIX, .{ .mode = .stream }) catch |err| switch (err) { error.OptionUnsupported => return error.Unexpected, else => |e| return e, }; errdefer posix.close(socket_fd); var storage: UnixAddress = undefined; const addr_len = addressUnixToPosix(address, &storage); - try posixConnectUnix(t, socket_fd, &storage.any, addr_len); + try posixConnectUnix(current_thread, socket_fd, &storage.any, addr_len); return socket_fd; } @@ -3662,8 +4289,9 @@ fn netConnectUnixWindows( ) net.UnixAddress.ConnectError!net.Socket.Handle { if (!net.has_unix_sockets) return error.AddressFamilyUnsupported; const t: *Threaded = @ptrCast(@alignCast(userdata)); + const current_thread = Thread.getCurrent(t); - const socket_handle = try openSocketWsa(t, posix.AF.UNIX, .{ .mode = .stream }); + const socket_handle = try openSocketWsa(t, current_thread, posix.AF.UNIX, .{ .mode = .stream }); errdefer closeSocketWindows(socket_handle); var storage: WsaAddress = undefined; const addr_len = addressUnixToWsa(address, &storage); @@ -3711,13 +4339,14 @@ fn netBindIpPosix( ) IpAddress.BindError!net.Socket { if (!have_networking) return error.NetworkDown; const t: *Threaded = @ptrCast(@alignCast(userdata)); + const current_thread = Thread.getCurrent(t); const family = posixAddressFamily(address); - const socket_fd = try openSocketPosix(t, family, options); + const socket_fd = try openSocketPosix(current_thread, family, options); errdefer posix.close(socket_fd); var storage: PosixAddress = undefined; var addr_len = addressToPosix(address, &storage); - try posixBind(t, socket_fd, &storage.any, addr_len); - try posixGetSockName(t, socket_fd, &storage.any, &addr_len); + try posixBind(current_thread, socket_fd, &storage.any, addr_len); + try posixGetSockName(current_thread, socket_fd, &storage.any, &addr_len); return .{ .handle = socket_fd, .address = addressFromPosix(&storage), @@ -3731,8 +4360,9 @@ fn netBindIpWindows( ) IpAddress.BindError!net.Socket { if (!have_networking) return error.NetworkDown; const t: *Threaded = @ptrCast(@alignCast(userdata)); + const current_thread = Thread.getCurrent(t); const family = posixAddressFamily(address); - const socket_handle = try openSocketWsa(t, family, .{ + const socket_handle = try openSocketWsa(t, current_thread, family, .{ .mode = options.mode, .protocol = options.protocol, }); @@ -3741,29 +4371,41 @@ fn netBindIpWindows( var storage: WsaAddress = undefined; var addr_len = addressToWsa(address, &storage); + try current_thread.beginSyscall(); while (true) { - try t.checkCancel(); const rc = ws2_32.bind(socket_handle, &storage.any, addr_len); - if (rc != ws2_32.SOCKET_ERROR) break; + if (rc != ws2_32.SOCKET_ERROR) { + current_thread.endSyscall(); + break; + } switch (ws2_32.WSAGetLastError()) { - .EINTR => continue, - .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled, - .NOTINITIALISED => { - try initializeWsa(t); + .EINTR => { + try current_thread.checkCancel(); continue; }, - .EADDRINUSE => return error.AddressInUse, - .EADDRNOTAVAIL => return error.AddressUnavailable, - .ENOTSOCK => |err| return wsaErrorBug(err), - .EFAULT => |err| return wsaErrorBug(err), - .EINVAL => |err| return wsaErrorBug(err), - .ENOBUFS => return error.SystemResources, - .ENETDOWN => return error.NetworkDown, - else => |err| return windows.unexpectedWSAError(err), + .NOTINITIALISED => { + try initializeWsa(t); + try current_thread.checkCancel(); + continue; + }, + else => |e| { + current_thread.endSyscall(); + switch (e) { + .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled, + .EADDRINUSE => return error.AddressInUse, + .EADDRNOTAVAIL => return error.AddressUnavailable, + .ENOTSOCK => |err| return wsaErrorBug(err), + .EFAULT => |err| return wsaErrorBug(err), + .EINVAL => |err| return wsaErrorBug(err), + .ENOBUFS => return error.SystemResources, + .ENETDOWN => return error.NetworkDown, + else => |err| return windows.unexpectedWSAError(err), + } + }, } } - try wsaGetSockName(t, socket_handle, &storage.any, &addr_len); + try wsaGetSockName(t, current_thread, socket_handle, &storage.any, &addr_len); return .{ .handle = socket_handle, @@ -3783,7 +4425,7 @@ fn netBindIpUnavailable( } fn openSocketPosix( - t: *Threaded, + current_thread: *Thread, family: posix.sa_family_t, options: IpAddress.BindOptions, ) error{ @@ -3800,8 +4442,8 @@ fn openSocketPosix( }!posix.socket_t { const mode = posixSocketMode(options.mode); const protocol = posixProtocol(options.protocol); + try current_thread.beginSyscall(); const socket_fd = while (true) { - try t.checkCancel(); const flags: u32 = mode | if (socket_flags_unsupported) 0 else posix.SOCK.CLOEXEC; const socket_rc = posix.system.socket(family, flags, protocol); switch (posix.errno(socket_rc)) { @@ -3809,60 +4451,90 @@ fn openSocketPosix( const fd: posix.fd_t = @intCast(socket_rc); errdefer posix.close(fd); if (socket_flags_unsupported) while (true) { - try t.checkCancel(); + try current_thread.checkCancel(); switch (posix.errno(posix.system.fcntl(fd, posix.F.SETFD, @as(usize, posix.FD_CLOEXEC)))) { .SUCCESS => break, .INTR => continue, - .CANCELED => return error.Canceled, - else => |err| return posix.unexpectedErrno(err), + else => |e| { + current_thread.endSyscall(); + switch (e) { + .CANCELED => return error.Canceled, + else => |err| return posix.unexpectedErrno(err), + } + }, } }; + current_thread.endSyscall(); break fd; }, - .INTR => continue, - .CANCELED => return error.Canceled, - - .AFNOSUPPORT => return error.AddressFamilyUnsupported, - .INVAL => return error.ProtocolUnsupportedBySystem, - .MFILE => return error.ProcessFdQuotaExceeded, - .NFILE => return error.SystemFdQuotaExceeded, - .NOBUFS => return error.SystemResources, - .NOMEM => return error.SystemResources, - .PROTONOSUPPORT => return error.ProtocolUnsupportedByAddressFamily, - .PROTOTYPE => return error.SocketModeUnsupported, - else => |err| return posix.unexpectedErrno(err), + .INTR => { + try current_thread.checkCancel(); + continue; + }, + else => |e| { + current_thread.endSyscall(); + switch (e) { + .CANCELED => return error.Canceled, + .AFNOSUPPORT => return error.AddressFamilyUnsupported, + .INVAL => return error.ProtocolUnsupportedBySystem, + .MFILE => return error.ProcessFdQuotaExceeded, + .NFILE => return error.SystemFdQuotaExceeded, + .NOBUFS => return error.SystemResources, + .NOMEM => return error.SystemResources, + .PROTONOSUPPORT => return error.ProtocolUnsupportedByAddressFamily, + .PROTOTYPE => return error.SocketModeUnsupported, + else => |err| return posix.unexpectedErrno(err), + } + }, } }; errdefer posix.close(socket_fd); if (options.ip6_only) { if (posix.IPV6 == void) return error.OptionUnsupported; - try setSocketOption(t, socket_fd, posix.IPPROTO.IPV6, posix.IPV6.V6ONLY, 0); + try setSocketOption(current_thread, socket_fd, posix.IPPROTO.IPV6, posix.IPV6.V6ONLY, 0); } return socket_fd; } -fn openSocketWsa(t: *Threaded, family: posix.sa_family_t, options: IpAddress.BindOptions) !ws2_32.SOCKET { +fn openSocketWsa( + t: *Threaded, + current_thread: *Thread, + family: posix.sa_family_t, + options: IpAddress.BindOptions, +) !ws2_32.SOCKET { const mode = posixSocketMode(options.mode); const protocol = posixProtocol(options.protocol); const flags: u32 = ws2_32.WSA_FLAG_OVERLAPPED | ws2_32.WSA_FLAG_NO_HANDLE_INHERIT; + try current_thread.beginSyscall(); while (true) { - try t.checkCancel(); const rc = ws2_32.WSASocketW(family, @bitCast(mode), @bitCast(protocol), null, 0, flags); - if (rc != ws2_32.INVALID_SOCKET) return rc; + if (rc != ws2_32.INVALID_SOCKET) { + current_thread.endSyscall(); + return rc; + } switch (ws2_32.WSAGetLastError()) { - .EINTR => continue, - .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled, - .NOTINITIALISED => { - try initializeWsa(t); + .EINTR => { + try current_thread.checkCancel(); continue; }, - .EAFNOSUPPORT => return error.AddressFamilyUnsupported, - .EMFILE => return error.ProcessFdQuotaExceeded, - .ENOBUFS => return error.SystemResources, - .EPROTONOSUPPORT => return error.ProtocolUnsupportedByAddressFamily, - else => |err| return windows.unexpectedWSAError(err), + .NOTINITIALISED => { + try initializeWsa(t); + try current_thread.checkCancel(); + continue; + }, + else => |e| { + current_thread.endSyscall(); + switch (e) { + .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled, + .EAFNOSUPPORT => return error.AddressFamilyUnsupported, + .EMFILE => return error.ProcessFdQuotaExceeded, + .ENOBUFS => return error.SystemResources, + .EPROTONOSUPPORT => return error.ProtocolUnsupportedByAddressFamily, + else => |err| return windows.unexpectedWSAError(err), + } + }, } } } @@ -3870,10 +4542,11 @@ fn openSocketWsa(t: *Threaded, family: posix.sa_family_t, options: IpAddress.Bin fn netAcceptPosix(userdata: ?*anyopaque, listen_fd: net.Socket.Handle) net.Server.AcceptError!net.Stream { if (!have_networking) return error.NetworkDown; const t: *Threaded = @ptrCast(@alignCast(userdata)); + const current_thread = Thread.getCurrent(t); var storage: PosixAddress = undefined; var addr_len: posix.socklen_t = @sizeOf(PosixAddress); + try current_thread.beginSyscall(); const fd = while (true) { - try t.checkCancel(); const rc = if (have_accept4) posix.system.accept4(listen_fd, &storage.any, &addr_len, posix.SOCK.CLOEXEC) else @@ -3883,33 +4556,43 @@ fn netAcceptPosix(userdata: ?*anyopaque, listen_fd: net.Socket.Handle) net.Serve const fd: posix.fd_t = @intCast(rc); errdefer posix.close(fd); if (!have_accept4) while (true) { - try t.checkCancel(); + try current_thread.checkCancel(); switch (posix.errno(posix.system.fcntl(fd, posix.F.SETFD, @as(usize, posix.FD_CLOEXEC)))) { .SUCCESS => break, .INTR => continue, - .CANCELED => return error.Canceled, - else => |err| return posix.unexpectedErrno(err), + else => |err| { + current_thread.endSyscall(); + return posix.unexpectedErrno(err); + }, } }; + current_thread.endSyscall(); break fd; }, - .INTR => continue, - .CANCELED => return error.Canceled, - - .AGAIN => |err| return errnoBug(err), - .BADF => |err| return errnoBug(err), // File descriptor used after closed. - .CONNABORTED => return error.ConnectionAborted, - .FAULT => |err| return errnoBug(err), - .INVAL => return error.SocketNotListening, - .NOTSOCK => |err| return errnoBug(err), - .MFILE => return error.ProcessFdQuotaExceeded, - .NFILE => return error.SystemFdQuotaExceeded, - .NOBUFS => return error.SystemResources, - .NOMEM => return error.SystemResources, - .OPNOTSUPP => |err| return errnoBug(err), - .PROTO => return error.ProtocolFailure, - .PERM => return error.BlockedByFirewall, - else => |err| return posix.unexpectedErrno(err), + .INTR => { + try current_thread.checkCancel(); + continue; + }, + else => |e| { + current_thread.endSyscall(); + switch (e) { + .CANCELED => return error.Canceled, + .AGAIN => |err| return errnoBug(err), + .BADF => |err| return errnoBug(err), // File descriptor used after closed. + .CONNABORTED => return error.ConnectionAborted, + .FAULT => |err| return errnoBug(err), + .INVAL => return error.SocketNotListening, + .NOTSOCK => |err| return errnoBug(err), + .MFILE => return error.ProcessFdQuotaExceeded, + .NFILE => return error.SystemFdQuotaExceeded, + .NOBUFS => return error.SystemResources, + .NOMEM => return error.SystemResources, + .OPNOTSUPP => |err| return errnoBug(err), + .PROTO => return error.ProtocolFailure, + .PERM => return error.BlockedByFirewall, + else => |err| return posix.unexpectedErrno(err), + } + }, } }; return .{ .socket = .{ @@ -3921,31 +4604,44 @@ fn netAcceptPosix(userdata: ?*anyopaque, listen_fd: net.Socket.Handle) net.Serve fn netAcceptWindows(userdata: ?*anyopaque, listen_handle: net.Socket.Handle) net.Server.AcceptError!net.Stream { if (!have_networking) return error.NetworkDown; const t: *Threaded = @ptrCast(@alignCast(userdata)); + const current_thread = Thread.getCurrent(t); var storage: WsaAddress = undefined; var addr_len: i32 = @sizeOf(WsaAddress); + try current_thread.beginSyscall(); while (true) { - try t.checkCancel(); const rc = ws2_32.accept(listen_handle, &storage.any, &addr_len); - if (rc != ws2_32.INVALID_SOCKET) return .{ .socket = .{ - .handle = rc, - .address = addressFromWsa(&storage), - } }; + if (rc != ws2_32.INVALID_SOCKET) { + current_thread.endSyscall(); + return .{ .socket = .{ + .handle = rc, + .address = addressFromWsa(&storage), + } }; + } switch (ws2_32.WSAGetLastError()) { - .EINTR => continue, - .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled, - .NOTINITIALISED => { - try initializeWsa(t); + .EINTR => { + try current_thread.checkCancel(); continue; }, - .ECONNRESET => return error.ConnectionAborted, - .EFAULT => |err| return wsaErrorBug(err), - .ENOTSOCK => |err| return wsaErrorBug(err), - .EINVAL => |err| return wsaErrorBug(err), - .EMFILE => return error.ProcessFdQuotaExceeded, - .ENETDOWN => return error.NetworkDown, - .ENOBUFS => return error.SystemResources, - .EOPNOTSUPP => |err| return wsaErrorBug(err), - else => |err| return windows.unexpectedWSAError(err), + .NOTINITIALISED => { + try initializeWsa(t); + try current_thread.checkCancel(); + continue; + }, + else => |e| { + current_thread.endSyscall(); + switch (e) { + .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled, + .ECONNRESET => return error.ConnectionAborted, + .EFAULT => |err| return wsaErrorBug(err), + .ENOTSOCK => |err| return wsaErrorBug(err), + .EINVAL => |err| return wsaErrorBug(err), + .EMFILE => return error.ProcessFdQuotaExceeded, + .ENETDOWN => return error.NetworkDown, + .ENOBUFS => return error.SystemResources, + .EOPNOTSUPP => |err| return wsaErrorBug(err), + else => |err| return windows.unexpectedWSAError(err), + } + }, } } } @@ -3959,6 +4655,7 @@ fn netAcceptUnavailable(userdata: ?*anyopaque, listen_handle: net.Socket.Handle) fn netReadPosix(userdata: ?*anyopaque, fd: net.Socket.Handle, data: [][]u8) net.Stream.Reader.Error!usize { if (!have_networking) return error.NetworkDown; const t: *Threaded = @ptrCast(@alignCast(userdata)); + const current_thread = Thread.getCurrent(t); var iovecs_buffer: [max_iovecs_len]posix.iovec = undefined; var i: usize = 0; @@ -3972,48 +4669,70 @@ fn netReadPosix(userdata: ?*anyopaque, fd: net.Socket.Handle, data: [][]u8) net. const dest = iovecs_buffer[0..i]; assert(dest[0].len > 0); - if (native_os == .wasi and !builtin.link_libc) while (true) { - try t.checkCancel(); - var n: usize = undefined; - switch (std.os.wasi.fd_read(fd, dest.ptr, dest.len, &n)) { - .SUCCESS => return n, - .INTR => continue, - .CANCELED => return error.Canceled, - - .INVAL => |err| return errnoBug(err), - .FAULT => |err| return errnoBug(err), - .AGAIN => |err| return errnoBug(err), - .BADF => |err| return errnoBug(err), // File descriptor used after closed. - .NOBUFS => return error.SystemResources, - .NOMEM => return error.SystemResources, - .NOTCONN => return error.SocketUnconnected, - .CONNRESET => return error.ConnectionResetByPeer, - .TIMEDOUT => return error.Timeout, - .NOTCAPABLE => return error.AccessDenied, - else => |err| return posix.unexpectedErrno(err), + if (native_os == .wasi and !builtin.link_libc) { + try current_thread.beginSyscall(); + while (true) { + var n: usize = undefined; + switch (std.os.wasi.fd_read(fd, dest.ptr, dest.len, &n)) { + .SUCCESS => { + current_thread.endSyscall(); + return n; + }, + .INTR => { + try current_thread.checkCancel(); + continue; + }, + else => |e| { + current_thread.endSyscall(); + switch (e) { + .CANCELED => return error.Canceled, + .INVAL => |err| return errnoBug(err), + .FAULT => |err| return errnoBug(err), + .AGAIN => |err| return errnoBug(err), + .BADF => |err| return errnoBug(err), // File descriptor used after closed. + .NOBUFS => return error.SystemResources, + .NOMEM => return error.SystemResources, + .NOTCONN => return error.SocketUnconnected, + .CONNRESET => return error.ConnectionResetByPeer, + .TIMEDOUT => return error.Timeout, + .NOTCAPABLE => return error.AccessDenied, + else => |err| return posix.unexpectedErrno(err), + } + }, + } } - }; + } + try current_thread.beginSyscall(); while (true) { - try t.checkCancel(); const rc = posix.system.readv(fd, dest.ptr, @intCast(dest.len)); switch (posix.errno(rc)) { - .SUCCESS => return @intCast(rc), - .INTR => continue, - .CANCELED => return error.Canceled, - - .INVAL => |err| return errnoBug(err), - .FAULT => |err| return errnoBug(err), - .AGAIN => |err| return errnoBug(err), - .BADF => |err| return errnoBug(err), // File descriptor used after closed. - .NOBUFS => return error.SystemResources, - .NOMEM => return error.SystemResources, - .NOTCONN => return error.SocketUnconnected, - .CONNRESET => return error.ConnectionResetByPeer, - .TIMEDOUT => return error.Timeout, - .PIPE => return error.SocketUnconnected, - .NETDOWN => return error.NetworkDown, - else => |err| return posix.unexpectedErrno(err), + .SUCCESS => { + current_thread.endSyscall(); + return @intCast(rc); + }, + .INTR => { + try current_thread.checkCancel(); + continue; + }, + else => |e| { + current_thread.endSyscall(); + switch (e) { + .CANCELED => return error.Canceled, + .INVAL => |err| return errnoBug(err), + .FAULT => |err| return errnoBug(err), + .AGAIN => |err| return errnoBug(err), + .BADF => |err| return errnoBug(err), // File descriptor used after closed. + .NOBUFS => return error.SystemResources, + .NOMEM => return error.SystemResources, + .NOTCONN => return error.SocketUnconnected, + .CONNRESET => return error.ConnectionResetByPeer, + .TIMEDOUT => return error.Timeout, + .PIPE => return error.SocketUnconnected, + .NETDOWN => return error.NetworkDown, + else => |err| return posix.unexpectedErrno(err), + } + }, } } } @@ -4021,6 +4740,7 @@ fn netReadPosix(userdata: ?*anyopaque, fd: net.Socket.Handle, data: [][]u8) net. fn netReadWindows(userdata: ?*anyopaque, handle: net.Socket.Handle, data: [][]u8) net.Stream.Reader.Error!usize { if (!have_networking) return error.NetworkDown; const t: *Threaded = @ptrCast(@alignCast(userdata)); + const current_thread = Thread.getCurrent(t); const bufs = b: { var iovec_buffer: [max_iovecs_len]ws2_32.WSABUF = undefined; @@ -4048,7 +4768,7 @@ fn netReadWindows(userdata: ?*anyopaque, handle: net.Socket.Handle, data: [][]u8 }; while (true) { - try t.checkCancel(); + try current_thread.checkCancel(); var flags: u32 = 0; var overlapped: windows.OVERLAPPED = std.mem.zeroes(windows.OVERLAPPED); @@ -4108,6 +4828,7 @@ fn netSendPosix( ) struct { ?net.Socket.SendError, usize } { if (!have_networking) return .{ error.NetworkDown, 0 }; const t: *Threaded = @ptrCast(@alignCast(userdata)); + const current_thread = Thread.getCurrent(t); const posix_flags: u32 = @as(u32, if (@hasDecl(posix.MSG, "CONFIRM") and flags.confirm) posix.MSG.CONFIRM else 0) | @@ -4120,10 +4841,10 @@ fn netSendPosix( var i: usize = 0; while (messages.len - i != 0) { if (have_sendmmsg) { - i += netSendMany(t, handle, messages[i..], posix_flags) catch |err| return .{ err, i }; + i += netSendMany(current_thread, handle, messages[i..], posix_flags) catch |err| return .{ err, i }; continue; } - netSendOne(t, handle, &messages[i], posix_flags) catch |err| return .{ err, i }; + netSendOne(t, current_thread, handle, &messages[i], posix_flags) catch |err| return .{ err, i }; i += 1; } return .{ null, i }; @@ -4159,6 +4880,7 @@ fn netSendUnavailable( fn netSendOne( t: *Threaded, + current_thread: *Thread, handle: net.Socket.Handle, message: *net.OutgoingMessage, flags: u32, @@ -4175,75 +4897,92 @@ fn netSendOne( .controllen = @intCast(message.control.len), .flags = 0, }; + try current_thread.beginSyscall(); while (true) { - try t.checkCancel(); const rc = posix.system.sendmsg(handle, &msg, flags); if (is_windows) { - if (rc == ws2_32.SOCKET_ERROR) { - switch (ws2_32.WSAGetLastError()) { - .EINTR => continue, - .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled, - .NOTINITIALISED => { - try initializeWsa(t); - continue; - }, - .EACCES => return error.AccessDenied, - .EADDRNOTAVAIL => return error.AddressUnavailable, - .ECONNRESET => return error.ConnectionResetByPeer, - .EMSGSIZE => return error.MessageOversize, - .ENOBUFS => return error.SystemResources, - .ENOTSOCK => return error.FileDescriptorNotASocket, - .EAFNOSUPPORT => return error.AddressFamilyUnsupported, - .EDESTADDRREQ => unreachable, // A destination address is required. - .EFAULT => unreachable, // The lpBuffers, lpTo, lpOverlapped, lpNumberOfBytesSent, or lpCompletionRoutine parameters are not part of the user address space, or the lpTo parameter is too small. - .EHOSTUNREACH => return error.NetworkUnreachable, - .EINVAL => unreachable, - .ENETDOWN => return error.NetworkDown, - .ENETRESET => return error.ConnectionResetByPeer, - .ENETUNREACH => return error.NetworkUnreachable, - .ENOTCONN => return error.SocketUnconnected, - .ESHUTDOWN => |err| return wsaErrorBug(err), - else => |err| return windows.unexpectedWSAError(err), - } - } else { + if (rc != ws2_32.SOCKET_ERROR) { + current_thread.endSyscall(); message.data_len = @intCast(rc); return; } + switch (ws2_32.WSAGetLastError()) { + .EINTR => { + try current_thread.checkCancel(); + continue; + }, + .NOTINITIALISED => { + try initializeWsa(t); + try current_thread.checkCancel(); + continue; + }, + else => |e| { + current_thread.endSyscall(); + switch (e) { + .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled, + .EACCES => return error.AccessDenied, + .EADDRNOTAVAIL => return error.AddressUnavailable, + .ECONNRESET => return error.ConnectionResetByPeer, + .EMSGSIZE => return error.MessageOversize, + .ENOBUFS => return error.SystemResources, + .ENOTSOCK => return error.FileDescriptorNotASocket, + .EAFNOSUPPORT => return error.AddressFamilyUnsupported, + .EDESTADDRREQ => unreachable, // A destination address is required. + .EFAULT => unreachable, // The lpBuffers, lpTo, lpOverlapped, lpNumberOfBytesSent, or lpCompletionRoutine parameters are not part of the user address space, or the lpTo parameter is too small. + .EHOSTUNREACH => return error.NetworkUnreachable, + .EINVAL => unreachable, + .ENETDOWN => return error.NetworkDown, + .ENETRESET => return error.ConnectionResetByPeer, + .ENETUNREACH => return error.NetworkUnreachable, + .ENOTCONN => return error.SocketUnconnected, + .ESHUTDOWN => |err| return wsaErrorBug(err), + else => |err| return windows.unexpectedWSAError(err), + } + }, + } } switch (posix.errno(rc)) { .SUCCESS => { + current_thread.endSyscall(); message.data_len = @intCast(rc); return; }, - .INTR => continue, - .CANCELED => return error.Canceled, - - .ACCES => return error.AccessDenied, - .ALREADY => return error.FastOpenAlreadyInProgress, - .BADF => |err| return errnoBug(err), // File descriptor used after closed. - .CONNRESET => return error.ConnectionResetByPeer, - .DESTADDRREQ => |err| return errnoBug(err), - .FAULT => |err| return errnoBug(err), - .INVAL => |err| return errnoBug(err), - .ISCONN => |err| return errnoBug(err), - .MSGSIZE => return error.MessageOversize, - .NOBUFS => return error.SystemResources, - .NOMEM => return error.SystemResources, - .NOTSOCK => |err| return errnoBug(err), - .OPNOTSUPP => |err| return errnoBug(err), - .PIPE => return error.SocketUnconnected, - .AFNOSUPPORT => return error.AddressFamilyUnsupported, - .HOSTUNREACH => return error.HostUnreachable, - .NETUNREACH => return error.NetworkUnreachable, - .NOTCONN => return error.SocketUnconnected, - .NETDOWN => return error.NetworkDown, - else => |err| return posix.unexpectedErrno(err), + .INTR => { + try current_thread.checkCancel(); + continue; + }, + else => |e| { + current_thread.endSyscall(); + switch (e) { + .CANCELED => return error.Canceled, + .ACCES => return error.AccessDenied, + .ALREADY => return error.FastOpenAlreadyInProgress, + .BADF => |err| return errnoBug(err), // File descriptor used after closed. + .CONNRESET => return error.ConnectionResetByPeer, + .DESTADDRREQ => |err| return errnoBug(err), + .FAULT => |err| return errnoBug(err), + .INVAL => |err| return errnoBug(err), + .ISCONN => |err| return errnoBug(err), + .MSGSIZE => return error.MessageOversize, + .NOBUFS => return error.SystemResources, + .NOMEM => return error.SystemResources, + .NOTSOCK => |err| return errnoBug(err), + .OPNOTSUPP => |err| return errnoBug(err), + .PIPE => return error.SocketUnconnected, + .AFNOSUPPORT => return error.AddressFamilyUnsupported, + .HOSTUNREACH => return error.HostUnreachable, + .NETUNREACH => return error.NetworkUnreachable, + .NOTCONN => return error.SocketUnconnected, + .NETDOWN => return error.NetworkDown, + else => |err| return posix.unexpectedErrno(err), + } + }, } } } fn netSendMany( - t: *Threaded, + current_thread: *Thread, handle: net.Socket.Handle, messages: []net.OutgoingMessage, flags: u32, @@ -4273,40 +5012,48 @@ fn netSendMany( }; } + try current_thread.beginSyscall(); while (true) { - try t.checkCancel(); const rc = posix.system.sendmmsg(handle, clamped_msgs.ptr, @intCast(clamped_msgs.len), flags); switch (posix.errno(rc)) { .SUCCESS => { + current_thread.endSyscall(); const n: usize = @intCast(rc); for (clamped_messages[0..n], clamped_msgs[0..n]) |*message, *msg| { message.data_len = msg.len; } return n; }, - .INTR => continue, - .CANCELED => return error.Canceled, - - .AGAIN => |err| return errnoBug(err), - .ALREADY => return error.FastOpenAlreadyInProgress, - .BADF => |err| return errnoBug(err), // File descriptor used after closed. - .CONNRESET => return error.ConnectionResetByPeer, - .DESTADDRREQ => |err| return errnoBug(err), // The socket is not connection-mode, and no peer address is set. - .FAULT => |err| return errnoBug(err), // An invalid user space address was specified for an argument. - .INVAL => |err| return errnoBug(err), // Invalid argument passed. - .ISCONN => |err| return errnoBug(err), // connection-mode socket was connected already but a recipient was specified - .MSGSIZE => return error.MessageOversize, - .NOBUFS => return error.SystemResources, - .NOMEM => return error.SystemResources, - .NOTSOCK => |err| return errnoBug(err), // The file descriptor sockfd does not refer to a socket. - .OPNOTSUPP => |err| return errnoBug(err), // Some bit in the flags argument is inappropriate for the socket type. - .PIPE => return error.SocketUnconnected, - .AFNOSUPPORT => return error.AddressFamilyUnsupported, - .HOSTUNREACH => return error.HostUnreachable, - .NETUNREACH => return error.NetworkUnreachable, - .NOTCONN => return error.SocketUnconnected, - .NETDOWN => return error.NetworkDown, - else => |err| return posix.unexpectedErrno(err), + .INTR => { + try current_thread.checkCancel(); + continue; + }, + else => |e| { + current_thread.endSyscall(); + switch (e) { + .CANCELED => return error.Canceled, + .AGAIN => |err| return errnoBug(err), + .ALREADY => return error.FastOpenAlreadyInProgress, + .BADF => |err| return errnoBug(err), // File descriptor used after closed. + .CONNRESET => return error.ConnectionResetByPeer, + .DESTADDRREQ => |err| return errnoBug(err), // The socket is not connection-mode, and no peer address is set. + .FAULT => |err| return errnoBug(err), // An invalid user space address was specified for an argument. + .INVAL => |err| return errnoBug(err), // Invalid argument passed. + .ISCONN => |err| return errnoBug(err), // connection-mode socket was connected already but a recipient was specified + .MSGSIZE => return error.MessageOversize, + .NOBUFS => return error.SystemResources, + .NOMEM => return error.SystemResources, + .NOTSOCK => |err| return errnoBug(err), // The file descriptor sockfd does not refer to a socket. + .OPNOTSUPP => |err| return errnoBug(err), // Some bit in the flags argument is inappropriate for the socket type. + .PIPE => return error.SocketUnconnected, + .AFNOSUPPORT => return error.AddressFamilyUnsupported, + .HOSTUNREACH => return error.HostUnreachable, + .NETUNREACH => return error.NetworkUnreachable, + .NOTCONN => return error.SocketUnconnected, + .NETDOWN => return error.NetworkDown, + else => |err| return posix.unexpectedErrno(err), + } + }, } } } @@ -4321,6 +5068,7 @@ fn netReceivePosix( ) struct { ?net.Socket.ReceiveTimeoutError, usize } { if (!have_networking) return .{ error.NetworkDown, 0 }; const t: *Threaded = @ptrCast(@alignCast(userdata)); + const current_thread = Thread.getCurrent(t); const t_io = io(t); // recvmmsg is useless, here's why: @@ -4351,8 +5099,6 @@ fn netReceivePosix( const deadline = timeout.toDeadline(t_io) catch |err| return .{ err, message_i }; recv: while (true) { - t.checkCancel() catch |err| return .{ err, message_i }; - if (message_buffer.len - message_i == 0) return .{ null, message_i }; const message = &message_buffer[message_i]; const remaining_data_buffer = data_buffer[data_i..]; @@ -4368,7 +5114,9 @@ fn netReceivePosix( .flags = undefined, }; + current_thread.beginSyscall() catch |err| return .{ err, message_i }; const recv_rc = posix.system.recvmsg(handle, &msg, posix_flags); + current_thread.endSyscall(); switch (posix.errno(recv_rc)) { .SUCCESS => { const data = remaining_data_buffer[0..@intCast(recv_rc)]; @@ -4389,7 +5137,6 @@ fn netReceivePosix( continue; }, .AGAIN => while (true) { - t.checkCancel() catch |err| return .{ err, message_i }; if (message_i != 0) return .{ null, message_i }; const max_poll_ms = std.math.maxInt(u31); @@ -4399,7 +5146,10 @@ fn netReceivePosix( break :t @intCast(@min(max_poll_ms, duration.raw.toMilliseconds())); } else max_poll_ms; + current_thread.beginSyscall() catch |err| return .{ err, message_i }; const poll_rc = posix.system.poll(&poll_fds, poll_fds.len, timeout_ms); + current_thread.endSyscall(); + switch (posix.errno(poll_rc)) { .SUCCESS => { if (poll_rc == 0) { @@ -4486,6 +5236,7 @@ fn netWritePosix( ) net.Stream.Writer.Error!usize { if (!have_networking) return error.NetworkDown; const t: *Threaded = @ptrCast(@alignCast(userdata)); + const current_thread = Thread.getCurrent(t); var iovecs: [max_iovecs_len]posix.iovec_const = undefined; var msg: posix.msghdr_const = .{ @@ -4526,35 +5277,45 @@ fn netWritePosix( }, }; const flags = posix.MSG.NOSIGNAL; + try current_thread.beginSyscall(); while (true) { - try t.checkCancel(); const rc = posix.system.sendmsg(fd, &msg, flags); switch (posix.errno(rc)) { - .SUCCESS => return @intCast(rc), - .INTR => continue, - .CANCELED => return error.Canceled, - - .ACCES => |err| return errnoBug(err), - .AGAIN => |err| return errnoBug(err), - .ALREADY => return error.FastOpenAlreadyInProgress, - .BADF => |err| return errnoBug(err), // File descriptor used after closed. - .CONNRESET => return error.ConnectionResetByPeer, - .DESTADDRREQ => |err| return errnoBug(err), // The socket is not connection-mode, and no peer address is set. - .FAULT => |err| return errnoBug(err), // An invalid user space address was specified for an argument. - .INVAL => |err| return errnoBug(err), // Invalid argument passed. - .ISCONN => |err| return errnoBug(err), // connection-mode socket was connected already but a recipient was specified - .MSGSIZE => |err| return errnoBug(err), - .NOBUFS => return error.SystemResources, - .NOMEM => return error.SystemResources, - .NOTSOCK => |err| return errnoBug(err), // The file descriptor sockfd does not refer to a socket. - .OPNOTSUPP => |err| return errnoBug(err), // Some bit in the flags argument is inappropriate for the socket type. - .PIPE => return error.SocketUnconnected, - .AFNOSUPPORT => return error.AddressFamilyUnsupported, - .HOSTUNREACH => return error.HostUnreachable, - .NETUNREACH => return error.NetworkUnreachable, - .NOTCONN => return error.SocketUnconnected, - .NETDOWN => return error.NetworkDown, - else => |err| return posix.unexpectedErrno(err), + .SUCCESS => { + current_thread.endSyscall(); + return @intCast(rc); + }, + .INTR => { + try current_thread.checkCancel(); + continue; + }, + else => |e| { + current_thread.endSyscall(); + switch (e) { + .CANCELED => return error.Canceled, + .ACCES => |err| return errnoBug(err), + .AGAIN => |err| return errnoBug(err), + .ALREADY => return error.FastOpenAlreadyInProgress, + .BADF => |err| return errnoBug(err), // File descriptor used after closed. + .CONNRESET => return error.ConnectionResetByPeer, + .DESTADDRREQ => |err| return errnoBug(err), // The socket is not connection-mode, and no peer address is set. + .FAULT => |err| return errnoBug(err), // An invalid user space address was specified for an argument. + .INVAL => |err| return errnoBug(err), // Invalid argument passed. + .ISCONN => |err| return errnoBug(err), // connection-mode socket was connected already but a recipient was specified + .MSGSIZE => |err| return errnoBug(err), + .NOBUFS => return error.SystemResources, + .NOMEM => return error.SystemResources, + .NOTSOCK => |err| return errnoBug(err), // The file descriptor sockfd does not refer to a socket. + .OPNOTSUPP => |err| return errnoBug(err), // Some bit in the flags argument is inappropriate for the socket type. + .PIPE => return error.SocketUnconnected, + .AFNOSUPPORT => return error.AddressFamilyUnsupported, + .HOSTUNREACH => return error.HostUnreachable, + .NETUNREACH => return error.NetworkUnreachable, + .NOTCONN => return error.SocketUnconnected, + .NETDOWN => return error.NetworkDown, + else => |err| return posix.unexpectedErrno(err), + } + }, } } } @@ -4567,6 +5328,7 @@ fn netWriteWindows( splat: usize, ) net.Stream.Writer.Error!usize { const t: *Threaded = @ptrCast(@alignCast(userdata)); + const current_thread = Thread.getCurrent(t); comptime assert(native_os == .windows); var iovecs: [max_iovecs_len]ws2_32.WSABUF = undefined; @@ -4600,7 +5362,7 @@ fn netWriteWindows( }; while (true) { - try t.checkCancel(); + try current_thread.checkCancel(); var n: u32 = undefined; var overlapped: windows.OVERLAPPED = std.mem.zeroes(windows.OVERLAPPED); @@ -4707,9 +5469,10 @@ fn netInterfaceNameResolve( ) net.Interface.Name.ResolveError!net.Interface { if (!have_networking) return error.InterfaceNotFound; const t: *Threaded = @ptrCast(@alignCast(userdata)); + const current_thread = Thread.getCurrent(t); if (native_os == .linux) { - const sock_fd = openSocketPosix(t, posix.AF.UNIX, .{ .mode = .dgram }) catch |err| switch (err) { + const sock_fd = openSocketPosix(current_thread, posix.AF.UNIX, .{ .mode = .dgram }) catch |err| switch (err) { error.ProcessFdQuotaExceeded => return error.SystemResources, error.SystemFdQuotaExceeded => return error.SystemResources, error.AddressFamilyUnsupported => return error.Unexpected, @@ -4726,32 +5489,42 @@ fn netInterfaceNameResolve( .ifru = undefined, }; + try current_thread.beginSyscall(); while (true) { - try t.checkCancel(); switch (posix.errno(posix.system.ioctl(sock_fd, posix.SIOCGIFINDEX, @intFromPtr(&ifr)))) { - .SUCCESS => return .{ .index = @bitCast(ifr.ifru.ivalue) }, - .INTR => continue, - .CANCELED => return error.Canceled, - - .INVAL => |err| return errnoBug(err), // Bad parameters. - .NOTTY => |err| return errnoBug(err), - .NXIO => |err| return errnoBug(err), - .BADF => |err| return errnoBug(err), // File descriptor used after closed. - .FAULT => |err| return errnoBug(err), // Bad pointer parameter. - .IO => |err| return errnoBug(err), // sock_fd is not a file descriptor - .NODEV => return error.InterfaceNotFound, - else => |err| return posix.unexpectedErrno(err), + .SUCCESS => { + current_thread.endSyscall(); + return .{ .index = @bitCast(ifr.ifru.ivalue) }; + }, + .INTR => { + try current_thread.checkCancel(); + continue; + }, + else => |e| { + current_thread.endSyscall(); + switch (e) { + .CANCELED => return error.Canceled, + .INVAL => |err| return errnoBug(err), // Bad parameters. + .NOTTY => |err| return errnoBug(err), + .NXIO => |err| return errnoBug(err), + .BADF => |err| return errnoBug(err), // File descriptor used after closed. + .FAULT => |err| return errnoBug(err), // Bad pointer parameter. + .IO => |err| return errnoBug(err), // sock_fd is not a file descriptor + .NODEV => return error.InterfaceNotFound, + else => |err| return posix.unexpectedErrno(err), + } + }, } } } if (native_os == .windows) { - try t.checkCancel(); + try current_thread.checkCancel(); @panic("TODO implement netInterfaceNameResolve for Windows"); } if (builtin.link_libc) { - try t.checkCancel(); + try current_thread.checkCancel(); const index = std.c.if_nametoindex(&name.bytes); if (index == 0) return error.InterfaceNotFound; return .{ .index = @bitCast(index) }; @@ -4771,7 +5544,8 @@ fn netInterfaceNameResolveUnavailable( fn netInterfaceName(userdata: ?*anyopaque, interface: net.Interface) net.Interface.NameError!net.Interface.Name { const t: *Threaded = @ptrCast(@alignCast(userdata)); - try t.checkCancel(); + const current_thread = Thread.getCurrent(t); + try current_thread.checkCancel(); if (native_os == .linux) { _ = interface; @@ -4802,8 +5576,9 @@ fn netLookup( options: HostName.LookupOptions, ) void { const t: *Threaded = @ptrCast(@alignCast(userdata)); + const current_thread = Thread.getCurrent(t); const t_io = io(t); - resolved.putOneUncancelable(t_io, .{ .end = netLookupFallible(t, host_name, resolved, options) }); + resolved.putOneUncancelable(t_io, .{ .end = netLookupFallible(t, current_thread, host_name, resolved, options) }); } fn netLookupUnavailable( @@ -4821,6 +5596,7 @@ fn netLookupUnavailable( fn netLookupFallible( t: *Threaded, + current_thread: *Thread, host_name: HostName, resolved: *Io.Queue(HostName.LookupResult), options: HostName.LookupOptions, @@ -4866,7 +5642,7 @@ fn netLookupFallible( var res: *ws2_32.ADDRINFOEXW = undefined; const timeout: ?*ws2_32.timeval = null; while (true) { - try t.checkCancel(); // TODO make requestCancel call GetAddrInfoExCancel + try current_thread.checkCancel(); // TODO make requestCancel call GetAddrInfoExCancel // TODO make this append to the queue eagerly rather than blocking until // the whole thing finishes const rc: ws2_32.WinsockError = @enumFromInt(ws2_32.GetAddrInfoExW(name_w, port_w, .DNS, null, &hints, &res, timeout, null, null, cancel_handle)); @@ -5013,23 +5789,39 @@ fn netLookupFallible( .next = null, }; var res: ?*posix.addrinfo = null; + try current_thread.beginSyscall(); while (true) { - try t.checkCancel(); switch (posix.system.getaddrinfo(name_c.ptr, port_c.ptr, &hints, &res)) { - @as(posix.system.EAI, @enumFromInt(0)) => break, - .ADDRFAMILY => return error.AddressFamilyUnsupported, - .AGAIN => return error.NameServerFailure, - .FAIL => return error.NameServerFailure, - .FAMILY => return error.AddressFamilyUnsupported, - .MEMORY => return error.SystemResources, - .NODATA => return error.UnknownHostName, - .NONAME => return error.UnknownHostName, - .SYSTEM => switch (posix.errno(-1)) { - .INTR => continue, - .CANCELED => return error.Canceled, - else => |e| return posix.unexpectedErrno(e), + @as(posix.system.EAI, @enumFromInt(0)) => { + current_thread.endSyscall(); + break; + }, + .SYSTEM => switch (posix.errno(-1)) { + .INTR => { + try current_thread.checkCancel(); + continue; + }, + else => |e| { + current_thread.endSyscall(); + switch (e) { + .CANCELED => return error.Canceled, + else => |inner| return posix.unexpectedErrno(inner), + } + }, + }, + else => |e| { + current_thread.endSyscall(); + switch (e) { + .ADDRFAMILY => return error.AddressFamilyUnsupported, + .AGAIN => return error.NameServerFailure, + .FAIL => return error.NameServerFailure, + .FAMILY => return error.AddressFamilyUnsupported, + .MEMORY => return error.SystemResources, + .NODATA => return error.UnknownHostName, + .NONAME => return error.UnknownHostName, + else => return error.Unexpected, + } }, - else => return error.Unexpected, } } defer if (res) |some| posix.system.freeaddrinfo(some); @@ -5726,12 +6518,12 @@ fn copyCanon(canonical_name_buffer: *[HostName.max_len]u8, name: []const u8) Hos /// ulock_wait2() uses 64-bit nano-second timeouts (with the same convention) const darwin_supports_ulock_wait2 = builtin.os.version_range.semver.min.major >= 11; -fn futexWait(t: *Threaded, ptr: *const std.atomic.Value(u32), expect: u32) Io.Cancelable!void { +fn futexWait(current_thread: *Thread, ptr: *const std.atomic.Value(u32), expect: u32) Io.Cancelable!void { @branchHint(.cold); if (builtin.cpu.arch.isWasm()) { comptime assert(builtin.cpu.has(.wasm, .atomics)); - try t.checkCancel(); + try current_thread.checkCancel(); const timeout: i64 = -1; const signed_expect: i32 = @bitCast(expect); const result = asm volatile ( @@ -5754,8 +6546,9 @@ fn futexWait(t: *Threaded, ptr: *const std.atomic.Value(u32), expect: u32) Io.Ca } else switch (native_os) { .linux => { const linux = std.os.linux; - try t.checkCancel(); + try current_thread.beginSyscall(); const rc = linux.futex_4arg(ptr, .{ .cmd = .WAIT, .private = true }, expect, null); + current_thread.endSyscall(); if (is_debug) switch (linux.errno(rc)) { .SUCCESS => {}, // notified by `wake()` .INTR => {}, // gives caller a chance to check cancellation @@ -5772,11 +6565,12 @@ fn futexWait(t: *Threaded, ptr: *const std.atomic.Value(u32), expect: u32) Io.Ca .op = .COMPARE_AND_WAIT, .NO_ERRNO = true, }; - try t.checkCancel(); + try current_thread.beginSyscall(); const status = if (darwin_supports_ulock_wait2) c.__ulock_wait2(flags, ptr, expect, 0, 0) else c.__ulock_wait(flags, ptr, expect, 0); + current_thread.endSyscall(); if (status >= 0) return; @@ -5791,7 +6585,7 @@ fn futexWait(t: *Threaded, ptr: *const std.atomic.Value(u32), expect: u32) Io.Ca }; }, .windows => { - try t.checkCancel(); + try current_thread.checkCancel(); switch (windows.ntdll.RtlWaitOnAddress(ptr, &expect, @sizeOf(@TypeOf(expect)), null)) { .SUCCESS => {}, .CANCELLED => return error.Canceled, @@ -5800,8 +6594,9 @@ fn futexWait(t: *Threaded, ptr: *const std.atomic.Value(u32), expect: u32) Io.Ca }, .freebsd => { const flags = @intFromEnum(std.c.UMTX_OP.WAIT_UINT_PRIVATE); - try t.checkCancel(); + try current_thread.beginSyscall(); const rc = std.c._umtx_op(@intFromPtr(&ptr.raw), flags, @as(c_ulong, expect), 0, 0); + current_thread.endSyscall(); if (is_debug) switch (posix.errno(rc)) { .SUCCESS => {}, .FAULT => unreachable, // one of the args points to invalid memory @@ -6050,8 +6845,9 @@ const ResetEventFutex = enum(u32) { if (state == .unset) { state = @cmpxchgStrong(ResetEventFutex, ref, state, .waiting, .acquire, .acquire) orelse .waiting; } + const current_thread = Thread.getCurrent(t); while (state == .waiting) { - try futexWait(t, @ptrCast(ref), @intFromEnum(ResetEventFutex.waiting)); + try futexWait(current_thread, @ptrCast(ref), @intFromEnum(ResetEventFutex.waiting)); state = @atomicLoad(ResetEventFutex, ref, .acquire); } assert(state == .is_set); @@ -6140,6 +6936,7 @@ const ResetEventPosix = struct { .waiting => unreachable, // Invalid state. .is_set => return, }; + const current_thread = Thread.getCurrent(t); assert(std.c.pthread_mutex_lock(&rep.mutex) == .SUCCESS); defer assert(std.c.pthread_mutex_unlock(&rep.mutex) == .SUCCESS); sw: switch (rep.state) { @@ -6148,8 +6945,9 @@ const ResetEventPosix = struct { continue :sw .waiting; }, .waiting => { - try t.checkCancel(); + try current_thread.beginSyscall(); assert(std.c.pthread_cond_wait(&rep.cond, &rep.mutex) == .SUCCESS); + current_thread.endSyscall(); continue :sw rep.state; }, .is_set => return, @@ -6222,10 +7020,10 @@ const Wsa = struct { } || Io.UnexpectedError; }; -fn initializeWsa(t: *Threaded) error{NetworkDown}!void { +fn initializeWsa(t: *Threaded) error{ NetworkDown, Canceled }!void { const t_io = io(t); const wsa = &t.wsa; - wsa.mutex.lockUncancelable(t_io); + try wsa.mutex.lock(t_io); defer wsa.mutex.unlock(t_io); switch (wsa.status) { .uninitialized => { @@ -6237,12 +7035,15 @@ fn initializeWsa(t: *Threaded) error{NetworkDown}!void { wsa.status = .initialized; return; }, - else => |err_int| switch (@as(ws2_32.WinsockError, @enumFromInt(@as(u16, @intCast(err_int))))) { - .SYSNOTREADY => wsa.init_error = error.NetworkDown, - .VERNOTSUPPORTED => wsa.init_error = error.VersionUnsupported, - .EINPROGRESS => wsa.init_error = error.BlockingOperationInProgress, - .EPROCLIM => wsa.init_error = error.ProcessFdQuotaExceeded, - else => |err| wsa.init_error = windows.unexpectedWSAError(err), + else => |err_int| { + wsa.status = .failure; + wsa.init_error = switch (@as(ws2_32.WinsockError, @enumFromInt(@as(u16, @intCast(err_int))))) { + .SYSNOTREADY => error.NetworkDown, + .VERNOTSUPPORTED => error.VersionUnsupported, + .EINPROGRESS => error.BlockingOperationInProgress, + .EPROCLIM => error.ProcessFdQuotaExceeded, + else => |err| windows.unexpectedWSAError(err), + }; }, } }, From d60760d61e19018aa354fbb2f241444db88ca51b Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 28 Nov 2025 18:07:42 -0800 Subject: [PATCH 02/14] std.Io.Threaded: tune requestCancel On a heavily loaded Linux 6.17.5, I observed a maximum of 20 attempts not acknowledged before the timeout (including exponential backoff) was sufficient, despite the heavy load. The time wasted here sleeping is mitigated by the fact that, later on, the system will likely wait for the canceled task, causing it to indefinitely yield until the canceled task finishes, and the task must acknowledge the cancel before it proceeds to that point. --- lib/std/Io/Threaded.zig | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index ee26c6153d..e258d62757 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -201,8 +201,22 @@ const Closure = struct { // We can send a signal to interrupt the syscall, but if it arrives before // the syscall instruction, it will be missed. Therefore, this code tries // again until the cancellation request is acknowledged. - const max_attempts = 3; - for (0..max_attempts) |_| { + + // 1 << 10 ns is about 1 microsecond, approximately syscall overhead. + // 1 << 20 ns is about 1 millisecond. + // 1 << 30 ns is about 1 second. + // + // On a heavily loaded Linux 6.17.5, I observed a maximum of 20 + // attempts not acknowledged before the timeout (including exponential + // backoff) was sufficient, despite the heavy load. + // + // The time wasted here sleeping is mitigated by the fact that, later + // on, the system will likely wait for the canceled task, causing it + // to indefinitely yield until the canceled task finishes, and the + // task must acknowledge the cancel before it proceeds to that point. + const max_attempts = 22; + + for (0..max_attempts) |attempt_index| { if (std.Thread.use_pthreads) { const rc = std.c.pthread_kill(signal_id, .IO); if (is_debug) assert(rc == 0); @@ -219,11 +233,14 @@ const Closure = struct { return; } - // TODO make this a nanosleep with 1 << attempt duration - std.Thread.yield() catch {}; + var timespec: posix.timespec = .{ + .sec = 0, + .nsec = @as(isize, 1) << @intCast(attempt_index), + }; + _ = posix.system.nanosleep(×pec, ×pec); switch (@atomicRmw(CancelStatus, &closure.cancel_status, .Xchg, .requested, .monotonic).unpack()) { - .requested => continue, + .requested => continue, // Retry needed in case other thread hasn't yet entered the syscall. .none, .acknowledged => return, .signal_id => |new_signal_id| signal_id = new_signal_id, } From 39ac40209b57d105dff28988a954a594e2e9dd8f Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sat, 29 Nov 2025 07:36:44 -0800 Subject: [PATCH 03/14] std.Io.Threaded: use musl's beautiful pthread_cancel semantics --- lib/std/Io/Threaded.zig | 186 ++++++++++++++++++++++------------------ lib/std/c.zig | 17 ++++ lib/std/posix.zig | 1 + 3 files changed, 121 insertions(+), 83 deletions(-) diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index e258d62757..b093bfd127 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -3,6 +3,7 @@ const Threaded = @This(); const builtin = @import("builtin"); const native_os = builtin.os.tag; const is_windows = native_os == .windows; +const is_musl = builtin.link_libc and builtin.abi.isMusl(); const windows = std.os.windows; const ws2_32 = std.os.windows.ws2_32; const is_debug = builtin.mode == .Debug; @@ -111,7 +112,7 @@ const Thread = struct { @atomicStore(CancelStatus, &closure.cancel_status, .acknowledged, .release); return error.Canceled; }, - .acknowledged => unreachable, + .acknowledged => return, _ => unreachable, } } @@ -128,6 +129,13 @@ const Thread = struct { ); } + fn endSyscallCanceled(thread: *Thread) Io.Cancelable { + if (thread.current_closure) |closure| { + @atomicStore(CancelStatus, &closure.cancel_status, .acknowledged, .release); + } + return error.Canceled; + } + fn currentSignalId() SignalId { return if (std.Thread.use_pthreads) std.c.pthread_self() else std.Thread.getCurrentId(); } @@ -197,6 +205,13 @@ const Closure = struct { .none, .acknowledged, .requested => return, .signal_id => |signal_id| signal_id, }; + // Musl has an undocumented extension that makes pthread_cancel have the useful, desired + // behavior of causing the next syscall to return ECANCELED. + if (is_musl) { + _ = std.c.pthread_cancel(signal_id); + return; + } + // The task will enter a blocking syscall before checking for cancellation again. // We can send a signal to interrupt the syscall, but if it arrives before // the syscall instruction, it will be missed. Therefore, this code tries @@ -218,8 +233,7 @@ const Closure = struct { for (0..max_attempts) |attempt_index| { if (std.Thread.use_pthreads) { - const rc = std.c.pthread_kill(signal_id, .IO); - if (is_debug) assert(rc == 0); + if (std.c.pthread_kill(signal_id, .IO) != 0) return; } else if (native_os == .linux) { const pid: posix.pid_t = p: { const cached_pid = @atomicLoad(Pid, &t.pid, .monotonic); @@ -228,7 +242,7 @@ const Closure = struct { @atomicStore(Pid, &t.pid, @enumFromInt(pid), .monotonic); break :p pid; }; - _ = std.os.linux.tgkill(pid, @bitCast(signal_id), .IO); + if (std.os.linux.tgkill(pid, @bitCast(signal_id), .IO) != 0) return; } else { return; } @@ -354,6 +368,11 @@ fn worker(t: *Threaded) void { while (true) { while (t.run_queue.popFirst()) |closure_node| { t.mutex.unlock(); + + // Musl has an undocumented extension that makes pthread_cancel have the useful, desired + // behavior of causing the next syscall to return ECANCELED. + if (is_musl) assert(std.c.pthread_setcancelstate(.MASKED, null) == .SUCCESS); + const closure: *Closure = @fieldParentPtr("node", closure_node); closure.start(closure, t); t.mutex.lock(); @@ -1198,10 +1217,10 @@ fn dirMakePosix(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, mode: try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { - .CANCELED => return error.Canceled, .ACCES => return error.AccessDenied, .BADF => |err| return errnoBug(err), // File descriptor used after closed. .PERM => return error.PermissionDenied, @@ -1241,25 +1260,29 @@ fn dirMakeWasi(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, mode: I try current_thread.checkCancel(); continue; }, - .CANCELED => return error.Canceled, - - .ACCES => return error.AccessDenied, - .BADF => |err| return errnoBug(err), // File descriptor used after closed. - .PERM => return error.PermissionDenied, - .DQUOT => return error.DiskQuota, - .EXIST => return error.PathAlreadyExists, - .FAULT => |err| return errnoBug(err), - .LOOP => return error.SymLinkLoop, - .MLINK => return error.LinkQuotaExceeded, - .NAMETOOLONG => return error.NameTooLong, - .NOENT => return error.FileNotFound, - .NOMEM => return error.SystemResources, - .NOSPC => return error.NoSpaceLeft, - .NOTDIR => return error.NotDir, - .ROFS => return error.ReadOnlyFileSystem, - .NOTCAPABLE => return error.AccessDenied, - .ILSEQ => return error.BadPathName, - else => |err| return posix.unexpectedErrno(err), + .CANCELED => return current_thread.endSyscallCanceled(), + else => |e| { + current_thread.endSyscall(); + switch (e) { + .ACCES => return error.AccessDenied, + .BADF => |err| return errnoBug(err), // File descriptor used after closed. + .PERM => return error.PermissionDenied, + .DQUOT => return error.DiskQuota, + .EXIST => return error.PathAlreadyExists, + .FAULT => |err| return errnoBug(err), + .LOOP => return error.SymLinkLoop, + .MLINK => return error.LinkQuotaExceeded, + .NAMETOOLONG => return error.NameTooLong, + .NOENT => return error.FileNotFound, + .NOMEM => return error.SystemResources, + .NOSPC => return error.NoSpaceLeft, + .NOTDIR => return error.NotDir, + .ROFS => return error.ReadOnlyFileSystem, + .NOTCAPABLE => return error.AccessDenied, + .ILSEQ => return error.BadPathName, + else => |err| return posix.unexpectedErrno(err), + } + }, } } } @@ -1503,10 +1526,10 @@ fn dirStatPathLinux( try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { - .CANCELED => return error.Canceled, .ACCES => return error.AccessDenied, .BADF => |err| return errnoBug(err), // File descriptor used after closed. .FAULT => |err| return errnoBug(err), @@ -1549,10 +1572,10 @@ fn dirStatPathPosix( try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { - .CANCELED => return error.Canceled, .INVAL => |err| return errnoBug(err), .BADF => |err| return errnoBug(err), // File descriptor used after closed. .NOMEM => return error.SystemResources, @@ -1610,10 +1633,10 @@ fn dirStatPathWasi( try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { - .CANCELED => return error.Canceled, .INVAL => |err| return errnoBug(err), .BADF => |err| return errnoBug(err), // File descriptor used after closed. .NOMEM => return error.SystemResources, @@ -1656,10 +1679,10 @@ fn fileStatPosix(userdata: ?*anyopaque, file: Io.File) Io.File.StatError!Io.File try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { - .CANCELED => return error.Canceled, .INVAL => |err| return errnoBug(err), .BADF => |err| return errnoBug(err), // File descriptor used after closed. .NOMEM => return error.SystemResources, @@ -1695,10 +1718,10 @@ fn fileStatLinux(userdata: ?*anyopaque, file: Io.File) Io.File.StatError!Io.File try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { - .CANCELED => return error.Canceled, .ACCES => |err| return errnoBug(err), .BADF => |err| return errnoBug(err), // File descriptor used after closed. .FAULT => |err| return errnoBug(err), @@ -1781,10 +1804,10 @@ fn fileStatWasi(userdata: ?*anyopaque, file: Io.File) Io.File.StatError!Io.File. try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { - .CANCELED => return error.Canceled, .INVAL => |err| return errnoBug(err), .BADF => |err| return errnoBug(err), // File descriptor used after closed. .NOMEM => return error.SystemResources, @@ -1833,10 +1856,10 @@ fn dirAccessPosix( try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { - .CANCELED => return error.Canceled, .ACCES => return error.AccessDenied, .PERM => return error.PermissionDenied, .ROFS => return error.ReadOnlyFileSystem, @@ -1883,10 +1906,10 @@ fn dirAccessWasi( try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { - .CANCELED => return error.Canceled, .INVAL => |err| return errnoBug(err), .BADF => |err| return errnoBug(err), // File descriptor used after closed. .NOMEM => return error.SystemResources, @@ -2030,10 +2053,10 @@ fn dirCreateFilePosix( try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { - .CANCELED => return error.Canceled, .FAULT => |err| return errnoBug(err), .INVAL => return error.BadPathName, .BADF => |err| return errnoBug(err), // File descriptor used after closed. @@ -2085,10 +2108,10 @@ fn dirCreateFilePosix( try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { - .CANCELED => return error.Canceled, .BADF => |err| return errnoBug(err), // File descriptor used after closed. .INVAL => |err| return errnoBug(err), // invalid parameters .NOLCK => return error.SystemResources, @@ -2240,10 +2263,10 @@ fn dirCreateFileWasi( try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { - .CANCELED => return error.Canceled, .FAULT => |err| return errnoBug(err), .INVAL => return error.BadPathName, .BADF => |err| return errnoBug(err), // File descriptor used after closed. @@ -2334,10 +2357,10 @@ fn dirOpenFilePosix( try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { - .CANCELED => return error.Canceled, .FAULT => |err| return errnoBug(err), .INVAL => return error.BadPathName, .BADF => |err| return errnoBug(err), // File descriptor used after closed. @@ -2388,10 +2411,10 @@ fn dirOpenFilePosix( try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { - .CANCELED => return error.Canceled, .BADF => |err| return errnoBug(err), // File descriptor used after closed. .INVAL => |err| return errnoBug(err), // invalid parameters .NOLCK => return error.SystemResources, @@ -2417,6 +2440,7 @@ fn dirOpenFilePosix( try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |err| { current_thread.endSyscall(); return posix.unexpectedErrno(err); @@ -2437,6 +2461,7 @@ fn dirOpenFilePosix( try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |err| { current_thread.endSyscall(); return posix.unexpectedErrno(err); @@ -2635,10 +2660,10 @@ fn dirOpenFileWasi( try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { - .CANCELED => return error.Canceled, .FAULT => |err| return errnoBug(err), .BADF => |err| return errnoBug(err), // File descriptor used after closed. .ACCES => return error.AccessDenied, @@ -2719,10 +2744,10 @@ fn dirOpenDirPosix( try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { - .CANCELED => return error.Canceled, .FAULT => |err| return errnoBug(err), .INVAL => return error.BadPathName, .BADF => |err| return errnoBug(err), // File descriptor used after closed. @@ -2772,10 +2797,10 @@ fn dirOpenDirHaiku( try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { - .CANCELED => return error.Canceled, .FAULT => |err| return errnoBug(err), .INVAL => |err| return errnoBug(err), .BADF => |err| return errnoBug(err), // File descriptor used after closed. @@ -2917,10 +2942,10 @@ fn dirOpenDirWasi( try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { - .CANCELED => return error.Canceled, .FAULT => |err| return errnoBug(err), .INVAL => return error.BadPathName, .BADF => |err| return errnoBug(err), // File descriptor used after closed. @@ -2984,10 +3009,10 @@ fn fileReadStreamingPosix(userdata: ?*anyopaque, file: Io.File, data: [][]u8) Io try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { - .CANCELED => return error.Canceled, .INVAL => |err| return errnoBug(err), .FAULT => |err| return errnoBug(err), .BADF => return error.NotOpenForReading, // File operation on directory. @@ -3018,10 +3043,10 @@ fn fileReadStreamingPosix(userdata: ?*anyopaque, file: Io.File, data: [][]u8) Io try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { - .CANCELED => return error.Canceled, .INVAL => |err| return errnoBug(err), .FAULT => |err| return errnoBug(err), .SRCH => return error.ProcessNotFound, @@ -3104,10 +3129,10 @@ fn fileReadPositionalPosix(userdata: ?*anyopaque, file: Io.File, data: [][]u8, o try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { - .CANCELED => return error.Canceled, .INVAL => |err| return errnoBug(err), .FAULT => |err| return errnoBug(err), .AGAIN => |err| return errnoBug(err), @@ -3142,10 +3167,10 @@ fn fileReadPositionalPosix(userdata: ?*anyopaque, file: Io.File, data: [][]u8, o try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { - .CANCELED => return error.Canceled, .INVAL => |err| return errnoBug(err), .FAULT => |err| return errnoBug(err), .SRCH => return error.ProcessNotFound, @@ -3244,10 +3269,10 @@ fn fileSeekTo(userdata: ?*anyopaque, file: Io.File, offset: u64) Io.File.SeekErr try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { - .CANCELED => return error.Canceled, .BADF => |err| return errnoBug(err), // File descriptor used after closed. .INVAL => return error.Unseekable, .OVERFLOW => return error.Unseekable, @@ -3277,10 +3302,10 @@ fn fileSeekTo(userdata: ?*anyopaque, file: Io.File, offset: u64) Io.File.SeekErr try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { - .CANCELED => return error.Canceled, .BADF => |err| return errnoBug(err), // File descriptor used after closed. .INVAL => return error.Unseekable, .OVERFLOW => return error.Unseekable, @@ -3306,10 +3331,10 @@ fn fileSeekTo(userdata: ?*anyopaque, file: Io.File, offset: u64) Io.File.SeekErr try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { - .CANCELED => return error.Canceled, .BADF => |err| return errnoBug(err), // File descriptor used after closed. .INVAL => return error.Unseekable, .OVERFLOW => return error.Unseekable, @@ -3430,7 +3455,8 @@ fn nowWasi(userdata: ?*anyopaque, clock: Io.Clock) Io.Clock.Error!Io.Timestamp { const sleep = switch (native_os) { .windows => sleepWindows, .wasi => sleepWasi, - .linux => sleepLinux, + // Since we use musl's pthread_cancel, it's important that all the syscalls go through libc. + .linux => if (is_musl) sleepPosix else sleepLinux, else => sleepPosix, }; @@ -3462,10 +3488,10 @@ fn sleepLinux(userdata: ?*anyopaque, timeout: Io.Timeout) Io.SleepError!void { try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { - .CANCELED => return error.Canceled, .INVAL => return error.UnsupportedClock, else => |err| return posix.unexpectedErrno(err), } @@ -3540,11 +3566,9 @@ fn sleepPosix(userdata: ?*anyopaque, timeout: Io.Timeout) Io.SleepError!void { try current_thread.checkCancel(); continue; }, - else => { - // This prong handles success as well as unexpected errors. - current_thread.endSyscall(); - return; - }, + .CANCELED => return current_thread.endSyscallCanceled(), + // This prong handles success as well as unexpected errors. + else => return current_thread.endSyscall(), } } } @@ -3616,10 +3640,10 @@ fn netListenIpPosix( try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { - .CANCELED => return error.Canceled, .ADDRINUSE => return error.AddressInUse, .BADF => |err| return errnoBug(err), // File descriptor used after closed. else => |err| return posix.unexpectedErrno(err), @@ -3777,10 +3801,10 @@ fn netListenUnixPosix( try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { - .CANCELED => return error.Canceled, .ADDRINUSE => return error.AddressInUse, .BADF => |err| return errnoBug(err), // File descriptor used after closed. else => |err| return posix.unexpectedErrno(err), @@ -3901,10 +3925,10 @@ fn posixBindUnix( try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { - .CANCELED => return error.Canceled, .ACCES => return error.AccessDenied, .ADDRINUSE => return error.AddressInUse, .AFNOSUPPORT => return error.AddressFamilyUnsupported, @@ -3946,10 +3970,10 @@ fn posixBind( try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { - .CANCELED => return error.Canceled, .ADDRINUSE => return error.AddressInUse, .BADF => |err| return errnoBug(err), // File descriptor used after closed. .INVAL => |err| return errnoBug(err), // invalid parameters @@ -3982,10 +4006,10 @@ fn posixConnect( try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { - .CANCELED => return error.Canceled, .ADDRNOTAVAIL => return error.AddressUnavailable, .AFNOSUPPORT => return error.AddressFamilyUnsupported, .AGAIN, .INPROGRESS => return error.WouldBlock, @@ -4029,10 +4053,10 @@ fn posixConnectUnix( try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { - .CANCELED => return error.Canceled, .AFNOSUPPORT => return error.AddressFamilyUnsupported, .AGAIN => return error.WouldBlock, .INPROGRESS => return error.WouldBlock, @@ -4074,10 +4098,10 @@ fn posixGetSockName( try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { - .CANCELED => return error.Canceled, .BADF => |err| return errnoBug(err), // File descriptor used after closed. .FAULT => |err| return errnoBug(err), .INVAL => |err| return errnoBug(err), // invalid parameters @@ -4142,10 +4166,10 @@ fn setSocketOption(current_thread: *Thread, fd: posix.fd_t, level: i32, opt_name try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { - .CANCELED => return error.Canceled, .BADF => |err| return errnoBug(err), // File descriptor used after closed. .NOTSOCK => |err| return errnoBug(err), .INVAL => |err| return errnoBug(err), @@ -4472,12 +4496,10 @@ fn openSocketPosix( switch (posix.errno(posix.system.fcntl(fd, posix.F.SETFD, @as(usize, posix.FD_CLOEXEC)))) { .SUCCESS => break, .INTR => continue, - else => |e| { + .CANCELED => return current_thread.endSyscallCanceled(), + else => |err| { current_thread.endSyscall(); - switch (e) { - .CANCELED => return error.Canceled, - else => |err| return posix.unexpectedErrno(err), - } + return posix.unexpectedErrno(err); }, } }; @@ -4488,10 +4510,10 @@ fn openSocketPosix( try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { - .CANCELED => return error.Canceled, .AFNOSUPPORT => return error.AddressFamilyUnsupported, .INVAL => return error.ProtocolUnsupportedBySystem, .MFILE => return error.ProcessFdQuotaExceeded, @@ -4590,10 +4612,10 @@ fn netAcceptPosix(userdata: ?*anyopaque, listen_fd: net.Socket.Handle) net.Serve try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { - .CANCELED => return error.Canceled, .AGAIN => |err| return errnoBug(err), .BADF => |err| return errnoBug(err), // File descriptor used after closed. .CONNABORTED => return error.ConnectionAborted, @@ -4699,10 +4721,10 @@ fn netReadPosix(userdata: ?*anyopaque, fd: net.Socket.Handle, data: [][]u8) net. try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { - .CANCELED => return error.Canceled, .INVAL => |err| return errnoBug(err), .FAULT => |err| return errnoBug(err), .AGAIN => |err| return errnoBug(err), @@ -4732,10 +4754,10 @@ fn netReadPosix(userdata: ?*anyopaque, fd: net.Socket.Handle, data: [][]u8) net. try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { - .CANCELED => return error.Canceled, .INVAL => |err| return errnoBug(err), .FAULT => |err| return errnoBug(err), .AGAIN => |err| return errnoBug(err), @@ -4968,10 +4990,10 @@ fn netSendOne( try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { - .CANCELED => return error.Canceled, .ACCES => return error.AccessDenied, .ALREADY => return error.FastOpenAlreadyInProgress, .BADF => |err| return errnoBug(err), // File descriptor used after closed. @@ -5045,10 +5067,10 @@ fn netSendMany( try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { - .CANCELED => return error.Canceled, .AGAIN => |err| return errnoBug(err), .ALREADY => return error.FastOpenAlreadyInProgress, .BADF => |err| return errnoBug(err), // File descriptor used after closed. @@ -5178,7 +5200,7 @@ fn netReceivePosix( continue :recv; }, .INTR => continue, - .CANCELED => return .{ error.Canceled, message_i }, + .CANCELED => return .{ current_thread.endSyscallCanceled(), message_i }, .FAULT => |err| return .{ errnoBug(err), message_i }, .INVAL => |err| return .{ errnoBug(err), message_i }, @@ -5187,7 +5209,7 @@ fn netReceivePosix( } }, .INTR => continue, - .CANCELED => return .{ error.Canceled, message_i }, + .CANCELED => return .{ current_thread.endSyscallCanceled(), message_i }, .BADF => |err| return .{ errnoBug(err), message_i }, .NFILE => return .{ error.SystemFdQuotaExceeded, message_i }, @@ -5306,10 +5328,10 @@ fn netWritePosix( try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { - .CANCELED => return error.Canceled, .ACCES => |err| return errnoBug(err), .AGAIN => |err| return errnoBug(err), .ALREADY => return error.FastOpenAlreadyInProgress, @@ -5405,7 +5427,7 @@ fn netWriteWindows( }; switch (wsa_error) { .EINTR => continue, - .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled, + .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return current_thread.endSyscallCanceled(), .NOTINITIALISED => { try initializeWsa(t); continue; @@ -5517,10 +5539,10 @@ fn netInterfaceNameResolve( try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { - .CANCELED => return error.Canceled, .INVAL => |err| return errnoBug(err), // Bad parameters. .NOTTY => |err| return errnoBug(err), .NXIO => |err| return errnoBug(err), @@ -5818,12 +5840,10 @@ fn netLookupFallible( try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); - switch (e) { - .CANCELED => return error.Canceled, - else => |inner| return posix.unexpectedErrno(inner), - } + return posix.unexpectedErrno(e); }, }, else => |e| { diff --git a/lib/std/c.zig b/lib/std/c.zig index f22112a086..886c5e902c 100644 --- a/lib/std/c.zig +++ b/lib/std/c.zig @@ -10881,6 +10881,23 @@ pub extern "c" fn pthread_create( start_routine: *const fn (?*anyopaque) callconv(.c) ?*anyopaque, noalias arg: ?*anyopaque, ) E; +pub const pthread_cancelstate = switch (native_os) { + .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => enum(c_int) { + ENABLE = 1, + DISABLE = 0, + }, + .linux => if (native_abi.isMusl()) enum(c_int) { + ENABLE = 0, + DISABLE = 1, + MASKED = 2, + } else if (native_abi.isGnu()) enum(c_int) { + ENABLE = 0, + DISABLE = 1, + }, + else => void, +}; +pub extern "c" fn pthread_setcancelstate(pthread_cancelstate, ?*pthread_cancelstate) E; +pub extern "c" fn pthread_cancel(pthread_t) E; pub extern "c" fn pthread_attr_init(attr: *pthread_attr_t) E; pub extern "c" fn pthread_attr_setstack(attr: *pthread_attr_t, stackaddr: *anyopaque, stacksize: usize) E; pub extern "c" fn pthread_attr_setstacksize(attr: *pthread_attr_t, stacksize: usize) E; diff --git a/lib/std/posix.zig b/lib/std/posix.zig index 3e80466cd3..4176910537 100644 --- a/lib/std/posix.zig +++ b/lib/std/posix.zig @@ -1360,6 +1360,7 @@ pub fn writev(fd: fd_t, iov: []const iovec_const) WriteError!usize { .PIPE => return error.BrokenPipe, .CONNRESET => return error.ConnectionResetByPeer, .BUSY => return error.DeviceBusy, + .CANCELED => return error.Canceled, else => |err| return unexpectedErrno(err), } } From 9e981c3ae517738eacc0456632651aae5626d109 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sat, 29 Nov 2025 07:38:33 -0800 Subject: [PATCH 04/14] std.os.linux: delete unnecessary `@compileError` Without this, it already fails to compile with a sufficiently helpful error message. --- lib/std/os/linux.zig | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/std/os/linux.zig b/lib/std/os/linux.zig index 2029356a66..a41ead5485 100644 --- a/lib/std/os/linux.zig +++ b/lib/std/os/linux.zig @@ -1748,9 +1748,7 @@ pub fn settimeofday(tv: *const timeval, tz: *const timezone) usize { } pub fn nanosleep(req: *const timespec, rem: ?*timespec) usize { - if (native_arch == .riscv32) { - @compileError("No nanosleep syscall on this architecture."); - } else return syscall2(.nanosleep, @intFromPtr(req), @intFromPtr(rem)); + return syscall2(.nanosleep, @intFromPtr(req), @intFromPtr(rem)); } pub fn pause() usize { From 144206856e26300cc814b3c894527b062db0e69f Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sat, 29 Nov 2025 07:52:32 -0800 Subject: [PATCH 05/14] std.Io.Threaded: fix compilation for riscv32-linux --- lib/std/Io/Threaded.zig | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index b093bfd127..03670a9eb7 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -251,7 +251,11 @@ const Closure = struct { .sec = 0, .nsec = @as(isize, 1) << @intCast(attempt_index), }; - _ = posix.system.nanosleep(×pec, ×pec); + if (native_os == .linux) { + _ = std.os.linux.clock_nanosleep(posix.CLOCK.MONOTONIC, .{ .ABSTIME = false }, ×pec, ×pec); + } else { + _ = posix.system.nanosleep(×pec, ×pec); + } switch (@atomicRmw(CancelStatus, &closure.cancel_status, .Xchg, .requested, .monotonic).unpack()) { .requested => continue, // Retry needed in case other thread hasn't yet entered the syscall. From de87bad4c3240ab34ee81023e6df1ba94d6d5e6a Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sat, 29 Nov 2025 08:43:57 -0800 Subject: [PATCH 06/14] std.Io.Threaded: don't solve the cancel race after all Unfortunately, trying again until the cancellation request is acknowledged has been observed to incur a large amount of overhead, and usually strong cancellation guarantees are not needed, so the race condition is not handled here. Users who want to avoid this have this menu of options instead: * Use no libc, in which case Zig std lib can avoid the race (tracking issue: https://codeberg.org/ziglang/zig/issues/30049) * Use musl libc * Use `std.Io.Evented`. But this is not implemented yet. Tracked by - https://codeberg.org/ziglang/zig/issues/30050 - https://codeberg.org/ziglang/zig/issues/30051 glibc + threaded is the only problematic combination. --- lib/std/Io/Threaded.zig | 76 +++++++++++++++-------------------------- 1 file changed, 27 insertions(+), 49 deletions(-) diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 03670a9eb7..f285a51d6c 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -201,7 +201,7 @@ const Closure = struct { const Start = *const fn (*Closure, *Threaded) void; fn requestCancel(closure: *Closure, t: *Threaded) void { - var signal_id = switch (@atomicRmw(CancelStatus, &closure.cancel_status, .Xchg, .requested, .monotonic).unpack()) { + const signal_id = switch (@atomicRmw(CancelStatus, &closure.cancel_status, .Xchg, .requested, .monotonic).unpack()) { .none, .acknowledged, .requested => return, .signal_id => |signal_id| signal_id, }; @@ -214,54 +214,32 @@ const Closure = struct { // The task will enter a blocking syscall before checking for cancellation again. // We can send a signal to interrupt the syscall, but if it arrives before - // the syscall instruction, it will be missed. Therefore, this code tries - // again until the cancellation request is acknowledged. - - // 1 << 10 ns is about 1 microsecond, approximately syscall overhead. - // 1 << 20 ns is about 1 millisecond. - // 1 << 30 ns is about 1 second. + // the syscall instruction, it will be missed. // - // On a heavily loaded Linux 6.17.5, I observed a maximum of 20 - // attempts not acknowledged before the timeout (including exponential - // backoff) was sufficient, despite the heavy load. - // - // The time wasted here sleeping is mitigated by the fact that, later - // on, the system will likely wait for the canceled task, causing it - // to indefinitely yield until the canceled task finishes, and the - // task must acknowledge the cancel before it proceeds to that point. - const max_attempts = 22; - - for (0..max_attempts) |attempt_index| { - if (std.Thread.use_pthreads) { - if (std.c.pthread_kill(signal_id, .IO) != 0) return; - } else if (native_os == .linux) { - const pid: posix.pid_t = p: { - const cached_pid = @atomicLoad(Pid, &t.pid, .monotonic); - if (cached_pid != .unknown) break :p @intFromEnum(cached_pid); - const pid = std.os.linux.getpid(); - @atomicStore(Pid, &t.pid, @enumFromInt(pid), .monotonic); - break :p pid; - }; - if (std.os.linux.tgkill(pid, @bitCast(signal_id), .IO) != 0) return; - } else { - return; - } - - var timespec: posix.timespec = .{ - .sec = 0, - .nsec = @as(isize, 1) << @intCast(attempt_index), + // Unfortunately, trying again until the cancellation request is + // acknowledged has been observed to incur a large amount of overhead, + // and usually strong cancellation guarantees are not needed, so the + // race condition is not handled here. Users who want to avoid this + // have this menu of options instead: + // * Use no libc, in which case Zig std lib can avoid the race (tracking + // issue: https://codeberg.org/ziglang/zig/issues/30049) + // * Use musl libc instead of glibc + // * Use `std.Io.Evented`. But this is not implemented yet. Tracked by + // - https://codeberg.org/ziglang/zig/issues/30050 + // - https://codeberg.org/ziglang/zig/issues/30051 + if (std.Thread.use_pthreads) { + if (std.c.pthread_kill(signal_id, .IO) != 0) return; + } else if (native_os == .linux) { + const pid: posix.pid_t = p: { + const cached_pid = @atomicLoad(Pid, &t.pid, .monotonic); + if (cached_pid != .unknown) break :p @intFromEnum(cached_pid); + const pid = std.os.linux.getpid(); + @atomicStore(Pid, &t.pid, @enumFromInt(pid), .monotonic); + break :p pid; }; - if (native_os == .linux) { - _ = std.os.linux.clock_nanosleep(posix.CLOCK.MONOTONIC, .{ .ABSTIME = false }, ×pec, ×pec); - } else { - _ = posix.system.nanosleep(×pec, ×pec); - } - - switch (@atomicRmw(CancelStatus, &closure.cancel_status, .Xchg, .requested, .monotonic).unpack()) { - .requested => continue, // Retry needed in case other thread hasn't yet entered the syscall. - .none, .acknowledged => return, - .signal_id => |new_signal_id| signal_id = new_signal_id, - } + if (std.os.linux.tgkill(pid, @bitCast(signal_id), .IO) != 0) return; + } else { + return; } } }; @@ -303,7 +281,7 @@ pub fn init( .mask = posix.sigemptyset(), .flags = 0, }; - if (have_sig_io) posix.sigaction(.IO, &act, &t.old_sig_io); + if (!is_musl and have_sig_io) posix.sigaction(.IO, &act, &t.old_sig_io); if (have_sig_pipe) posix.sigaction(.PIPE, &act, &t.old_sig_pipe); t.have_signal_handler = true; } @@ -341,7 +319,7 @@ pub fn deinit(t: *Threaded) void { if (ws2_32.WSACleanup() != 0) recoverableOsBugDetected(); } if (posix.Sigaction != void and t.have_signal_handler) { - if (have_sig_io) posix.sigaction(.IO, &t.old_sig_io, null); + if (!is_musl and have_sig_io) posix.sigaction(.IO, &t.old_sig_io, null); if (have_sig_pipe) posix.sigaction(.PIPE, &t.old_sig_pipe, null); } t.* = undefined; From c4f5dda135616207c24a6009e221724f5a6fa6aa Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sat, 29 Nov 2025 08:58:50 -0800 Subject: [PATCH 07/14] std.Io.Threaded: re-introduce retry logic behind config --- lib/std/Io/Threaded.zig | 94 ++++++++++++++++++++++++++++++----------- 1 file changed, 69 insertions(+), 25 deletions(-) diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index f285a51d6c..9c818e1794 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -53,6 +53,22 @@ cpu_count_error: ?std.Thread.CpuCountError, busy_count: usize = 0, main_thread: Thread, pid: Pid = .unknown, +/// When a cancel request is made, blocking syscalls can be unblocked by +/// issuing a signal. However, if the signal arrives after the check and before +/// the syscall instruction, it is missed. +/// +/// This option solves the race condition by retrying the signal delivery +/// until it is acknowledged, with an exponential backoff. +/// +/// Unfortunately, trying again until the cancellation request is acknowledged +/// has been observed to be relatively slow, and usually strong cancellation +/// guarantees are not needed, so this defaults to off. +/// +/// Musl libc does not have this problem because of a clever, undocumented +/// extension related to pthread_cancel, which this code integrates with. +/// When compiling with no libc, `Threaded` does not yet implement the +/// equivalent trick (tracked by https://codeberg.org/ziglang/zig/issues/30049). +robust_cancel: RobustCancel = if (is_musl) .enabled else .disabled, wsa: if (is_windows) Wsa else struct {} = .{}, @@ -60,6 +76,15 @@ have_signal_handler: bool, old_sig_io: if (have_sig_io) posix.Sigaction else void, old_sig_pipe: if (have_sig_pipe) posix.Sigaction else void, +pub const RobustCancel = if (is_musl) enum { + enabled, +} else if (std.Thread.use_pthreads or native_os == .linux) enum { + enabled, + disabled, +} else enum { + disabled, +}; + pub const Pid = if (native_os == .linux) enum(posix.pid_t) { unknown = 0, _, @@ -201,7 +226,7 @@ const Closure = struct { const Start = *const fn (*Closure, *Threaded) void; fn requestCancel(closure: *Closure, t: *Threaded) void { - const signal_id = switch (@atomicRmw(CancelStatus, &closure.cancel_status, .Xchg, .requested, .monotonic).unpack()) { + var signal_id = switch (@atomicRmw(CancelStatus, &closure.cancel_status, .Xchg, .requested, .monotonic).unpack()) { .none, .acknowledged, .requested => return, .signal_id => |signal_id| signal_id, }; @@ -214,32 +239,51 @@ const Closure = struct { // The task will enter a blocking syscall before checking for cancellation again. // We can send a signal to interrupt the syscall, but if it arrives before - // the syscall instruction, it will be missed. + // the syscall instruction, it will be missed. Therefore, this code tries + // again until the cancellation request is acknowledged. + + // 1 << 10 ns is about 1 microsecond, approximately syscall overhead. + // 1 << 20 ns is about 1 millisecond. + // 1 << 30 ns is about 1 second. // - // Unfortunately, trying again until the cancellation request is - // acknowledged has been observed to incur a large amount of overhead, - // and usually strong cancellation guarantees are not needed, so the - // race condition is not handled here. Users who want to avoid this - // have this menu of options instead: - // * Use no libc, in which case Zig std lib can avoid the race (tracking - // issue: https://codeberg.org/ziglang/zig/issues/30049) - // * Use musl libc instead of glibc - // * Use `std.Io.Evented`. But this is not implemented yet. Tracked by - // - https://codeberg.org/ziglang/zig/issues/30050 - // - https://codeberg.org/ziglang/zig/issues/30051 - if (std.Thread.use_pthreads) { - if (std.c.pthread_kill(signal_id, .IO) != 0) return; - } else if (native_os == .linux) { - const pid: posix.pid_t = p: { - const cached_pid = @atomicLoad(Pid, &t.pid, .monotonic); - if (cached_pid != .unknown) break :p @intFromEnum(cached_pid); - const pid = std.os.linux.getpid(); - @atomicStore(Pid, &t.pid, @enumFromInt(pid), .monotonic); - break :p pid; + // On a heavily loaded Linux 6.17.5, I observed a maximum of 20 + // attempts not acknowledged before the timeout (including exponential + // backoff) was sufficient, despite the heavy load. + const max_attempts = 22; + + for (0..max_attempts) |attempt_index| { + if (std.Thread.use_pthreads) { + if (std.c.pthread_kill(signal_id, .IO) != 0) return; + } else if (native_os == .linux) { + const pid: posix.pid_t = p: { + const cached_pid = @atomicLoad(Pid, &t.pid, .monotonic); + if (cached_pid != .unknown) break :p @intFromEnum(cached_pid); + const pid = std.os.linux.getpid(); + @atomicStore(Pid, &t.pid, @enumFromInt(pid), .monotonic); + break :p pid; + }; + if (std.os.linux.tgkill(pid, @bitCast(signal_id), .IO) != 0) return; + } else { + return; + } + + if (t.robust_cancel != .enabled) return; + + var timespec: posix.timespec = .{ + .sec = 0, + .nsec = @as(isize, 1) << @intCast(attempt_index), }; - if (std.os.linux.tgkill(pid, @bitCast(signal_id), .IO) != 0) return; - } else { - return; + if (native_os == .linux) { + _ = std.os.linux.clock_nanosleep(posix.CLOCK.MONOTONIC, .{ .ABSTIME = false }, ×pec, ×pec); + } else { + _ = posix.system.nanosleep(×pec, ×pec); + } + + switch (@atomicRmw(CancelStatus, &closure.cancel_status, .Xchg, .requested, .monotonic).unpack()) { + .requested => continue, // Retry needed in case other thread hasn't yet entered the syscall. + .none, .acknowledged => return, + .signal_id => |new_signal_id| signal_id = new_signal_id, + } } } }; From 85053a6a36ac1d76ab226943ddecb849e21ef4c6 Mon Sep 17 00:00:00 2001 From: David Rubin Date: Sat, 29 Nov 2025 19:16:11 -0800 Subject: [PATCH 08/14] link.Elf: implement aarch64 relocation --- src/link/Elf/Atom.zig | 39 +++++++-------------------------------- src/link/aarch64.zig | 6 ++++++ 2 files changed, 13 insertions(+), 32 deletions(-) diff --git a/src/link/Elf/Atom.zig b/src/link/Elf/Atom.zig index 8d8bc71a30..80ca049deb 100644 --- a/src/link/Elf/Atom.zig +++ b/src/link/Elf/Atom.zig @@ -1499,22 +1499,18 @@ const aarch64 = struct { .ABS64 => { try atom.scanReloc(symbol, rel, dynAbsRelocAction(symbol, elf_file), elf_file); }, - .ADR_PREL_PG_HI21 => { try atom.scanReloc(symbol, rel, pcRelocAction(symbol, elf_file), elf_file); }, - .ADR_GOT_PAGE => { // TODO: relax if possible symbol.flags.needs_got = true; }, - .LD64_GOT_LO12_NC, .LD64_GOTPAGE_LO15, => { symbol.flags.needs_got = true; }, - .CALL26, .JUMP26, => { @@ -1522,25 +1518,21 @@ const aarch64 = struct { symbol.flags.needs_plt = true; } }, - .TLSLE_ADD_TPREL_HI12, .TLSLE_ADD_TPREL_LO12_NC, => { if (is_dyn_lib) try atom.reportPicError(symbol, rel, elf_file); }, - .TLSIE_ADR_GOTTPREL_PAGE21, .TLSIE_LD64_GOTTPREL_LO12_NC, => { symbol.flags.needs_gottp = true; }, - .TLSGD_ADR_PAGE21, .TLSGD_ADD_LO12_NC, => { symbol.flags.needs_tlsgd = true; }, - .TLSDESC_ADR_PAGE21, .TLSDESC_LD64_LO12, .TLSDESC_ADD_LO12, @@ -1551,18 +1543,17 @@ const aarch64 = struct { symbol.flags.needs_tlsdesc = true; } }, - .ADD_ABS_LO12_NC, .ADR_PREL_LO21, - .LDST8_ABS_LO12_NC, + .CONDBR19, + .LDST128_ABS_LO12_NC, .LDST16_ABS_LO12_NC, .LDST32_ABS_LO12_NC, .LDST64_ABS_LO12_NC, - .LDST128_ABS_LO12_NC, + .LDST8_ABS_LO12_NC, .PREL32, .PREL64, => {}, - else => try atom.reportUnhandledRelocError(rel, elf_file), } } @@ -1599,7 +1590,6 @@ const aarch64 = struct { r_offset, ); }, - .CALL26, .JUMP26, => { @@ -1611,27 +1601,26 @@ const aarch64 = struct { }; util.writeBranchImm(disp, code); }, - + .CONDBR19 => { + const value = math.cast(i19, S + A - P) orelse return error.Overflow; + util.writeCondBrImm(value, code); + }, .PREL32 => { const value = math.cast(i32, S + A - P) orelse return error.Overflow; mem.writeInt(u32, code, @bitCast(value), .little); }, - .PREL64 => { const value = S + A - P; mem.writeInt(u64, code_buffer[r_offset..][0..8], @bitCast(value), .little); }, - .ADR_PREL_LO21 => { const value = math.cast(i21, S + A - P) orelse return error.Overflow; util.writeAdrInst(value, code); }, - .ADR_PREL_PG_HI21 => { // TODO: check for relaxation of ADRP+ADD util.writeAdrInst(try util.calcNumberOfPages(P, S + A), code); }, - .ADR_GOT_PAGE => if (target.flags.has_got) { util.writeAdrInst(try util.calcNumberOfPages(P, G + GOT + A), code); } else { @@ -1644,18 +1633,15 @@ const aarch64 = struct { r_offset, }); }, - .LD64_GOT_LO12_NC => { assert(target.flags.has_got); const taddr = @as(u64, @intCast(G + GOT + A)); util.writeLoadStoreRegInst(@divExact(@as(u12, @truncate(taddr)), 8), code); }, - .ADD_ABS_LO12_NC => { const taddr = @as(u64, @intCast(S + A)); util.writeAddImmInst(@truncate(taddr), code); }, - .LDST8_ABS_LO12_NC, .LDST16_ABS_LO12_NC, .LDST32_ABS_LO12_NC, @@ -1674,44 +1660,37 @@ const aarch64 = struct { }; util.writeLoadStoreRegInst(off, code); }, - .TLSLE_ADD_TPREL_HI12 => { const value = math.cast(i12, (S + A - TP) >> 12) orelse return error.Overflow; util.writeAddImmInst(@bitCast(value), code); }, - .TLSLE_ADD_TPREL_LO12_NC => { const value: i12 = @truncate(S + A - TP); util.writeAddImmInst(@bitCast(value), code); }, - .TLSIE_ADR_GOTTPREL_PAGE21 => { const S_ = target.gotTpAddress(elf_file); relocs_log.debug(" [{x} => {x}]", .{ P, S_ + A }); util.writeAdrInst(try util.calcNumberOfPages(P, S_ + A), code); }, - .TLSIE_LD64_GOTTPREL_LO12_NC => { const S_ = target.gotTpAddress(elf_file); relocs_log.debug(" [{x} => {x}]", .{ P, S_ + A }); const off: u12 = try math.divExact(u12, @truncate(@as(u64, @bitCast(S_ + A))), 8); util.writeLoadStoreRegInst(off, code); }, - .TLSGD_ADR_PAGE21 => { const S_ = target.tlsGdAddress(elf_file); relocs_log.debug(" [{x} => {x}]", .{ P, S_ + A }); util.writeAdrInst(try util.calcNumberOfPages(P, S_ + A), code); }, - .TLSGD_ADD_LO12_NC => { const S_ = target.tlsGdAddress(elf_file); relocs_log.debug(" [{x} => {x}]", .{ P, S_ + A }); const off: u12 = @truncate(@as(u64, @bitCast(S_ + A))); util.writeAddImmInst(off, code); }, - .TLSDESC_ADR_PAGE21 => { if (target.flags.has_tlsdesc) { const S_ = target.tlsDescAddress(elf_file); @@ -1722,7 +1701,6 @@ const aarch64 = struct { util.encoding.Instruction.nop().write(code); } }, - .TLSDESC_LD64_LO12 => { if (target.flags.has_tlsdesc) { const S_ = target.tlsDescAddress(elf_file); @@ -1734,7 +1712,6 @@ const aarch64 = struct { util.encoding.Instruction.nop().write(code); } }, - .TLSDESC_ADD_LO12 => { if (target.flags.has_tlsdesc) { const S_ = target.tlsDescAddress(elf_file); @@ -1747,13 +1724,11 @@ const aarch64 = struct { util.encoding.Instruction.movz(.x0, value, .{ .lsl = .@"16" }).write(code); } }, - .TLSDESC_CALL => if (!target.flags.has_tlsdesc) { relocs_log.debug(" relaxing br => movk(x0, {x})", .{S + A - TP}); const value: u16 = @bitCast(@as(i16, @truncate(S + A - TP))); util.encoding.Instruction.movk(.x0, value, .{}).write(code); }, - else => try atom.reportUnhandledRelocError(rel, elf_file), } } diff --git a/src/link/aarch64.zig b/src/link/aarch64.zig index c9defc27b3..0536316f4c 100644 --- a/src/link/aarch64.zig +++ b/src/link/aarch64.zig @@ -29,6 +29,12 @@ pub fn writeBranchImm(disp: i28, code: *[4]u8) void { inst.write(code); } +pub fn writeCondBrImm(disp: i19, code: *[4]u8) void { + var inst: encoding.Instruction = .read(code); + inst.branch_exception_generating_system.conditional_branch_immediate.group.imm19 = @intCast(@shrExact(disp, 2)); + inst.write(code); +} + const assert = std.debug.assert; const builtin = @import("builtin"); const math = std.math; From 103467fa6c38c6bbfc6d2d9f3da28a551f6a9d17 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sun, 30 Nov 2025 19:43:53 -0800 Subject: [PATCH 09/14] std.Io.Threaded: make is_musl linux-only --- lib/std/Io/Threaded.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 9c818e1794..9d472f686e 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -3,7 +3,7 @@ const Threaded = @This(); const builtin = @import("builtin"); const native_os = builtin.os.tag; const is_windows = native_os == .windows; -const is_musl = builtin.link_libc and builtin.abi.isMusl(); +const is_musl = native_os == .linux and builtin.link_libc and builtin.abi.isMusl(); const windows = std.os.windows; const ws2_32 = std.os.windows.ws2_32; const is_debug = builtin.mode == .Debug; From 57f5de5b7703f9d27e8c12ca71e2db32cab43cf3 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sun, 30 Nov 2025 20:55:21 -0800 Subject: [PATCH 10/14] std.Io.Threaded: use the correct mmsghdr struct --- lib/std/Io/Threaded.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 9d472f686e..3b3ef98ba4 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -5052,7 +5052,7 @@ fn netSendMany( messages: []net.OutgoingMessage, flags: u32, ) net.Socket.SendError!usize { - var msg_buffer: [64]std.os.linux.mmsghdr = undefined; + var msg_buffer: [64]posix.system.mmsghdr = undefined; var addr_buffer: [msg_buffer.len]PosixAddress = undefined; var iovecs_buffer: [msg_buffer.len]posix.iovec = undefined; const min_len: usize = @min(messages.len, msg_buffer.len); From 54a84964f8e3cd015688c6929612f7438b7e9553 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 1 Dec 2025 17:47:47 -0800 Subject: [PATCH 11/14] std.os.linux: SIG enum is non-exhaustive --- lib/std/os/linux.zig | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/std/os/linux.zig b/lib/std/os/linux.zig index a41ead5485..b80d7c606f 100644 --- a/lib/std/os/linux.zig +++ b/lib/std/os/linux.zig @@ -3771,6 +3771,7 @@ pub const SIG = if (is_mips) enum(u32) { PROF = 29, XCPU = 30, XFZ = 31, + _, } else if (is_sparc) enum(u32) { pub const BLOCK = 1; pub const UNBLOCK = 2; @@ -3816,6 +3817,7 @@ pub const SIG = if (is_mips) enum(u32) { LOST = 29, USR1 = 30, USR2 = 31, + _, } else enum(u32) { pub const BLOCK = 0; pub const UNBLOCK = 1; @@ -3859,6 +3861,7 @@ pub const SIG = if (is_mips) enum(u32) { IO = 29, PWR = 30, SYS = 31, + _, }; pub const kernel_rwf = u32; From bf0ffc45b9ca76250749097522714b7e3831a0c1 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 1 Dec 2025 19:02:09 -0800 Subject: [PATCH 12/14] std.Io.Threaded: musl: handle ECANCELED same as EINTR Otherwise the pthread_cancel can affect unrelated tasks. --- lib/std/Io/Threaded.zig | 247 +++++++++++++++------------------------- 1 file changed, 92 insertions(+), 155 deletions(-) diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 3b3ef98ba4..d74ae0fb07 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -93,10 +93,10 @@ pub const Pid = if (native_os == .linux) enum(posix.pid_t) { const Thread = struct { /// The value that needs to be passed to pthread_kill or tgkill in order to /// send a signal. - signal_id: SignalId, + signal_id: SignaleeId, current_closure: ?*Closure = null, - const SignalId = if (std.Thread.use_pthreads) std.c.pthread_t else std.Thread.Id; + const SignaleeId = if (std.Thread.use_pthreads) std.c.pthread_t else std.Thread.Id; threadlocal var current: ?*Thread = null; @@ -113,11 +113,15 @@ const Thread = struct { .acknowledged, .acq_rel, .acquire, - ) orelse return error.Canceled) { - .none => return, + ) orelse { + if (is_musl) assert(std.c.pthread_setcancelstate(.DISABLE, null) == .SUCCESS); + return error.Canceled; + }) { .requested => unreachable, .acknowledged => unreachable, - _ => return, + .none, _ => { + if (is_musl) assert(std.c.pthread_setcancelstate(.MASKED, null) == .SUCCESS); + }, } } @@ -128,13 +132,14 @@ const Thread = struct { CancelStatus, &closure.cancel_status, .none, - .fromSignalId(thread.signal_id), + .fromSignaleeId(thread.signal_id), .acq_rel, .acquire, ) orelse return) { .none => unreachable, .requested => { @atomicStore(CancelStatus, &closure.cancel_status, .acknowledged, .release); + if (is_musl) assert(std.c.pthread_setcancelstate(.DISABLE, null) == .SUCCESS); return error.Canceled; }, .acknowledged => return, @@ -144,24 +149,22 @@ const Thread = struct { fn endSyscall(thread: *Thread) void { const closure = thread.current_closure orelse return; - _ = @cmpxchgStrong( + const prev = @cmpxchgStrong( CancelStatus, &closure.cancel_status, - .fromSignalId(thread.signal_id), + .fromSignaleeId(thread.signal_id), .none, .acq_rel, .acquire, - ); - } - - fn endSyscallCanceled(thread: *Thread) Io.Cancelable { - if (thread.current_closure) |closure| { - @atomicStore(CancelStatus, &closure.cancel_status, .acknowledged, .release); + ) orelse return; + if (is_musl and prev == .requested) { + // They called pthread_cancel, but we want to disarm it since + // the next call to beginSyscall will notice requested status. + assert(std.c.pthread_setcancelstate(.DISABLE, null) == .SUCCESS); } - return error.Canceled; } - fn currentSignalId() SignalId { + fn currentSignalId() SignaleeId { return if (std.Thread.use_pthreads) std.c.pthread_self() else std.Thread.getCurrentId(); } }; @@ -184,7 +187,7 @@ const CancelStatus = enum(usize) { /// Cancellation has been acknowledged and is in progress. Signals should /// not be sent. acknowledged = std.math.maxInt(usize), - /// Stores a `Thread.SignalId` and indicates that sending a signal to this thread + /// Stores a `Thread.SignaleeId` and indicates that sending a signal to this thread /// is needed in order to cancel. This state is set before going into /// a blocking operation that needs to get unblocked via signal. _, @@ -193,7 +196,7 @@ const CancelStatus = enum(usize) { none, requested, acknowledged, - signal_id: Thread.SignalId, + signal_id: Thread.SignaleeId, }; fn unpack(cs: CancelStatus) Unpacked { @@ -210,7 +213,7 @@ const CancelStatus = enum(usize) { }; } - fn fromSignalId(signal_id: Thread.SignalId) CancelStatus { + fn fromSignaleeId(signal_id: Thread.SignaleeId) CancelStatus { return if (std.Thread.use_pthreads) @enumFromInt(@intFromPtr(signal_id)) else @@ -397,6 +400,10 @@ fn worker(t: *Threaded) void { // Musl has an undocumented extension that makes pthread_cancel have the useful, desired // behavior of causing the next syscall to return ECANCELED. + // + // The call to `requestCancel` and this can race, leading to + // ECANCELED being returned for a syscall in an unrelated task, + // which is why EINTR and ECANCELED are both handled with a check. if (is_musl) assert(std.c.pthread_setcancelstate(.MASKED, null) == .SUCCESS); const closure: *Closure = @fieldParentPtr("node", closure_node); @@ -1239,11 +1246,10 @@ fn dirMakePosix(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, mode: current_thread.endSyscall(); return; }, - .INTR => { + .INTR, .CANCELED => { try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -1282,11 +1288,10 @@ fn dirMakeWasi(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, mode: I current_thread.endSyscall(); return; }, - .INTR => { + .INTR, .CANCELED => { try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -1548,11 +1553,10 @@ fn dirStatPathLinux( current_thread.endSyscall(); return statFromLinux(&statx); }, - .INTR => { + .INTR, .CANCELED => { try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -1594,11 +1598,10 @@ fn dirStatPathPosix( current_thread.endSyscall(); return statFromPosix(&stat); }, - .INTR => { + .INTR, .CANCELED => { try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -1655,11 +1658,10 @@ fn dirStatPathWasi( current_thread.endSyscall(); return statFromWasi(&stat); }, - .INTR => { + .INTR, .CANCELED => { try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -1701,11 +1703,10 @@ fn fileStatPosix(userdata: ?*anyopaque, file: Io.File) Io.File.StatError!Io.File current_thread.endSyscall(); return statFromPosix(&stat); }, - .INTR => { + .INTR, .CANCELED => { try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -1740,11 +1741,10 @@ fn fileStatLinux(userdata: ?*anyopaque, file: Io.File) Io.File.StatError!Io.File current_thread.endSyscall(); return statFromLinux(&statx); }, - .INTR => { + .INTR, .CANCELED => { try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -1826,11 +1826,10 @@ fn fileStatWasi(userdata: ?*anyopaque, file: Io.File) Io.File.StatError!Io.File. current_thread.endSyscall(); return statFromWasi(&stat); }, - .INTR => { + .INTR, .CANCELED => { try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -1878,11 +1877,10 @@ fn dirAccessPosix( current_thread.endSyscall(); return; }, - .INTR => { + .INTR, .CANCELED => { try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -1928,11 +1926,10 @@ fn dirAccessWasi( current_thread.endSyscall(); break; }, - .INTR => { + .INTR, .CANCELED => { try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -2075,11 +2072,10 @@ fn dirCreateFilePosix( current_thread.endSyscall(); break @intCast(rc); }, - .INTR => { + .INTR, .CANCELED => { try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -2130,11 +2126,10 @@ fn dirCreateFilePosix( current_thread.endSyscall(); break; }, - .INTR => { + .INTR, .CANCELED => { try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -2159,7 +2154,7 @@ fn dirCreateFilePosix( current_thread.endSyscall(); break @intCast(rc); }, - .INTR => { + .INTR, .CANCELED => { try current_thread.checkCancel(); continue; }, @@ -2179,7 +2174,7 @@ fn dirCreateFilePosix( current_thread.endSyscall(); break; }, - .INTR => { + .INTR, .CANCELED => { try current_thread.checkCancel(); continue; }, @@ -2285,11 +2280,10 @@ fn dirCreateFileWasi( current_thread.endSyscall(); return .{ .handle = fd }; }, - .INTR => { + .INTR, .CANCELED => { try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -2379,11 +2373,10 @@ fn dirOpenFilePosix( current_thread.endSyscall(); break @intCast(rc); }, - .INTR => { + .INTR, .CANCELED => { try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -2433,11 +2426,10 @@ fn dirOpenFilePosix( current_thread.endSyscall(); break; }, - .INTR => { + .INTR, .CANCELED => { try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -2462,11 +2454,10 @@ fn dirOpenFilePosix( current_thread.endSyscall(); break @intCast(rc); }, - .INTR => { + .INTR, .CANCELED => { try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |err| { current_thread.endSyscall(); return posix.unexpectedErrno(err); @@ -2483,11 +2474,10 @@ fn dirOpenFilePosix( current_thread.endSyscall(); break; }, - .INTR => { + .INTR, .CANCELED => { try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |err| { current_thread.endSyscall(); return posix.unexpectedErrno(err); @@ -2682,11 +2672,10 @@ fn dirOpenFileWasi( current_thread.endSyscall(); return .{ .handle = fd }; }, - .INTR => { + .INTR, .CANCELED => { try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -2766,11 +2755,10 @@ fn dirOpenDirPosix( current_thread.endSyscall(); return .{ .handle = @intCast(rc) }; }, - .INTR => { + .INTR, .CANCELED => { try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -2819,11 +2807,10 @@ fn dirOpenDirHaiku( return .{ .handle = rc }; } switch (@as(posix.E, @enumFromInt(rc))) { - .INTR => { + .INTR, .CANCELED => { try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -2964,11 +2951,10 @@ fn dirOpenDirWasi( current_thread.endSyscall(); return .{ .handle = fd }; }, - .INTR => { + .INTR, .CANCELED => { try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -3031,11 +3017,10 @@ fn fileReadStreamingPosix(userdata: ?*anyopaque, file: Io.File, data: [][]u8) Io current_thread.endSyscall(); return nread; }, - .INTR => { + .INTR, .CANCELED => { try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -3065,11 +3050,10 @@ fn fileReadStreamingPosix(userdata: ?*anyopaque, file: Io.File, data: [][]u8) Io current_thread.endSyscall(); return @intCast(rc); }, - .INTR => { + .INTR, .CANCELED => { try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -3151,11 +3135,10 @@ fn fileReadPositionalPosix(userdata: ?*anyopaque, file: Io.File, data: [][]u8, o current_thread.endSyscall(); return nread; }, - .INTR => { + .INTR, .CANCELED => { try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -3189,11 +3172,10 @@ fn fileReadPositionalPosix(userdata: ?*anyopaque, file: Io.File, data: [][]u8, o current_thread.endSyscall(); return @bitCast(rc); }, - .INTR => { + .INTR, .CANCELED => { try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -3291,11 +3273,10 @@ fn fileSeekTo(userdata: ?*anyopaque, file: Io.File, offset: u64) Io.File.SeekErr current_thread.endSyscall(); return; }, - .INTR => { + .INTR, .CANCELED => { try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -3324,11 +3305,10 @@ fn fileSeekTo(userdata: ?*anyopaque, file: Io.File, offset: u64) Io.File.SeekErr current_thread.endSyscall(); return; }, - .INTR => { + .INTR, .CANCELED => { try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -3353,11 +3333,10 @@ fn fileSeekTo(userdata: ?*anyopaque, file: Io.File, offset: u64) Io.File.SeekErr current_thread.endSyscall(); return; }, - .INTR => { + .INTR, .CANCELED => { try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -3510,11 +3489,10 @@ fn sleepLinux(userdata: ?*anyopaque, timeout: Io.Timeout) Io.SleepError!void { current_thread.endSyscall(); return; }, - .INTR => { + .INTR, .CANCELED => { try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -3588,11 +3566,10 @@ fn sleepPosix(userdata: ?*anyopaque, timeout: Io.Timeout) Io.SleepError!void { try current_thread.beginSyscall(); while (true) { switch (posix.errno(posix.system.nanosleep(×pec, ×pec))) { - .INTR => { + .INTR, .CANCELED => { try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), // This prong handles success as well as unexpected errors. else => return current_thread.endSyscall(), } @@ -3662,11 +3639,10 @@ fn netListenIpPosix( current_thread.endSyscall(); break; }, - .INTR => { + .INTR, .CANCELED => { try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -3711,7 +3687,10 @@ fn netListenIpWindows( try current_thread.beginSyscall(); while (true) { const rc = ws2_32.bind(socket_handle, &storage.any, addr_len); - if (rc != ws2_32.SOCKET_ERROR) break; + if (rc != ws2_32.SOCKET_ERROR) { + current_thread.endSyscall(); + break; + } switch (ws2_32.WSAGetLastError()) { .EINTR => { try current_thread.checkCancel(); @@ -3739,7 +3718,7 @@ fn netListenIpWindows( } } - try current_thread.checkCancel(); + try current_thread.beginSyscall(); while (true) { const rc = ws2_32.listen(socket_handle, options.kernel_backlog); if (rc != ws2_32.SOCKET_ERROR) { @@ -3823,11 +3802,10 @@ fn netListenUnixPosix( current_thread.endSyscall(); break; }, - .INTR => { + .INTR, .CANCELED => { try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -3947,11 +3925,10 @@ fn posixBindUnix( current_thread.endSyscall(); break; }, - .INTR => { + .INTR, .CANCELED => { try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -3992,11 +3969,10 @@ fn posixBind( current_thread.endSyscall(); break; }, - .INTR => { + .INTR, .CANCELED => { try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -4028,11 +4004,10 @@ fn posixConnect( current_thread.endSyscall(); return; }, - .INTR => { + .INTR, .CANCELED => { try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -4075,11 +4050,10 @@ fn posixConnectUnix( current_thread.endSyscall(); return; }, - .INTR => { + .INTR, .CANCELED => { try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -4120,11 +4094,10 @@ fn posixGetSockName( current_thread.endSyscall(); break; }, - .INTR => { + .INTR, .CANCELED => { try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -4188,11 +4161,10 @@ fn setSocketOption(current_thread: *Thread, fd: posix.fd_t, level: i32, opt_name current_thread.endSyscall(); return; }, - .INTR => { + .INTR, .CANCELED => { try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -4521,8 +4493,7 @@ fn openSocketPosix( try current_thread.checkCancel(); switch (posix.errno(posix.system.fcntl(fd, posix.F.SETFD, @as(usize, posix.FD_CLOEXEC)))) { .SUCCESS => break, - .INTR => continue, - .CANCELED => return current_thread.endSyscallCanceled(), + .INTR, .CANCELED => continue, else => |err| { current_thread.endSyscall(); return posix.unexpectedErrno(err); @@ -4532,11 +4503,10 @@ fn openSocketPosix( current_thread.endSyscall(); break fd; }, - .INTR => { + .INTR, .CANCELED => { try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -4624,7 +4594,7 @@ fn netAcceptPosix(userdata: ?*anyopaque, listen_fd: net.Socket.Handle) net.Serve try current_thread.checkCancel(); switch (posix.errno(posix.system.fcntl(fd, posix.F.SETFD, @as(usize, posix.FD_CLOEXEC)))) { .SUCCESS => break, - .INTR => continue, + .INTR, .CANCELED => continue, else => |err| { current_thread.endSyscall(); return posix.unexpectedErrno(err); @@ -4634,11 +4604,10 @@ fn netAcceptPosix(userdata: ?*anyopaque, listen_fd: net.Socket.Handle) net.Serve current_thread.endSyscall(); break fd; }, - .INTR => { + .INTR, .CANCELED => { try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -4743,11 +4712,10 @@ fn netReadPosix(userdata: ?*anyopaque, fd: net.Socket.Handle, data: [][]u8) net. current_thread.endSyscall(); return n; }, - .INTR => { + .INTR, .CANCELED => { try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -4776,11 +4744,10 @@ fn netReadPosix(userdata: ?*anyopaque, fd: net.Socket.Handle, data: [][]u8) net. current_thread.endSyscall(); return @intCast(rc); }, - .INTR => { + .INTR, .CANCELED => { try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -5012,11 +4979,10 @@ fn netSendOne( message.data_len = @intCast(rc); return; }, - .INTR => { + .INTR, .CANCELED => { try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -5089,11 +5055,10 @@ fn netSendMany( } return n; }, - .INTR => { + .INTR, .CANCELED => { try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -5225,8 +5190,7 @@ fn netReceivePosix( } continue :recv; }, - .INTR => continue, - .CANCELED => return .{ current_thread.endSyscallCanceled(), message_i }, + .INTR, .CANCELED => continue, .FAULT => |err| return .{ errnoBug(err), message_i }, .INVAL => |err| return .{ errnoBug(err), message_i }, @@ -5234,8 +5198,7 @@ fn netReceivePosix( else => |err| return .{ posix.unexpectedErrno(err), message_i }, } }, - .INTR => continue, - .CANCELED => return .{ current_thread.endSyscallCanceled(), message_i }, + .INTR, .CANCELED => continue, .BADF => |err| return .{ errnoBug(err), message_i }, .NFILE => return .{ error.SystemFdQuotaExceeded, message_i }, @@ -5350,11 +5313,10 @@ fn netWritePosix( current_thread.endSyscall(); return @intCast(rc); }, - .INTR => { + .INTR, .CANCELED => { try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -5452,8 +5414,7 @@ fn netWriteWindows( else => |err| err, }; switch (wsa_error) { - .EINTR => continue, - .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return current_thread.endSyscallCanceled(), + .EINTR, .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => continue, .NOTINITIALISED => { try initializeWsa(t); continue; @@ -5561,11 +5522,10 @@ fn netInterfaceNameResolve( current_thread.endSyscall(); return .{ .index = @bitCast(ifr.ifru.ivalue) }; }, - .INTR => { + .INTR, .CANCELED => { try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -5862,11 +5822,10 @@ fn netLookupFallible( break; }, .SYSTEM => switch (posix.errno(-1)) { - .INTR => { + .INTR, .CANCELED => { try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); return posix.unexpectedErrno(e); @@ -6612,15 +6571,15 @@ fn futexWait(current_thread: *Thread, ptr: *const std.atomic.Value(u32), expect: try current_thread.beginSyscall(); const rc = linux.futex_4arg(ptr, .{ .cmd = .WAIT, .private = true }, expect, null); current_thread.endSyscall(); - if (is_debug) switch (linux.errno(rc)) { + switch (linux.errno(rc)) { .SUCCESS => {}, // notified by `wake()` - .INTR => {}, // gives caller a chance to check cancellation + .INTR => {}, // caller's responsibility to retry .AGAIN => {}, // ptr.* != expect .INVAL => {}, // possibly timeout overflow - .TIMEDOUT => unreachable, - .FAULT => unreachable, // ptr was invalid - else => unreachable, - }; + .TIMEDOUT => recoverableOsBugDetected(), + .FAULT => recoverableOsBugDetected(), // ptr was invalid + else => recoverableOsBugDetected(), + } }, .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => { const c = std.c; @@ -6703,7 +6662,7 @@ pub fn futexWaitUncancelable(ptr: *const std.atomic.Value(u32), expect: u32) voi const rc = linux.futex_4arg(ptr, .{ .cmd = .WAIT, .private = true }, expect, null); switch (linux.errno(rc)) { .SUCCESS => {}, // notified by `wake()` - .INTR => {}, // gives caller a chance to check cancellation + .INTR => {}, // caller's responsibility to repeat .AGAIN => {}, // ptr.* != expect .INVAL => {}, // possibly timeout overflow .TIMEDOUT => recoverableOsBugDetected(), @@ -6757,28 +6716,6 @@ pub fn futexWaitUncancelable(ptr: *const std.atomic.Value(u32), expect: u32) voi } } -pub fn futexWaitDurationUncancelable(ptr: *const std.atomic.Value(u32), expect: u32, timeout: Io.Duration) void { - @branchHint(.cold); - - if (native_os == .linux) { - const linux = std.os.linux; - var ts = timestampToPosix(timeout.toNanoseconds()); - const rc = linux.futex_4arg(ptr, .{ .cmd = .WAIT, .private = true }, expect, &ts); - if (is_debug) switch (linux.errno(rc)) { - .SUCCESS => {}, // notified by `wake()` - .INTR => {}, // gives caller a chance to check cancellation - .AGAIN => {}, // ptr.* != expect - .TIMEDOUT => {}, - .INVAL => {}, // possibly timeout overflow - .FAULT => unreachable, // ptr was invalid - else => unreachable, - }; - return; - } else { - @compileError("TODO"); - } -} - pub fn futexWake(ptr: *const std.atomic.Value(u32), max_waiters: u32) void { @branchHint(.cold); From cf82064ebc3063c6a8e8f51388783ed1c01d6281 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 1 Dec 2025 19:07:39 -0800 Subject: [PATCH 13/14] std.Io.Threaded: don't use pthread_cancel with musl It doesn't support setting the "canceled" status to false, so once a thread has been canceled, all operations on the thread start permanently failing. --- lib/std/Io/Threaded.zig | 52 +++++++---------------------------------- 1 file changed, 8 insertions(+), 44 deletions(-) diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index d74ae0fb07..d53e8094cf 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -3,7 +3,6 @@ const Threaded = @This(); const builtin = @import("builtin"); const native_os = builtin.os.tag; const is_windows = native_os == .windows; -const is_musl = native_os == .linux and builtin.link_libc and builtin.abi.isMusl(); const windows = std.os.windows; const ws2_32 = std.os.windows.ws2_32; const is_debug = builtin.mode == .Debug; @@ -63,12 +62,7 @@ pid: Pid = .unknown, /// Unfortunately, trying again until the cancellation request is acknowledged /// has been observed to be relatively slow, and usually strong cancellation /// guarantees are not needed, so this defaults to off. -/// -/// Musl libc does not have this problem because of a clever, undocumented -/// extension related to pthread_cancel, which this code integrates with. -/// When compiling with no libc, `Threaded` does not yet implement the -/// equivalent trick (tracked by https://codeberg.org/ziglang/zig/issues/30049). -robust_cancel: RobustCancel = if (is_musl) .enabled else .disabled, +robust_cancel: RobustCancel = .disabled, wsa: if (is_windows) Wsa else struct {} = .{}, @@ -76,9 +70,7 @@ have_signal_handler: bool, old_sig_io: if (have_sig_io) posix.Sigaction else void, old_sig_pipe: if (have_sig_pipe) posix.Sigaction else void, -pub const RobustCancel = if (is_musl) enum { - enabled, -} else if (std.Thread.use_pthreads or native_os == .linux) enum { +pub const RobustCancel = if (std.Thread.use_pthreads or native_os == .linux) enum { enabled, disabled, } else enum { @@ -113,15 +105,10 @@ const Thread = struct { .acknowledged, .acq_rel, .acquire, - ) orelse { - if (is_musl) assert(std.c.pthread_setcancelstate(.DISABLE, null) == .SUCCESS); - return error.Canceled; - }) { + ) orelse return error.Canceled) { .requested => unreachable, .acknowledged => unreachable, - .none, _ => { - if (is_musl) assert(std.c.pthread_setcancelstate(.MASKED, null) == .SUCCESS); - }, + .none, _ => {}, } } @@ -139,7 +126,6 @@ const Thread = struct { .none => unreachable, .requested => { @atomicStore(CancelStatus, &closure.cancel_status, .acknowledged, .release); - if (is_musl) assert(std.c.pthread_setcancelstate(.DISABLE, null) == .SUCCESS); return error.Canceled; }, .acknowledged => return, @@ -149,7 +135,7 @@ const Thread = struct { fn endSyscall(thread: *Thread) void { const closure = thread.current_closure orelse return; - const prev = @cmpxchgStrong( + _ = @cmpxchgStrong( CancelStatus, &closure.cancel_status, .fromSignaleeId(thread.signal_id), @@ -157,11 +143,6 @@ const Thread = struct { .acq_rel, .acquire, ) orelse return; - if (is_musl and prev == .requested) { - // They called pthread_cancel, but we want to disarm it since - // the next call to beginSyscall will notice requested status. - assert(std.c.pthread_setcancelstate(.DISABLE, null) == .SUCCESS); - } } fn currentSignalId() SignaleeId { @@ -233,13 +214,6 @@ const Closure = struct { .none, .acknowledged, .requested => return, .signal_id => |signal_id| signal_id, }; - // Musl has an undocumented extension that makes pthread_cancel have the useful, desired - // behavior of causing the next syscall to return ECANCELED. - if (is_musl) { - _ = std.c.pthread_cancel(signal_id); - return; - } - // The task will enter a blocking syscall before checking for cancellation again. // We can send a signal to interrupt the syscall, but if it arrives before // the syscall instruction, it will be missed. Therefore, this code tries @@ -328,7 +302,7 @@ pub fn init( .mask = posix.sigemptyset(), .flags = 0, }; - if (!is_musl and have_sig_io) posix.sigaction(.IO, &act, &t.old_sig_io); + if (have_sig_io) posix.sigaction(.IO, &act, &t.old_sig_io); if (have_sig_pipe) posix.sigaction(.PIPE, &act, &t.old_sig_pipe); t.have_signal_handler = true; } @@ -366,7 +340,7 @@ pub fn deinit(t: *Threaded) void { if (ws2_32.WSACleanup() != 0) recoverableOsBugDetected(); } if (posix.Sigaction != void and t.have_signal_handler) { - if (!is_musl and have_sig_io) posix.sigaction(.IO, &t.old_sig_io, null); + if (have_sig_io) posix.sigaction(.IO, &t.old_sig_io, null); if (have_sig_pipe) posix.sigaction(.PIPE, &t.old_sig_pipe, null); } t.* = undefined; @@ -397,15 +371,6 @@ fn worker(t: *Threaded) void { while (true) { while (t.run_queue.popFirst()) |closure_node| { t.mutex.unlock(); - - // Musl has an undocumented extension that makes pthread_cancel have the useful, desired - // behavior of causing the next syscall to return ECANCELED. - // - // The call to `requestCancel` and this can race, leading to - // ECANCELED being returned for a syscall in an unrelated task, - // which is why EINTR and ECANCELED are both handled with a check. - if (is_musl) assert(std.c.pthread_setcancelstate(.MASKED, null) == .SUCCESS); - const closure: *Closure = @fieldParentPtr("node", closure_node); closure.start(closure, t); t.mutex.lock(); @@ -3460,8 +3425,7 @@ fn nowWasi(userdata: ?*anyopaque, clock: Io.Clock) Io.Clock.Error!Io.Timestamp { const sleep = switch (native_os) { .windows => sleepWindows, .wasi => sleepWasi, - // Since we use musl's pthread_cancel, it's important that all the syscalls go through libc. - .linux => if (is_musl) sleepPosix else sleepLinux, + .linux => sleepLinux, else => sleepPosix, }; From bb3f56d5d5424c7be42132c047a7146e82b49c74 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 1 Dec 2025 19:11:47 -0800 Subject: [PATCH 14/14] std.Io.Threaded: separate out ECANCELED handling again If ECANCELED occurs, it's from pthread_cancel which will *permanently* set that thread to be in a "canceling" state, which means the cancel cannot be ignored. That means it cannot be retried, like EINTR. It must be acknowledged. --- lib/std/Io/Threaded.zig | 166 +++++++++++++++++++++++++++------------- 1 file changed, 112 insertions(+), 54 deletions(-) diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index d53e8094cf..0e17cc01d0 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -145,6 +145,13 @@ const Thread = struct { ) orelse return; } + fn endSyscallCanceled(thread: *Thread) Io.Cancelable { + if (thread.current_closure) |closure| { + @atomicStore(CancelStatus, &closure.cancel_status, .acknowledged, .release); + } + return error.Canceled; + } + fn currentSignalId() SignaleeId { return if (std.Thread.use_pthreads) std.c.pthread_self() else std.Thread.getCurrentId(); } @@ -1211,10 +1218,11 @@ fn dirMakePosix(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, mode: current_thread.endSyscall(); return; }, - .INTR, .CANCELED => { + .INTR => { try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -1253,10 +1261,11 @@ fn dirMakeWasi(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, mode: I current_thread.endSyscall(); return; }, - .INTR, .CANCELED => { + .INTR => { try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -1518,10 +1527,11 @@ fn dirStatPathLinux( current_thread.endSyscall(); return statFromLinux(&statx); }, - .INTR, .CANCELED => { + .INTR => { try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -1563,10 +1573,11 @@ fn dirStatPathPosix( current_thread.endSyscall(); return statFromPosix(&stat); }, - .INTR, .CANCELED => { + .INTR => { try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -1623,10 +1634,11 @@ fn dirStatPathWasi( current_thread.endSyscall(); return statFromWasi(&stat); }, - .INTR, .CANCELED => { + .INTR => { try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -1668,10 +1680,11 @@ fn fileStatPosix(userdata: ?*anyopaque, file: Io.File) Io.File.StatError!Io.File current_thread.endSyscall(); return statFromPosix(&stat); }, - .INTR, .CANCELED => { + .INTR => { try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -1706,10 +1719,11 @@ fn fileStatLinux(userdata: ?*anyopaque, file: Io.File) Io.File.StatError!Io.File current_thread.endSyscall(); return statFromLinux(&statx); }, - .INTR, .CANCELED => { + .INTR => { try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -1791,10 +1805,11 @@ fn fileStatWasi(userdata: ?*anyopaque, file: Io.File) Io.File.StatError!Io.File. current_thread.endSyscall(); return statFromWasi(&stat); }, - .INTR, .CANCELED => { + .INTR => { try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -1842,10 +1857,11 @@ fn dirAccessPosix( current_thread.endSyscall(); return; }, - .INTR, .CANCELED => { + .INTR => { try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -1891,10 +1907,11 @@ fn dirAccessWasi( current_thread.endSyscall(); break; }, - .INTR, .CANCELED => { + .INTR => { try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -2037,10 +2054,11 @@ fn dirCreateFilePosix( current_thread.endSyscall(); break @intCast(rc); }, - .INTR, .CANCELED => { + .INTR => { try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -2091,10 +2109,11 @@ fn dirCreateFilePosix( current_thread.endSyscall(); break; }, - .INTR, .CANCELED => { + .INTR => { try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -2119,7 +2138,7 @@ fn dirCreateFilePosix( current_thread.endSyscall(); break @intCast(rc); }, - .INTR, .CANCELED => { + .INTR => { try current_thread.checkCancel(); continue; }, @@ -2139,7 +2158,7 @@ fn dirCreateFilePosix( current_thread.endSyscall(); break; }, - .INTR, .CANCELED => { + .INTR => { try current_thread.checkCancel(); continue; }, @@ -2245,10 +2264,11 @@ fn dirCreateFileWasi( current_thread.endSyscall(); return .{ .handle = fd }; }, - .INTR, .CANCELED => { + .INTR => { try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -2338,10 +2358,11 @@ fn dirOpenFilePosix( current_thread.endSyscall(); break @intCast(rc); }, - .INTR, .CANCELED => { + .INTR => { try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -2391,10 +2412,11 @@ fn dirOpenFilePosix( current_thread.endSyscall(); break; }, - .INTR, .CANCELED => { + .INTR => { try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -2419,10 +2441,11 @@ fn dirOpenFilePosix( current_thread.endSyscall(); break @intCast(rc); }, - .INTR, .CANCELED => { + .INTR => { try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |err| { current_thread.endSyscall(); return posix.unexpectedErrno(err); @@ -2439,10 +2462,11 @@ fn dirOpenFilePosix( current_thread.endSyscall(); break; }, - .INTR, .CANCELED => { + .INTR => { try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |err| { current_thread.endSyscall(); return posix.unexpectedErrno(err); @@ -2637,10 +2661,11 @@ fn dirOpenFileWasi( current_thread.endSyscall(); return .{ .handle = fd }; }, - .INTR, .CANCELED => { + .INTR => { try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -2720,10 +2745,11 @@ fn dirOpenDirPosix( current_thread.endSyscall(); return .{ .handle = @intCast(rc) }; }, - .INTR, .CANCELED => { + .INTR => { try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -2772,10 +2798,11 @@ fn dirOpenDirHaiku( return .{ .handle = rc }; } switch (@as(posix.E, @enumFromInt(rc))) { - .INTR, .CANCELED => { + .INTR => { try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -2916,10 +2943,11 @@ fn dirOpenDirWasi( current_thread.endSyscall(); return .{ .handle = fd }; }, - .INTR, .CANCELED => { + .INTR => { try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -2982,10 +3010,11 @@ fn fileReadStreamingPosix(userdata: ?*anyopaque, file: Io.File, data: [][]u8) Io current_thread.endSyscall(); return nread; }, - .INTR, .CANCELED => { + .INTR => { try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -3015,10 +3044,11 @@ fn fileReadStreamingPosix(userdata: ?*anyopaque, file: Io.File, data: [][]u8) Io current_thread.endSyscall(); return @intCast(rc); }, - .INTR, .CANCELED => { + .INTR => { try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -3100,10 +3130,11 @@ fn fileReadPositionalPosix(userdata: ?*anyopaque, file: Io.File, data: [][]u8, o current_thread.endSyscall(); return nread; }, - .INTR, .CANCELED => { + .INTR => { try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -3137,10 +3168,11 @@ fn fileReadPositionalPosix(userdata: ?*anyopaque, file: Io.File, data: [][]u8, o current_thread.endSyscall(); return @bitCast(rc); }, - .INTR, .CANCELED => { + .INTR => { try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -3238,10 +3270,11 @@ fn fileSeekTo(userdata: ?*anyopaque, file: Io.File, offset: u64) Io.File.SeekErr current_thread.endSyscall(); return; }, - .INTR, .CANCELED => { + .INTR => { try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -3270,10 +3303,11 @@ fn fileSeekTo(userdata: ?*anyopaque, file: Io.File, offset: u64) Io.File.SeekErr current_thread.endSyscall(); return; }, - .INTR, .CANCELED => { + .INTR => { try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -3298,10 +3332,11 @@ fn fileSeekTo(userdata: ?*anyopaque, file: Io.File, offset: u64) Io.File.SeekErr current_thread.endSyscall(); return; }, - .INTR, .CANCELED => { + .INTR => { try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -3453,10 +3488,11 @@ fn sleepLinux(userdata: ?*anyopaque, timeout: Io.Timeout) Io.SleepError!void { current_thread.endSyscall(); return; }, - .INTR, .CANCELED => { + .INTR => { try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -3530,10 +3566,11 @@ fn sleepPosix(userdata: ?*anyopaque, timeout: Io.Timeout) Io.SleepError!void { try current_thread.beginSyscall(); while (true) { switch (posix.errno(posix.system.nanosleep(×pec, ×pec))) { - .INTR, .CANCELED => { + .INTR => { try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), // This prong handles success as well as unexpected errors. else => return current_thread.endSyscall(), } @@ -3603,10 +3640,11 @@ fn netListenIpPosix( current_thread.endSyscall(); break; }, - .INTR, .CANCELED => { + .INTR => { try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -3766,10 +3804,11 @@ fn netListenUnixPosix( current_thread.endSyscall(); break; }, - .INTR, .CANCELED => { + .INTR => { try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -3889,10 +3928,11 @@ fn posixBindUnix( current_thread.endSyscall(); break; }, - .INTR, .CANCELED => { + .INTR => { try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -3933,10 +3973,11 @@ fn posixBind( current_thread.endSyscall(); break; }, - .INTR, .CANCELED => { + .INTR => { try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -3968,10 +4009,11 @@ fn posixConnect( current_thread.endSyscall(); return; }, - .INTR, .CANCELED => { + .INTR => { try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -4014,10 +4056,11 @@ fn posixConnectUnix( current_thread.endSyscall(); return; }, - .INTR, .CANCELED => { + .INTR => { try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -4058,10 +4101,11 @@ fn posixGetSockName( current_thread.endSyscall(); break; }, - .INTR, .CANCELED => { + .INTR => { try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -4125,10 +4169,11 @@ fn setSocketOption(current_thread: *Thread, fd: posix.fd_t, level: i32, opt_name current_thread.endSyscall(); return; }, - .INTR, .CANCELED => { + .INTR => { try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -4457,7 +4502,8 @@ fn openSocketPosix( try current_thread.checkCancel(); switch (posix.errno(posix.system.fcntl(fd, posix.F.SETFD, @as(usize, posix.FD_CLOEXEC)))) { .SUCCESS => break, - .INTR, .CANCELED => continue, + .INTR => continue, + .CANCELED => return current_thread.endSyscallCanceled(), else => |err| { current_thread.endSyscall(); return posix.unexpectedErrno(err); @@ -4467,10 +4513,11 @@ fn openSocketPosix( current_thread.endSyscall(); break fd; }, - .INTR, .CANCELED => { + .INTR => { try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -4558,7 +4605,7 @@ fn netAcceptPosix(userdata: ?*anyopaque, listen_fd: net.Socket.Handle) net.Serve try current_thread.checkCancel(); switch (posix.errno(posix.system.fcntl(fd, posix.F.SETFD, @as(usize, posix.FD_CLOEXEC)))) { .SUCCESS => break, - .INTR, .CANCELED => continue, + .INTR => continue, else => |err| { current_thread.endSyscall(); return posix.unexpectedErrno(err); @@ -4568,10 +4615,11 @@ fn netAcceptPosix(userdata: ?*anyopaque, listen_fd: net.Socket.Handle) net.Serve current_thread.endSyscall(); break fd; }, - .INTR, .CANCELED => { + .INTR => { try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -4676,10 +4724,11 @@ fn netReadPosix(userdata: ?*anyopaque, fd: net.Socket.Handle, data: [][]u8) net. current_thread.endSyscall(); return n; }, - .INTR, .CANCELED => { + .INTR => { try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -4708,10 +4757,11 @@ fn netReadPosix(userdata: ?*anyopaque, fd: net.Socket.Handle, data: [][]u8) net. current_thread.endSyscall(); return @intCast(rc); }, - .INTR, .CANCELED => { + .INTR => { try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -4943,10 +4993,11 @@ fn netSendOne( message.data_len = @intCast(rc); return; }, - .INTR, .CANCELED => { + .INTR => { try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -5019,10 +5070,11 @@ fn netSendMany( } return n; }, - .INTR, .CANCELED => { + .INTR => { try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -5154,7 +5206,8 @@ fn netReceivePosix( } continue :recv; }, - .INTR, .CANCELED => continue, + .INTR => continue, + .CANCELED => return .{ current_thread.endSyscallCanceled(), message_i }, .FAULT => |err| return .{ errnoBug(err), message_i }, .INVAL => |err| return .{ errnoBug(err), message_i }, @@ -5162,7 +5215,8 @@ fn netReceivePosix( else => |err| return .{ posix.unexpectedErrno(err), message_i }, } }, - .INTR, .CANCELED => continue, + .INTR => continue, + .CANCELED => return .{ current_thread.endSyscallCanceled(), message_i }, .BADF => |err| return .{ errnoBug(err), message_i }, .NFILE => return .{ error.SystemFdQuotaExceeded, message_i }, @@ -5277,10 +5331,11 @@ fn netWritePosix( current_thread.endSyscall(); return @intCast(rc); }, - .INTR, .CANCELED => { + .INTR => { try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -5378,7 +5433,8 @@ fn netWriteWindows( else => |err| err, }; switch (wsa_error) { - .EINTR, .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => continue, + .EINTR => continue, + .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return current_thread.endSyscallCanceled(), .NOTINITIALISED => { try initializeWsa(t); continue; @@ -5486,10 +5542,11 @@ fn netInterfaceNameResolve( current_thread.endSyscall(); return .{ .index = @bitCast(ifr.ifru.ivalue) }; }, - .INTR, .CANCELED => { + .INTR => { try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -5786,10 +5843,11 @@ fn netLookupFallible( break; }, .SYSTEM => switch (posix.errno(-1)) { - .INTR, .CANCELED => { + .INTR => { try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); return posix.unexpectedErrno(e);