commit 8226d706e2cb69845c400ce22d8b263c4a390f11 (tree)
parent 04226193ccb69f50936e47804be56bd1bdc316d9
Author: mlugg <mlugg@noreply.codeberg.org>
Date: Sat, 3 Jan 2026 21:33:46 +0100
Merge pull request 'std.Io.Threaded: performance enhancements, bugfixes, and better Windows and NetBSD support' (#30634) from std.Io.Threaded-groups-2 into master
Reviewed-on: https://codeberg.org/ziglang/zig/pulls/30634
Reviewed-by: Andrew Kelley <andrewrk@noreply.codeberg.org>
Resolves: https://codeberg.org/ziglang/zig/issues/30049
Diffstat:
17 files changed, 3721 insertions(+), 2778 deletions(-)
diff --git a/lib/compiler/build_runner.zig b/lib/compiler/build_runner.zig
@@ -849,7 +849,7 @@ fn runStepNames(
defer f.deinit();
f.start();
- f.waitAndPrintReport();
+ try f.waitAndPrintReport();
}
// Every test has a state
diff --git a/lib/std/Build/Fuzz.zig b/lib/std/Build/Fuzz.zig
@@ -513,11 +513,11 @@ fn addEntryPoint(fuzz: *Fuzz, coverage_id: u64, addr: u64) error{ AlreadyReporte
try coverage_map.entry_points.append(fuzz.gpa, @intCast(index));
}
-pub fn waitAndPrintReport(fuzz: *Fuzz) void {
+pub fn waitAndPrintReport(fuzz: *Fuzz) Io.Cancelable!void {
assert(fuzz.mode == .limit);
const io = fuzz.io;
- fuzz.group.awaitUncancelable(io);
+ try fuzz.group.await(io);
fuzz.group = .init;
std.debug.print("======= FUZZING REPORT =======\n", .{});
diff --git a/lib/std/Io.zig b/lib/std/Io.zig
@@ -631,7 +631,7 @@ pub const VTable = struct {
/// Copied and then passed to `start`.
context: []const u8,
context_alignment: std.mem.Alignment,
- start: *const fn (*Group, context: *const anyopaque) Cancelable!void,
+ start: *const fn (context: *const anyopaque) Cancelable!void,
) void,
/// Thread-safe.
groupConcurrent: *const fn (
@@ -642,7 +642,7 @@ pub const VTable = struct {
/// Copied and then passed to `start`.
context: []const u8,
context_alignment: std.mem.Alignment,
- start: *const fn (*Group, context: *const anyopaque) Cancelable!void,
+ start: *const fn (context: *const anyopaque) Cancelable!void,
) ConcurrentError!void,
groupAwait: *const fn (?*anyopaque, *Group, token: *anyopaque) Cancelable!void,
groupCancel: *const fn (?*anyopaque, *Group, token: *anyopaque) void,
@@ -1050,40 +1050,40 @@ pub fn Future(Result: type) type {
};
}
+/// An unordered set of tasks which can only be awaited or canceled as a whole.
+/// Tasks are spawned in the group with `Group.async` and `Group.concurrent`.
+///
+/// The resources associated with each task are *guaranteed* to be released when
+/// the individual task returns, as opposed to when the whole group completes or
+/// is awaited. For this reason, it is not a resource leak to have a long-lived
+/// group which concurrent tasks are repeatedly added to. However, asynchronous
+/// tasks are not guaranteed to run until `Group.await` or `Group.cancel` is
+/// called, so adding async tasks to a group without ever awaiting it may leak
+/// resources.
pub const Group = struct {
- state: usize,
- context: ?*anyopaque,
/// This value indicates whether or not a group has pending tasks. `null`
/// means there are no pending tasks, and no resources associated with the
/// group, so `await` and `cancel` return immediately without calling the
/// implementation. This means that `token` must be accessed atomically to
/// avoid racing with the check in `await` and `cancel`.
token: std.atomic.Value(?*anyopaque),
+ /// This value is available for the implementation to use as it wishes.
+ state: usize,
- pub const init: Group = .{ .state = 0, .context = null, .token = .init(null) };
+ pub const init: Group = .{ .token = .init(null), .state = 0 };
- /// Calls `function` with `args` asynchronously. The resource spawned is
- /// owned by the group.
- ///
- /// `function` *may* be called immediately, before `async` returns.
+ /// Equivalent to `Io.async`, except the task is spawned in this `Group`
+ /// instead of becoming associated with a `Future`.
///
- /// When this function returns, it is guaranteed that `function` has
- /// already been called and completed, or it has successfully been assigned
- /// a unit of concurrency.
+ /// The return type of `function` must be coercible to `Cancelable!void`.
///
- /// After this is called, `await` or `cancel` must be called before the
- /// group is deinitialized.
- ///
- /// Threadsafe.
- ///
- /// See also:
- /// * `concurrent`
- /// * `Io.async`
+ /// Once this function is called, there are resources associated with the
+ /// group. To release those resources, `Group.await` or `Group.cancel` must
+ /// eventually be called.
pub fn async(g: *Group, io: Io, function: anytype, args: std.meta.ArgsTuple(@TypeOf(function))) void {
const Args = @TypeOf(args);
const TypeErased = struct {
- fn start(group: *Group, context: *const anyopaque) Cancelable!void {
- _ = group;
+ fn start(context: *const anyopaque) Cancelable!void {
const args_casted: *const Args = @ptrCast(@alignCast(context));
return @call(.auto, function, args_casted.*);
}
@@ -1091,27 +1091,18 @@ pub const Group = struct {
io.vtable.groupAsync(io.userdata, g, @ptrCast(&args), .of(Args), TypeErased.start);
}
- /// Calls `function` with `args`, such that the function is not guaranteed
- /// to have returned until `await` is called, allowing the caller to
- /// progress while waiting for any `Io` operations.
- ///
- /// The resource spawned is owned by the group; after this is called,
- /// `await` or `cancel` must be called before the group is deinitialized.
+ /// Equivalent to `Io.concurrent`, except the task is spawned in this
+ /// `Group` instead of becoming associated with a `Future`.
///
- /// This has stronger guarantee than `async`, placing restrictions on what kind
- /// of `Io` implementations are supported. By calling `async` instead, one
- /// allows, for example, stackful single-threaded blocking I/O.
+ /// The return type of `function` must be coercible to `Cancelable!void`.
///
- /// Threadsafe.
- ///
- /// See also:
- /// * `async`
- /// * `Io.concurrent`
+ /// Once this function is called, there are resources associated with the
+ /// group. To release those resources, `Group.await` or `Group.cancel` must
+ /// eventually be called.
pub fn concurrent(g: *Group, io: Io, function: anytype, args: std.meta.ArgsTuple(@TypeOf(function))) ConcurrentError!void {
const Args = @TypeOf(args);
const TypeErased = struct {
- fn start(group: *Group, context: *const anyopaque) Cancelable!void {
- _ = group;
+ fn start(context: *const anyopaque) Cancelable!void {
const args_casted: *const Args = @ptrCast(@alignCast(context));
return @call(.auto, function, args_casted.*);
}
@@ -1120,7 +1111,9 @@ pub const Group = struct {
}
/// Blocks until all tasks of the group finish. During this time,
- /// cancelation requests propagate to all members of the group.
+ /// cancelation requests propagate to all members of the group, and
+ /// will also cause `error.Canceled` to be returned when the group
+ /// does ultimately finish.
///
/// Idempotent. Not threadsafe.
///
@@ -1133,17 +1126,6 @@ pub const Group = struct {
assert(g.token.raw == null);
}
- /// Equivalent to `await` but temporarily blocks cancelation while waiting.
- pub fn awaitUncancelable(g: *Group, io: Io) void {
- const token = g.token.load(.acquire) orelse return;
- const prev = swapCancelProtection(io, .blocked);
- defer _ = swapCancelProtection(io, prev);
- io.vtable.groupAwait(io.userdata, g, token) catch |err| switch (err) {
- error.Canceled => unreachable,
- };
- assert(g.token.raw == null);
- }
-
/// Equivalent to `await` but immediately requests cancelation on all
/// members of the group.
///
@@ -1263,19 +1245,20 @@ pub fn Select(comptime U: type) type {
function: anytype,
args: std.meta.ArgsTuple(@TypeOf(function)),
) void {
- const Args = @TypeOf(args);
- const TypeErased = struct {
- fn start(group: *Group, context: *const anyopaque) Cancelable!void {
- const args_casted: *const Args = @ptrCast(@alignCast(context));
- const unerased_select: *S = @fieldParentPtr("group", group);
- const elem = @unionInit(U, @tagName(field), @call(.auto, function, args_casted.*));
- unerased_select.queue.putOneUncancelable(unerased_select.io, elem) catch |err| switch (err) {
+ const Context = struct {
+ select: *S,
+ args: @TypeOf(args),
+ fn start(type_erased_context: *const anyopaque) Cancelable!void {
+ const context: *const @This() = @ptrCast(@alignCast(type_erased_context));
+ const elem = @unionInit(U, @tagName(field), @call(.auto, function, context.args));
+ context.select.queue.putOneUncancelable(context.select.io, elem) catch |err| switch (err) {
error.Closed => unreachable,
};
}
};
+ const context: Context = .{ .select = s, .args = args };
_ = @atomicRmw(usize, &s.outstanding, .Add, 1, .monotonic);
- s.io.vtable.groupAsync(s.io.userdata, &s.group, @ptrCast(&args), .of(Args), TypeErased.start);
+ s.io.vtable.groupAsync(s.io.userdata, &s.group, @ptrCast(&context), .of(Context), Context.start);
}
/// Blocks until another task of the select finishes.
diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig
@@ -37,9 +37,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,
+worker_threads: std.atomic.Value(?*Thread),
pid: Pid = .unknown,
-robust_cancel: RobustCancel,
wsa: if (is_windows) Wsa else struct {} = .{},
@@ -105,13 +104,6 @@ pub const Environ = struct {
};
};
-pub const RobustCancel = 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,
_,
@@ -153,129 +145,507 @@ pub const UseFchmodat2 = if (have_fchmodat2 and !have_fchmodat_flags) enum {
pub const default: UseFchmodat2 = .disabled;
};
-const Thread = struct {
- /// The value that needs to be passed to pthread_kill or tgkill in order to
- /// send a signal.
- signal_id: SignaleeId,
- current_closure: ?*Closure,
- /// Only populated if `current_closure != null`. Indicates the current cancel protection mode.
- cancel_protection: Io.CancelProtection,
-
- const SignaleeId = if (std.Thread.use_pthreads) std.c.pthread_t else std.Thread.Id;
+const Runnable = struct {
+ node: std.SinglyLinkedList.Node,
+ startFn: *const fn (*Runnable, *Thread, *Threaded) void,
+};
- threadlocal var current: ?*Thread = null;
+const Group = struct {
+ ptr: *Io.Group,
- fn getCurrent(t: *Threaded) *Thread {
- return current orelse return &t.main_thread;
+ /// Returns a correctly-typed pointer to the `Io.Group.token` field.
+ ///
+ /// The status indicates how many pending tasks are in the group, whether the group has been
+ /// canceled, and whether the group has been awaited.
+ ///
+ /// Note that the zero value of `Status` intentionally represents the initial group state (empty
+ /// with no awaiters). This is a requirement of `Io.Group`.
+ fn status(g: Group) *std.atomic.Value(Status) {
+ return @ptrCast(&g.ptr.token);
+ }
+ /// Returns a correctly-typed pointer to the `Io.Group.state` field. The double-pointer here is
+ /// intentional, because the `state` field itself stores a pointer, and this function returns a
+ /// pointer to that field.
+ ///
+ /// On completion of the whole group, if `status` indicates that there is an awaiter, the last
+ /// task must increment this `u32` and do a futex wake on it to signal that awaiter.
+ fn awaiter(g: Group) **std.atomic.Value(u32) {
+ return @ptrCast(&g.ptr.state);
}
- fn checkCancel(thread: *Thread) error{Canceled}!void {
- const closure = thread.current_closure orelse return;
+ const Status = packed struct(usize) {
+ num_running: @Int(.unsigned, @bitSizeOf(usize) - 2),
+ have_awaiter: bool,
+ canceled: bool,
+ };
- switch (thread.cancel_protection) {
- .unblocked => {},
- .blocked => return,
+ const Task = struct {
+ runnable: Runnable,
+ group: *Io.Group,
+ func: *const fn (context: *const anyopaque) Io.Cancelable!void,
+ context_alignment: Alignment,
+ alloc_len: usize,
+
+ /// `Task.runnable.node` is `undefined` in the created `Task`.
+ fn create(
+ gpa: Allocator,
+ group: Group,
+ context: []const u8,
+ context_alignment: Alignment,
+ func: *const fn (context: *const anyopaque) Io.Cancelable!void,
+ ) Allocator.Error!*Task {
+ const max_context_misalignment = context_alignment.toByteUnits() -| @alignOf(Task);
+ const worst_case_context_offset = context_alignment.forward(@sizeOf(Task) + max_context_misalignment);
+ const alloc_len = worst_case_context_offset + context.len;
+
+ const task: *Task = @ptrCast(@alignCast(try gpa.alignedAlloc(u8, .of(Task), alloc_len)));
+ errdefer comptime unreachable;
+
+ task.* = .{
+ .runnable = .{
+ .node = undefined,
+ .startFn = &start,
+ },
+ .group = group.ptr,
+ .func = func,
+ .context_alignment = context_alignment,
+ .alloc_len = alloc_len,
+ };
+ @memcpy(task.contextPointer()[0..context.len], context);
+ return task;
+ }
+
+ fn destroy(task: *Task, gpa: Allocator) void {
+ const base: [*]align(@alignOf(Task)) u8 = @ptrCast(task);
+ gpa.free(base[0..task.alloc_len]);
+ }
+
+ fn contextPointer(task: *Task) [*]u8 {
+ const base: [*]u8 = @ptrCast(task);
+ const offset = task.context_alignment.forward(@intFromPtr(base) + @sizeOf(Task)) - @intFromPtr(base);
+ return base + offset;
+ }
+
+ fn start(r: *Runnable, thread: *Thread, t: *Threaded) void {
+ const task: *Task = @fieldParentPtr("runnable", r);
+ const group: Group = .{ .ptr = task.group };
+
+ // This would be a simple store, but it's upgraded to an RMW so we can use `.acquire` to
+ // enforce the ordering between this and the `group.status().load` below. Paired with
+ // the `.release` rmw on `Thread.status` in `cancelThreads`, this creates a StoreLoad
+ // barrier which guarantees that when a group is canceled, either we see the cancelation
+ // in the group status, or the canceler sees our thread status so can directly notify us
+ // of the cancelation.
+ _ = thread.status.swap(.{
+ .cancelation = .none,
+ .awaitable = .fromGroup(group.ptr),
+ }, .acquire);
+ if (group.status().load(.monotonic).canceled) {
+ thread.status.store(.{
+ .cancelation = .canceling,
+ .awaitable = .fromGroup(group.ptr),
+ }, .monotonic);
+ }
+
+ const result = task.func(task.contextPointer());
+ const cancel_acknowledged = switch (thread.status.load(.monotonic).cancelation) {
+ .none, .canceling => false,
+ .canceled => true,
+ .parked => unreachable,
+ .blocked => unreachable,
+ .blocked_windows_dns => unreachable,
+ .blocked_canceling => unreachable,
+ };
+ if (result) {
+ assert(!cancel_acknowledged); // group task acknowledged cancelation but did not return `error.Canceled`
+ } else |err| switch (err) {
+ error.Canceled => assert(cancel_acknowledged), // group task returned `error.Canceled` but was never canceled
+ }
+
+ thread.status.store(.{ .cancelation = .none, .awaitable = .null }, .monotonic);
+ const old_status = group.status().fetchSub(.{
+ .num_running = 1,
+ .have_awaiter = false,
+ .canceled = false,
+ }, .acq_rel); // acquire `group.awaiter()`, release task results
+ assert(old_status.num_running > 0);
+ if (old_status.have_awaiter and old_status.num_running == 1) {
+ const to_signal = group.awaiter().*;
+ // `awaiter` should only be modified by us. For another thread to see `num_running`
+ // drop to 0 after this point would indicate that another task started up, meaning
+ // `async`/`cancel` was racing with awaited group completion.
+ group.awaiter().* = undefined;
+ _ = to_signal.fetchAdd(1, .release); // release results
+ Thread.futexWake(&to_signal.raw, 1);
+ }
+
+ // Task completed. Self-destruct sequence initiated.
+ task.destroy(t.allocator);
}
+ };
- switch (@cmpxchgStrong(
- CancelStatus,
- &closure.cancel_status,
- .requested,
- .acknowledged,
- .acq_rel,
- .acquire,
- ) orelse return error.Canceled) {
- .requested => unreachable,
- .acknowledged => unreachable,
- .none, _ => {},
+ /// Assumes the caller has already atomically updated the group status to indicate cancelation,
+ /// and notifies any already-running threads of this cancelation.
+ fn cancelThreads(g: Group, t: *Threaded) bool {
+ var any_blocked = false;
+ var it = t.worker_threads.load(.acquire); // acquire `Thread` values
+ while (it) |thread| : (it = thread.next) {
+ // This non-mutating RMW exists for ordering reasons: see comment in `Group.Task.start` for reasons.
+ _ = thread.status.fetchOr(.{ .cancelation = @enumFromInt(0), .awaitable = .null }, .release);
+ if (thread.cancelAwaitable(.fromGroup(g.ptr))) any_blocked = true;
}
+ return any_blocked;
}
- fn beginSyscall(thread: *Thread) error{Canceled}!void {
- const closure = thread.current_closure orelse return;
-
- switch (thread.cancel_protection) {
- .unblocked => {},
- .blocked => return,
+ /// Uses `Thread.signalCanceledSyscall` to signal any threads which are still blocked in a
+ /// syscall for this group and have not observed a cancelation request yet. Returns `true` if
+ /// more signals may be necessary, in which case the caller must call this again after a delay.
+ fn signalAllCanceledSyscalls(g: Group, t: *Threaded) bool {
+ var any_signaled = false;
+ var it = t.worker_threads.load(.acquire); // acquire `Thread` values
+ while (it) |thread| : (it = thread.next) {
+ if (thread.signalCanceledSyscall(t, .fromGroup(g.ptr))) any_signaled = true;
}
+ return any_signaled;
+ }
- switch (@cmpxchgStrong(
- CancelStatus,
- &closure.cancel_status,
- .none,
- .fromSignaleeId(thread.signal_id),
- .acq_rel,
- .acquire,
- ) orelse return) {
- .none => unreachable,
- .requested => {
- @atomicStore(CancelStatus, &closure.cancel_status, .acknowledged, .release);
- return error.Canceled;
- },
- .acknowledged => return,
- _ => unreachable,
+ /// The caller has canceled `g`. Inform any threads working on that group of the cancelation if
+ /// necessary, and wait for `g` to finish (indicated by `num_completed` being incremented from 0
+ /// to 1), while sending regular signals to threads if necessary for them to unblock from any
+ /// cancelable syscalls.
+ ///
+ /// `skip_signals` means it is already known that no threads are currently working on the group
+ /// so no notifications or signals are necessary.
+ fn waitForCancelWithSignaling(
+ g: Group,
+ t: *Threaded,
+ num_completed: *std.atomic.Value(u32),
+ skip_signals: bool,
+ ) void {
+ var need_signal: bool = !skip_signals and g.cancelThreads(t);
+ var timeout_ns: u64 = 1 << 10;
+ while (true) {
+ need_signal = need_signal and g.signalAllCanceledSyscalls(t);
+ Thread.futexWaitUncancelable(&num_completed.raw, 0, if (need_signal) timeout_ns else null);
+ switch (num_completed.load(.acquire)) { // acquire task results
+ 0 => {},
+ 1 => break,
+ else => unreachable,
+ }
+ timeout_ns <<|= 1;
}
}
+};
- fn endSyscall(thread: *Thread) void {
- const closure = thread.current_closure orelse return;
+/// Trailing data:
+/// 1. context
+/// 2. result
+const Future = struct {
+ runnable: Runnable,
+ func: *const fn (context: *const anyopaque, result: *anyopaque) void,
+ status: std.atomic.Value(Status),
+ /// On completion, increment this `u32` and do a futex wake on it.
+ awaiter: *std.atomic.Value(u32),
+ context_alignment: Alignment,
+ result_offset: usize,
+ alloc_len: usize,
- switch (thread.cancel_protection) {
- .unblocked => {},
- .blocked => return,
- }
+ const Status = packed struct(usize) {
+ /// The values of this enum are chosen so that await/cancel can just OR with 0b01 and 0b11
+ /// respectively. That *does* clobber `.done`, but that's actually fine, because if the tag
+ /// is `.done` then only the awaiter is referencing this `Future` anyway.
+ tag: enum(u2) {
+ /// The future is queued or running (depending on whether `thread` is set).
+ pending = 0b00,
+ /// Like `pending`, but the future is being awaited. `Future.awaiter` is populated.
+ pending_awaited = 0b01,
+ /// Like `pending`, but the future is being canceled. `Future.awaiter` is populated.
+ pending_canceled = 0b11,
+ /// The future has already completed. `thread` is `.null`, unless the future terminated
+ /// with an acknowledged cancel request, in which case `thread` is `.all_ones`.
+ done = 0b10,
+ },
+ /// When the future begins execution, this is atomically updated from `null` to the thread running the
+ /// `Future`, so that cancelation knows which thread to cancel.
+ thread: Thread.PackedPtr,
+ };
+
+ /// `Future.runnable.node` is `undefined` in the created `Future`.
+ fn create(
+ gpa: Allocator,
+ result_len: usize,
+ result_alignment: Alignment,
+ context: []const u8,
+ context_alignment: Alignment,
+ func: *const fn (context: *const anyopaque, result: *anyopaque) void,
+ ) Allocator.Error!*Future {
+ const max_context_misalignment = context_alignment.toByteUnits() -| @alignOf(Future);
+ const worst_case_context_offset = context_alignment.forward(@sizeOf(Future) + max_context_misalignment);
+ const worst_case_result_offset = result_alignment.forward(worst_case_context_offset + context.len);
+ const alloc_len = worst_case_result_offset + result_len;
+
+ const future: *Future = @ptrCast(@alignCast(try gpa.alignedAlloc(u8, .of(Future), alloc_len)));
+ errdefer comptime unreachable;
- _ = @cmpxchgStrong(
- CancelStatus,
- &closure.cancel_status,
- .fromSignaleeId(thread.signal_id),
- .none,
- .acq_rel,
- .acquire,
- ) orelse return;
+ const actual_context_addr = context_alignment.forward(@intFromPtr(future) + @sizeOf(Future));
+ const actual_result_addr = result_alignment.forward(actual_context_addr + context.len);
+ const actual_result_offset = actual_result_addr - @intFromPtr(future);
+ future.* = .{
+ .runnable = .{
+ .node = undefined,
+ .startFn = &start,
+ },
+ .func = func,
+ .status = .init(.{
+ .tag = .pending,
+ .thread = .null,
+ }),
+ .awaiter = undefined,
+ .context_alignment = context_alignment,
+ .result_offset = actual_result_offset,
+ .alloc_len = alloc_len,
+ };
+ @memcpy(future.contextPointer()[0..context.len], context);
+ return future;
}
- fn endSyscallErrnoBug(thread: *Thread, err: posix.E) Io.UnexpectedError {
- @branchHint(.cold);
- thread.endSyscall();
- return errnoBug(err);
+ fn destroy(future: *Future, gpa: Allocator) void {
+ const base: [*]align(@alignOf(Future)) u8 = @ptrCast(future);
+ gpa.free(base[0..future.alloc_len]);
}
- fn endSyscallUnexpectedErrno(thread: *Thread, err: posix.E) Io.UnexpectedError {
- @branchHint(.cold);
- thread.endSyscall();
- return posix.unexpectedErrno(err);
+ fn resultPointer(future: *Future) [*]u8 {
+ const base: [*]u8 = @ptrCast(future);
+ return base + future.result_offset;
}
- /// inline to make error return traces slightly shallower.
- inline fn endSyscallError(thread: *Thread, err: anytype) @TypeOf(err) {
- thread.endSyscall();
- return err;
+ fn contextPointer(future: *Future) [*]u8 {
+ const base: [*]u8 = @ptrCast(future);
+ const context_offset = future.context_alignment.forward(@intFromPtr(future) + @sizeOf(Future)) - @intFromPtr(future);
+ return base + context_offset;
+ }
+
+ fn start(r: *Runnable, thread: *Thread, t: *Threaded) void {
+ _ = t;
+ const future: *Future = @fieldParentPtr("runnable", r);
+
+ thread.status.store(.{
+ .cancelation = .none,
+ .awaitable = .fromFuture(future),
+ }, .monotonic);
+ {
+ const old_status = future.status.fetchOr(.{
+ .tag = .pending,
+ .thread = .pack(thread),
+ }, .release);
+ assert(old_status.thread == .null);
+ switch (old_status.tag) {
+ .pending, .pending_awaited => {},
+ .pending_canceled => thread.status.store(.{
+ .cancelation = .canceling,
+ .awaitable = .fromFuture(future),
+ }, .monotonic),
+ .done => unreachable,
+ }
+ }
+
+ future.func(future.contextPointer(), future.resultPointer());
+
+ const had_acknowledged_cancel = switch (thread.status.load(.monotonic).cancelation) {
+ .none, .canceling => false,
+ .canceled => true,
+ .parked => unreachable,
+ .blocked => unreachable,
+ .blocked_windows_dns => unreachable,
+ .blocked_canceling => unreachable,
+ };
+ thread.status.store(.{ .cancelation = .none, .awaitable = .null }, .monotonic);
+ const old_status = future.status.swap(.{
+ .tag = .done,
+ .thread = if (had_acknowledged_cancel) .all_ones else .null,
+ }, .acq_rel); // acquire `future.awaiter`, release results
+ switch (old_status.tag) {
+ .pending => {},
+ .pending_awaited, .pending_canceled => {
+ const to_signal = future.awaiter;
+ _ = to_signal.fetchAdd(1, .release); // release results
+ Thread.futexWake(&to_signal.raw, 1);
+ },
+ .done => unreachable,
+ }
+ }
+
+ /// The caller has canceled `future`. `thread` is the thread currently running that future.
+ /// Inform `thread` of the cancelation if necessary, and wait for `future` to finish (indicated
+ /// by `num_completed` being incremented from 0 to 1), while sending regular signals to `thread`
+ /// if necessary for it to unblock from a cancelable syscall.
+ fn waitForCancelWithSignaling(
+ future: *Future,
+ t: *Threaded,
+ num_completed: *std.atomic.Value(u32),
+ thread: ?*Thread,
+ ) void {
+ var need_signal: bool = thread != null and thread.?.cancelAwaitable(.fromFuture(future));
+ var timeout_ns: u64 = 1 << 10;
+ while (true) {
+ need_signal = need_signal and thread.?.signalCanceledSyscall(t, .fromFuture(future));
+ Thread.futexWaitUncancelable(&num_completed.raw, 0, if (need_signal) timeout_ns else null);
+ switch (num_completed.load(.acquire)) { // acquire task results
+ 0 => {},
+ 1 => break,
+ else => unreachable,
+ }
+ timeout_ns <<|= 1;
+ }
+ }
+};
+
+/// A sequence of (ptr_bit_width - 3) bits which uniquely identifies a group or future. The bits are
+/// the MSBs of the `*Io.Group` or `*Future`. These things do not necessarily have 3 zero bits at
+/// the end (they are pointer-aligned, so on 32-bit targets only have 2), but because they both have
+/// a *size* of at least 8 bytes, no two groups/futures in memory at the same time will have the
+/// same value for all of these bits. In other words, given a group/future pointer, the next group
+/// or future must be at least 8 bytes later, so its address will have a different value for one of
+/// the top (ptr_bit_width - 3) bits.
+const AwaitableId = enum(@Int(.unsigned, @bitSizeOf(usize) - 3)) {
+ comptime {
+ assert(@sizeOf(Future) >= 8);
+ assert(@sizeOf(Io.Group) >= 8);
+ }
+ null = 0,
+ all_ones = std.math.maxInt(@Int(.unsigned, @bitSizeOf(usize) - 3)),
+ _,
+ const Split = packed struct(usize) { low: u3, high: AwaitableId };
+ fn fromGroup(g: *Io.Group) AwaitableId {
+ const split: Split = @bitCast(@intFromPtr(g));
+ return split.high;
}
+ fn fromFuture(f: *Future) AwaitableId {
+ const split: Split = @bitCast(@intFromPtr(f));
+ return split.high;
+ }
+};
+
+const Thread = struct {
+ next: ?*Thread,
+
+ id: std.Thread.Id,
+ handle: Handle,
+
+ status: std.atomic.Value(Status),
+
+ cancel_protection: Io.CancelProtection,
+ /// Always released when `Status.cancelation` is set to `.parked`.
+ futex_waiter: if (use_parking_futex) ?*parking_futex.Waiter else ?noreturn,
+
+ const Handle = Handle: {
+ if (std.Thread.use_pthreads) break :Handle std.c.pthread_t;
+ if (builtin.target.os.tag == .windows) break :Handle windows.HANDLE;
+ break :Handle void;
+ };
+
+ const Status = packed struct(usize) {
+ /// The specific values of these enum fields are chosen to simplify the implementation of
+ /// the transformations we need to apply to this state.
+ cancelation: enum(u3) {
+ /// The thread has not yet been canceled, and is not in a cancelable operation.
+ /// To request cancelation, just set the status to `.canceling`.
+ none = 0b000,
+
+ /// The thread is parked in a cancelable futex wait or sleep.
+ /// Only applicable if `use_parking_futex` or `use_parking_sleep`.
+ /// To request cancelation, set the status to `.canceling` and unpark the thread.
+ /// To unpark for another reason (futex wake), set the status to `.none` and unpark the thread.
+ parked = 0b001,
+
+ /// The thread is blocked in a cancelable system call.
+ /// To request cancelation, set the status to `.blocked_canceling` and repeatedly interrupt the system call until the status changes.
+ blocked = 0b011,
+
+ /// Windows-only: the thread is blocked in a call to `GetAddrInfoExW`.
+ /// To request cancelation, set the status to `.canceling` and call `GetAddrInfoExCancel`.
+ blocked_windows_dns = 0b010,
+
+ /// The thread has an outstanding cancelation request but is not in a cancelable operation.
+ /// When it acknowledges the cancelation, it will set the status to `.canceled`.
+ canceling = 0b110,
+
+ /// The thread has received and acknowledged a cancelation request.
+ /// If `recancel` is called, the status will revert to `.canceling`, but otherwise, the status
+ /// will not change for the remainder of this task's execution.
+ canceled = 0b111,
+
+ /// The thread is blocked in a cancelable system call, and is being canceled. The thread which triggered the cancelation will send signals to this thread
+ /// until its status changes.
+ blocked_canceling = 0b101,
+ },
+
+ /// We cannot turn this value back into a pointer. Instead, it exists so that a task can be
+ /// canceled by a cmpxchg on thread status: if it is running the task we want to cancel,
+ /// then update the `cancelation` field.
+ awaitable: AwaitableId,
+ };
- fn currentSignalId() SignaleeId {
- return if (std.Thread.use_pthreads) std.c.pthread_self() else std.Thread.getCurrentId();
+ const SignaleeId = if (std.Thread.use_pthreads) std.c.pthread_t else std.Thread.Id;
+
+ threadlocal var current: ?*Thread = null;
+
+ /// The thread is neither in a syscall nor entering one, but we want to check for cancelation
+ /// anyway. If there is a pending cancel request, acknowledge it and return `error.Canceled`.
+ fn checkCancel() Io.Cancelable!void {
+ const thread = Thread.current orelse return;
+ switch (thread.cancel_protection) {
+ .blocked => return,
+ .unblocked => {},
+ }
+ // Here, unlike `Syscall.checkCancel`, it's not particularly likely that we're canceled, so
+ // it seems preferable to do a cheap atomic load and, in the unlikely case, a separate store
+ // to acknowledge. Besides, the state transitions we need here can't be done with one atomic
+ // OR/AND/XOR on `Status.cancelation`, so we don't actually have any other option.
+ const status = thread.status.load(.monotonic);
+ switch (status.cancelation) {
+ .parked => unreachable,
+ .blocked => unreachable,
+ .blocked_windows_dns => unreachable,
+ .blocked_canceling => unreachable,
+ .none, .canceled => {},
+ .canceling => {
+ thread.status.store(.{
+ .cancelation = .canceled,
+ .awaitable = status.awaitable,
+ }, .monotonic);
+ return error.Canceled;
+ },
+ }
}
- fn futexWaitUncancelable(ptr: *const u32, expect: u32) void {
- return Thread.futexWaitTimed(null, ptr, expect, null) catch unreachable;
+ fn futexWaitUncancelable(ptr: *const u32, expect: u32, timeout_ns: ?u64) void {
+ return Thread.futexWaitInner(ptr, expect, true, timeout_ns) catch unreachable;
}
- fn futexWait(thread: *Thread, ptr: *const u32, expect: u32) Io.Cancelable!void {
- return Thread.futexWaitTimed(thread, ptr, expect, null) catch |err| switch (err) {
- error.Canceled => return error.Canceled,
- error.Timeout => unreachable,
- };
+ fn futexWait(ptr: *const u32, expect: u32, timeout_ns: ?u64) Io.Cancelable!void {
+ return Thread.futexWaitInner(ptr, expect, false, timeout_ns);
}
- fn futexWaitTimed(thread: ?*Thread, ptr: *const u32, expect: u32, timeout_ns: ?u64) Io.Cancelable!void {
+ fn futexWaitInner(ptr: *const u32, expect: u32, uncancelable: bool, timeout_ns: ?u64) Io.Cancelable!void {
@branchHint(.cold);
if (builtin.single_threaded) unreachable; // nobody would ever wake us
- if (builtin.cpu.arch.isWasm()) {
+ if (use_parking_futex) {
+ return parking_futex.wait(
+ ptr,
+ expect,
+ uncancelable,
+ if (timeout_ns) |ns| .{ .duration = .{
+ .raw = .fromNanoseconds(ns),
+ .clock = .boot,
+ } } else .none,
+ );
+ } else if (builtin.cpu.arch.isWasm()) {
comptime assert(builtin.cpu.has(.wasm, .atomics));
- if (thread) |t| try t.checkCancel();
+ // TODO implement cancelation for WASM futex waits by signaling the futex
+ if (!uncancelable) try Thread.checkCancel();
const to: i64 = if (timeout_ns) |ns| ns else -1;
const signed_expect: i32 = @bitCast(expect);
const result = asm volatile (
@@ -303,9 +673,9 @@ const Thread = struct {
ts_buffer = timestampToPosix(ns);
break :ts &ts_buffer;
} else null;
- if (thread) |t| try t.beginSyscall();
+ const syscall: Syscall = if (uncancelable) .{ .thread = null } else try .start();
const rc = linux.futex_4arg(ptr, .{ .cmd = .WAIT, .private = true }, expect, ts);
- if (thread) |t| t.endSyscall();
+ syscall.finish();
switch (linux.errno(rc)) {
.SUCCESS => {}, // notified by `wake()`
.INTR => {}, // caller's responsibility to retry
@@ -322,7 +692,7 @@ const Thread = struct {
.op = .COMPARE_AND_WAIT,
.NO_ERRNO = true,
};
- if (thread) |t| try t.beginSyscall();
+ const syscall: Syscall = if (uncancelable) .{ .thread = null } else try .start();
const status = switch (darwin_supports_ulock_wait2) {
true => c.__ulock_wait2(flags, ptr, expect, ns: {
const ns = timeout_ns orelse break :ns 0;
@@ -336,7 +706,7 @@ const Thread = struct {
break :us us;
}),
};
- if (thread) |t| t.endSyscall();
+ syscall.finish();
if (status >= 0) return;
switch (@as(c.E, @enumFromInt(-status))) {
.INTR => {}, // spurious wake
@@ -348,24 +718,6 @@ const Thread = struct {
else => recoverableOsBugDetected(),
}
},
- .windows => {
- var timeout_value: windows.LARGE_INTEGER = undefined;
- var timeout_ptr: ?*const windows.LARGE_INTEGER = null;
- // NTDLL functions work with time in units of 100 nanoseconds.
- // Positive values are absolute deadlines while negative values are relative durations.
- if (timeout_ns) |delay| {
- timeout_value = @as(windows.LARGE_INTEGER, @intCast(delay / 100));
- timeout_value = -timeout_value;
- timeout_ptr = &timeout_value;
- }
- if (thread) |t| try t.checkCancel();
- switch (windows.ntdll.RtlWaitOnAddress(ptr, &expect, @sizeOf(@TypeOf(expect)), timeout_ptr)) {
- .SUCCESS => {},
- .CANCELLED => {},
- .TIMEOUT => {}, // timeout
- else => recoverableOsBugDetected(),
- }
- },
.freebsd => {
const flags = @intFromEnum(std.c.UMTX_OP.WAIT_UINT_PRIVATE);
var tm_size: usize = 0;
@@ -378,9 +730,9 @@ const Thread = struct {
tm.clockid = .MONOTONIC;
tm.timeout = timestampToPosix(ns);
}
- if (thread) |t| try t.beginSyscall();
+ const syscall: Syscall = if (uncancelable) .{ .thread = null } else try .start();
const rc = std.c._umtx_op(@intFromPtr(ptr), flags, @as(c_ulong, expect), tm_size, @intFromPtr(tm_ptr));
- if (thread) |t| t.endSyscall();
+ syscall.finish();
if (is_debug) switch (posix.errno(rc)) {
.SUCCESS => {},
.FAULT => unreachable, // one of the args points to invalid memory
@@ -397,7 +749,7 @@ const Thread = struct {
tm_ptr = &tm;
tm = timestampToPosix(ns);
}
- if (thread) |t| try t.beginSyscall();
+ const syscall: Syscall = if (uncancelable) .{ .thread = null } else try .start();
const rc = std.c.futex(
ptr,
std.c.FUTEX.WAIT | std.c.FUTEX.PRIVATE_FLAG,
@@ -405,7 +757,7 @@ const Thread = struct {
tm_ptr,
null, // uaddr2 is ignored
);
- if (thread) |t| t.endSyscall();
+ syscall.finish();
if (is_debug) switch (posix.errno(rc)) {
.SUCCESS => {},
.NOSYS => unreachable, // constant op known good value
@@ -424,9 +776,9 @@ const Thread = struct {
} else {
timeout_us = 0;
}
- if (thread) |t| try t.beginSyscall();
+ const syscall: Syscall = if (uncancelable) .{ .thread = null } else try .start();
const rc = std.c.umtx_sleep(@ptrCast(ptr), @bitCast(expect), timeout_us);
- if (thread) |t| t.endSyscall();
+ syscall.finish();
if (is_debug) switch (std.posix.errno(rc)) {
.SUCCESS => {},
.BUSY => {}, // ptr != expect
@@ -436,14 +788,7 @@ const Thread = struct {
else => unreachable,
};
},
- else => if (std.Thread.use_pthreads) {
- // TODO integrate the following function being called with robust cancelation.
- return pthreads_futex.wait(ptr, expect, timeout_ns) catch |err| switch (err) {
- error.Timeout => {},
- };
- } else {
- @compileError("unimplemented: futexWait");
- },
+ else => @compileError("unimplemented: futexWait"),
}
}
@@ -453,7 +798,9 @@ const Thread = struct {
if (builtin.single_threaded) return; // nothing to wake up
- if (builtin.cpu.arch.isWasm()) {
+ if (use_parking_futex) {
+ return parking_futex.wake(ptr, max_waiters);
+ } else if (builtin.cpu.arch.isWasm()) {
comptime assert(builtin.cpu.has(.wasm, .atomics));
const woken_count = asm volatile (
\\local.get %[ptr]
@@ -498,12 +845,6 @@ const Thread = struct {
}
}
},
- .windows => {
- switch (max_waiters) {
- 1 => windows.ntdll.RtlWakeAddressSingle(ptr),
- else => windows.ntdll.RtlWakeAddressAll(ptr),
- }
- },
.freebsd => {
const rc = std.c._umtx_op(
@intFromPtr(ptr),
@@ -536,130 +877,239 @@ const Thread = struct {
@min(max_waiters, std.math.maxInt(c_int)),
);
},
- else => if (std.Thread.use_pthreads) {
- return pthreads_futex.wake(ptr, max_waiters);
- } else {
- @compileError("unimplemented: futexWake");
- },
+ else => @compileError("unimplemented: futexWake"),
}
}
-};
-
-const max_iovecs_len = 8;
-const splat_buffer_size = 64;
-comptime {
- if (@TypeOf(posix.IOV_MAX) != void) assert(max_iovecs_len <= posix.IOV_MAX);
-}
+ /// Cancels `thread` if it is working on `awaitable`.
+ ///
+ /// It is possible that `thread` gets canceled by this function, but is blocked in a syscall. In
+ /// that case, the thread may need to be sent a signal to interrupt the call. This function will
+ /// return `true` to indicate this, in which case the caller must call `signalCanceledSyscall`.
+ fn cancelAwaitable(thread: *Thread, awaitable: AwaitableId) bool {
+ var status = thread.status.load(.monotonic);
+ while (true) {
+ if (status.awaitable != awaitable) return false; // thread is working on something else
+ status = switch (status.cancelation) {
+ .none => thread.status.cmpxchgWeak(
+ .{ .cancelation = .none, .awaitable = awaitable },
+ .{ .cancelation = .canceling, .awaitable = awaitable },
+ .monotonic,
+ .monotonic,
+ ) orelse return false,
+
+ .parked => thread.status.cmpxchgWeak(
+ .{ .cancelation = .parked, .awaitable = awaitable },
+ .{ .cancelation = .canceling, .awaitable = awaitable },
+ .acquire, // acquire `thread.futex_waiter`
+ .monotonic,
+ ) orelse {
+ if (!use_parking_futex and !use_parking_sleep) unreachable;
+ if (thread.futex_waiter) |futex_waiter| {
+ parking_futex.removeCanceledWaiter(futex_waiter);
+ }
+ unpark(&.{thread.id}, null);
+ return false;
+ },
-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,
- /// 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.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.
- _,
+ .blocked => thread.status.cmpxchgWeak(
+ .{ .cancelation = .blocked, .awaitable = awaitable },
+ .{ .cancelation = .blocked_canceling, .awaitable = awaitable },
+ .monotonic,
+ .monotonic,
+ ) orelse return true,
+
+ .blocked_windows_dns => thread.status.cmpxchgWeak(
+ .{ .cancelation = .blocked_windows_dns, .awaitable = awaitable },
+ .{ .cancelation = .canceling, .awaitable = awaitable },
+ .monotonic,
+ .monotonic,
+ ) orelse {
+ if (builtin.target.os.tag != .windows) unreachable;
+ if (true) {
+ // TODO: cancel Windows DNS queries. This code path is currently impossible
+ // as `netLookupFallible` doesn't actually use `.blocked_windows_dns` yet.
+ unreachable;
+ }
+ return false;
+ },
- const Unpacked = union(enum) {
- none,
- requested,
- acknowledged,
- signal_id: Thread.SignaleeId,
- };
-
- 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)),
- },
- };
- }
+ .canceling, .canceled => {
+ // This can happen when the task start raced with the cancelation, so the thread
+ // saw the cancelation on the future/group *and* we are trying to signal the
+ // thread here.
+ return false;
+ },
- fn fromSignaleeId(signal_id: Thread.SignaleeId) CancelStatus {
- return if (std.Thread.use_pthreads)
- @enumFromInt(@intFromPtr(signal_id))
- else
- @enumFromInt(signal_id);
+ .blocked_canceling => unreachable,
+ };
+ }
}
-};
-
-const Closure = struct {
- start: Start,
- node: std.SinglyLinkedList.Node = .{},
- cancel_status: CancelStatus,
- 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()) {
- .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.
-
- // 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.
- 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: {
+ /// Sends a signal to `thread` if it is still blocked in a syscall (i.e. has not yet observed
+ /// the cancelation request from `cancelAwaitable`).
+ ///
+ /// Unfortunately, the signal could arrive before the syscall actually starts, so the interrupt
+ /// is missed. To handle this, we may need to send multiple signals. As such, if this function
+ /// returns `true`, then it should be called again after a short delay to send another signal if
+ /// the thread is still blocked. For the implementation, `Future.waitForCancelWithSignaling` and
+ /// `Group.waitForCancelWithSignaling`: they use exponential backoff starting at a 1us delay and
+ /// doubling each call. In practice, it is rare to send more than one signal.
+ fn signalCanceledSyscall(thread: *Thread, t: *Threaded, awaitable: AwaitableId) bool {
+ const bad_status: Status = .{ .cancelation = .blocked_canceling, .awaitable = awaitable };
+ if (thread.status.load(.monotonic) != bad_status) return false;
+
+ // The thread ID and/or handle can be read non-atomically because they never change and were
+ // released by the store that made `thread` available to us.
+
+ if (std.Thread.use_pthreads) {
+ return switch (std.c.pthread_kill(thread.handle, .IO)) {
+ 0 => true,
+ else => false,
+ };
+ } else switch (builtin.target.os.tag) {
+ .linux => {
+ const pid: posix.pid_t = pid: {
const cached_pid = @atomicLoad(Pid, &t.pid, .monotonic);
- if (cached_pid != .unknown) break :p @intFromEnum(cached_pid);
+ if (cached_pid != .unknown) break :pid @intFromEnum(cached_pid);
const pid = std.os.linux.getpid();
@atomicStore(Pid, &t.pid, @enumFromInt(pid), .monotonic);
- break :p pid;
+ break :pid pid;
};
- if (std.os.linux.tgkill(pid, @bitCast(signal_id), .IO) != 0) return;
- } else {
- return;
- }
+ return switch (std.os.linux.tgkill(pid, @bitCast(thread.id), .IO)) {
+ 0 => true,
+ else => false,
+ };
+ },
+ .windows => {
+ var iosb: windows.IO_STATUS_BLOCK = undefined;
+ return switch (windows.ntdll.NtCancelSynchronousIoFile(thread.handle, null, &iosb)) {
+ .NOT_FOUND => true, // this might mean the operation hasn't started yet
+ .SUCCESS => false, // the OS confirmed that our cancelation worked
+ else => false,
+ };
+ },
+ else => return false,
+ }
+ }
- if (t.robust_cancel != .enabled) return;
+ /// Like a `*Thread`, but 2 bits smaller than a pointer (because the LSBs are always 0 due to
+ /// alignment) so that those two bits can be used in a `packed struct`.
+ const PackedPtr = enum(@Int(.unsigned, @bitSizeOf(usize) - 2)) {
+ null = 0,
+ all_ones = std.math.maxInt(@Int(.unsigned, @bitSizeOf(usize) - 2)),
+ _,
- var timespec: posix.timespec = .{
- .sec = 0,
- .nsec = @as(isize, 1) << @intCast(attempt_index),
- };
- if (native_os == .linux) {
- _ = std.os.linux.clock_nanosleep(posix.CLOCK.MONOTONIC, .{ .ABSTIME = false }, ×pec, ×pec);
- } else {
- _ = posix.system.nanosleep(×pec, ×pec);
- }
+ const Split = packed struct(usize) { low: u2, high: PackedPtr };
+ fn pack(ptr: *Thread) PackedPtr {
+ const split: Split = @bitCast(@intFromPtr(ptr));
+ assert(split.low == 0);
+ return split.high;
+ }
+ fn unpack(ptr: PackedPtr) ?*Thread {
+ const split: Split = .{ .low = 0, .high = ptr };
+ return @ptrFromInt(@as(usize, @bitCast(split)));
+ }
+ };
+};
- 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,
- }
+const Syscall = struct {
+ thread: ?*Thread,
+ /// Marks entry to a syscall region. This should be tightly scoped around the actual syscall
+ /// to minimize races. The syscall must be marked as "finished" by `checkCancel`, `finish`,
+ /// or one of the wrappers of `finish`.
+ fn start() Io.Cancelable!Syscall {
+ const thread = Thread.current orelse return .{ .thread = null };
+ switch (thread.cancel_protection) {
+ .blocked => return .{ .thread = null },
+ .unblocked => {},
}
+ switch (thread.status.fetchOr(.{
+ .cancelation = @enumFromInt(0b011),
+ .awaitable = .null,
+ }, .monotonic).cancelation) {
+ .parked => unreachable,
+ .blocked => unreachable,
+ .blocked_windows_dns => unreachable,
+ .blocked_canceling => unreachable,
+ .none => return .{ .thread = thread }, // new status is `.blocked`
+ .canceling => return error.Canceled, // new status is `.canceled`
+ .canceled => return .{ .thread = null }, // new status is `.canceled` (unchanged)
+ }
+ }
+ /// Checks whether this syscall has been canceled. This should be called when a syscall is
+ /// interrupted through a mechanism which may indicate cancelation, or may be spurious. If
+ /// the syscall was canceled, it is finished and `error.Canceled` is returned. Otherwise,
+ /// the syscall is not marked finished, and the caller should retry.
+ fn checkCancel(s: Syscall) Io.Cancelable!void {
+ const thread = s.thread orelse return;
+ switch (thread.status.fetchOr(.{
+ .cancelation = @enumFromInt(0b010),
+ .awaitable = .null,
+ }, .monotonic).cancelation) {
+ .none => unreachable,
+ .parked => unreachable,
+ .blocked_windows_dns => unreachable,
+ .canceling => unreachable,
+ .canceled => unreachable,
+ .blocked => {}, // new status is `.blocked` (unchanged)
+ .blocked_canceling => return error.Canceled, // new status is `.canceled`
+ }
+ }
+ /// Marks this syscall as finished.
+ fn finish(s: Syscall) void {
+ const thread = s.thread orelse return;
+ switch (thread.status.fetchXor(.{
+ .cancelation = @enumFromInt(0b011),
+ .awaitable = .null,
+ }, .monotonic).cancelation) {
+ .none => unreachable,
+ .parked => unreachable,
+ .blocked_windows_dns => unreachable,
+ .canceling => unreachable,
+ .canceled => unreachable,
+ .blocked => {}, // new status is `.none`
+ .blocked_canceling => {}, // new status is `.canceling`
+ }
+ }
+ /// Convenience wrapper which calls `finish`, then returns `err`.
+ fn fail(s: Syscall, err: anytype) @TypeOf(err) {
+ s.finish();
+ return err;
+ }
+ /// Convenience wrapper which calls `finish`, then calls `Threaded.errnoBug`.
+ fn errnoBug(s: Syscall, err: posix.E) Io.UnexpectedError {
+ @branchHint(.cold);
+ s.finish();
+ return Threaded.errnoBug(err);
+ }
+ /// Convenience wrapper which calls `finish`, then calls `posix.unexpectedErrno`.
+ fn unexpectedErrno(s: Syscall, err: posix.E) Io.UnexpectedError {
+ @branchHint(.cold);
+ s.finish();
+ return posix.unexpectedErrno(err);
+ }
+ /// Convenience wrapper which calls `finish`, then calls `windows.statusBug`.
+ fn ntstatusBug(s: Syscall, status: windows.NTSTATUS) Io.UnexpectedError {
+ @branchHint(.cold);
+ s.finish();
+ return windows.statusBug(status);
+ }
+ /// Convenience wrapper which calls `finish`, then calls `windows.unexpectedStatus`.
+ fn unexpectedNtstatus(s: Syscall, status: windows.NTSTATUS) Io.UnexpectedError {
+ @branchHint(.cold);
+ s.finish();
+ return windows.unexpectedStatus(status);
}
};
+const max_iovecs_len = 8;
+const splat_buffer_size = 64;
+
+comptime {
+ if (@TypeOf(posix.IOV_MAX) != void) assert(max_iovecs_len <= posix.IOV_MAX);
+}
+
pub const InitOptions = struct {
/// Affects how many bytes are memory-mapped for threads.
stack_size: usize = std.Thread.SpawnConfig.default_stack_size,
@@ -681,17 +1131,6 @@ pub const InitOptions = struct {
/// concurrent tasks. After this number, calls to `Io.concurrent` return
/// `error.ConcurrencyUnavailable`.
concurrent_limit: Io.Limit = .unlimited,
- /// 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.
- robust_cancel: RobustCancel = .disabled,
/// Affects the following operations:
/// * `processExecutablePath` on OpenBSD and Haiku.
argv0: Argv0 = .{},
@@ -727,14 +1166,9 @@ pub fn init(
.old_sig_io = undefined,
.old_sig_pipe = undefined,
.have_signal_handler = false,
- .main_thread = .{
- .signal_id = Thread.currentSignalId(),
- .current_closure = null,
- .cancel_protection = .unblocked,
- },
.argv0 = options.argv0,
.environ = options.environ,
- .robust_cancel = options.robust_cancel,
+ .worker_threads = .init(null),
};
if (posix.Sigaction != void) {
@@ -768,14 +1202,9 @@ pub const init_single_threaded: Threaded = .{
.old_sig_io = undefined,
.old_sig_pipe = undefined,
.have_signal_handler = false,
- .main_thread = .{
- .signal_id = undefined,
- .current_closure = null,
- .cancel_protection = .unblocked,
- },
- .robust_cancel = .disabled,
.argv0 = .{},
.environ = .{},
+ .worker_threads = .init(null),
};
var global_single_threaded_instance: Threaded = .init_single_threaded;
@@ -822,22 +1251,70 @@ fn join(t: *Threaded) void {
fn worker(t: *Threaded) void {
var thread: Thread = .{
- .signal_id = Thread.currentSignalId(),
- .current_closure = null,
+ .next = undefined,
+ .id = std.Thread.getCurrentId(),
+ .handle = handle: {
+ if (std.Thread.use_pthreads) break :handle std.c.pthread_self();
+ if (builtin.target.os.tag == .windows) break :handle undefined; // populated below
+ },
+ .status = .init(.{
+ .cancelation = .none,
+ .awaitable = .null,
+ }),
.cancel_protection = .unblocked,
+ .futex_waiter = undefined,
};
Thread.current = &thread;
+ if (builtin.target.os.tag == .windows) {
+ assert(windows.ntdll.NtOpenThread(
+ &thread.handle,
+ .{
+ .SPECIFIC = .{
+ .THREAD = .{
+ .TERMINATE = true, // for `NtCancelSynchronousIoFile`
+ },
+ },
+ },
+ &.{
+ .Length = @sizeOf(windows.OBJECT_ATTRIBUTES),
+ .RootDirectory = null,
+ .ObjectName = null,
+ .Attributes = .{},
+ .SecurityDescriptor = null,
+ .SecurityQualityOfService = null,
+ },
+ &windows.teb().ClientId,
+ ) == .SUCCESS);
+ }
+ defer if (builtin.target.os.tag == .windows) {
+ windows.CloseHandle(thread.handle);
+ };
+
+ {
+ var head = t.worker_threads.load(.monotonic);
+ while (true) {
+ thread.next = head;
+ head = t.worker_threads.cmpxchgWeak(
+ head,
+ &thread,
+ .release,
+ .monotonic,
+ ) orelse break;
+ }
+ }
+
defer t.wait_group.finish();
t.mutex.lock();
defer t.mutex.unlock();
while (true) {
- while (t.run_queue.popFirst()) |closure_node| {
+ while (t.run_queue.popFirst()) |runnable_node| {
t.mutex.unlock();
- const closure: *Closure = @fieldParentPtr("node", closure_node);
- closure.start(closure, t);
+ thread.cancel_protection = .unblocked;
+ const runnable: *Runnable = @fieldParentPtr("node", runnable_node);
+ runnable.startFn(runnable, &thread, t);
t.mutex.lock();
t.busy_count -= 1;
}
@@ -1145,103 +1622,6 @@ const linux_copy_file_range_use_c = std.c.versionCheck(if (builtin.abi.isAndroid
});
const linux_copy_file_range_sys = if (linux_copy_file_range_use_c) std.c else std.os.linux;
-/// Trailing data:
-/// 1. context
-/// 2. result
-const AsyncClosure = struct {
- closure: Closure,
- func: *const fn (context: *anyopaque, result: *anyopaque) void,
- event: Io.Event,
- select_condition: ?*Io.Event,
- context_alignment: Alignment,
- result_offset: usize,
- alloc_len: usize,
-
- const done_event: *Io.Event = @ptrFromInt(@alignOf(Io.Event));
-
- fn start(closure: *Closure, t: *Threaded) void {
- const ac: *AsyncClosure = @alignCast(@fieldParentPtr("closure", closure));
- const current_thread = Thread.getCurrent(t);
-
- current_thread.current_closure = closure;
- current_thread.cancel_protection = .unblocked;
-
- ac.func(ac.contextPointer(), ac.resultPointer());
-
- current_thread.current_closure = null;
- current_thread.cancel_protection = undefined;
-
- if (@atomicRmw(?*Io.Event, &ac.select_condition, .Xchg, done_event, .release)) |select_event| {
- assert(select_event != done_event);
- select_event.set(ioBasic(t));
- }
- ac.event.set(ioBasic(t));
- }
-
- fn resultPointer(ac: *AsyncClosure) [*]u8 {
- const base: [*]u8 = @ptrCast(ac);
- return base + ac.result_offset;
- }
-
- fn contextPointer(ac: *AsyncClosure) [*]u8 {
- const base: [*]u8 = @ptrCast(ac);
- const context_offset = ac.context_alignment.forward(@intFromPtr(ac) + @sizeOf(AsyncClosure)) - @intFromPtr(ac);
- return base + context_offset;
- }
-
- fn init(
- gpa: Allocator,
- result_len: usize,
- result_alignment: Alignment,
- context: []const u8,
- context_alignment: Alignment,
- func: *const fn (context: *const anyopaque, result: *anyopaque) void,
- ) Allocator.Error!*AsyncClosure {
- const max_context_misalignment = context_alignment.toByteUnits() -| @alignOf(AsyncClosure);
- const worst_case_context_offset = context_alignment.forward(@sizeOf(AsyncClosure) + max_context_misalignment);
- const worst_case_result_offset = result_alignment.forward(worst_case_context_offset + context.len);
- const alloc_len = worst_case_result_offset + result_len;
-
- const ac: *AsyncClosure = @ptrCast(@alignCast(try gpa.alignedAlloc(u8, .of(AsyncClosure), alloc_len)));
- errdefer comptime unreachable;
-
- const actual_context_addr = context_alignment.forward(@intFromPtr(ac) + @sizeOf(AsyncClosure));
- const actual_result_addr = result_alignment.forward(actual_context_addr + context.len);
- const actual_result_offset = actual_result_addr - @intFromPtr(ac);
- ac.* = .{
- .closure = .{
- .cancel_status = .none,
- .start = start,
- },
- .func = func,
- .context_alignment = context_alignment,
- .result_offset = actual_result_offset,
- .alloc_len = alloc_len,
- .event = .unset,
- .select_condition = null,
- };
- @memcpy(ac.contextPointer()[0..context.len], context);
- return ac;
- }
-
- fn waitAndDeinit(ac: *AsyncClosure, t: *Threaded, result: []u8) void {
- ac.event.wait(ioBasic(t)) catch |err| switch (err) {
- error.Canceled => {
- ac.closure.requestCancel(t);
- ac.event.waitUncancelable(ioBasic(t));
- recancel(t);
- },
- };
- @memcpy(result, ac.resultPointer()[0..result.len]);
- ac.deinit(t.allocator);
- }
-
- fn deinit(ac: *AsyncClosure, gpa: Allocator) void {
- const base: [*]align(@alignOf(AsyncClosure)) u8 = @ptrCast(ac);
- gpa.free(base[0..ac.alloc_len]);
- }
-};
-
fn async(
userdata: ?*anyopaque,
result: []u8,
@@ -1255,10 +1635,13 @@ fn async(
start(context.ptr, result.ptr);
return null;
}
+
const gpa = t.allocator;
- const ac = AsyncClosure.init(gpa, result.len, result_alignment, context, context_alignment, start) catch {
- start(context.ptr, result.ptr);
- return null;
+ const future = Future.create(gpa, result.len, result_alignment, context, context_alignment, start) catch |err| switch (err) {
+ error.OutOfMemory => {
+ start(context.ptr, result.ptr);
+ return null;
+ },
};
t.mutex.lock();
@@ -1267,7 +1650,7 @@ fn async(
if (busy_count >= @intFromEnum(t.async_limit)) {
t.mutex.unlock();
- ac.deinit(gpa);
+ future.destroy(gpa);
start(context.ptr, result.ptr);
return null;
}
@@ -1281,17 +1664,18 @@ fn async(
t.wait_group.finish();
t.busy_count = busy_count;
t.mutex.unlock();
- ac.deinit(gpa);
+ future.destroy(gpa);
start(context.ptr, result.ptr);
return null;
};
thread.detach();
}
- t.run_queue.prepend(&ac.closure.node);
+ t.run_queue.prepend(&future.runnable.node);
+
t.mutex.unlock();
t.cond.signal();
- return @ptrCast(ac);
+ return @ptrCast(future);
}
fn concurrent(
@@ -1307,9 +1691,10 @@ fn concurrent(
const t: *Threaded = @ptrCast(@alignCast(userdata));
const gpa = t.allocator;
- const ac = AsyncClosure.init(gpa, result_len, result_alignment, context, context_alignment, start) catch
- return error.ConcurrencyUnavailable;
- errdefer ac.deinit(gpa);
+ const future = Future.create(gpa, result_len, result_alignment, context, context_alignment, start) catch |err| switch (err) {
+ error.OutOfMemory => return error.ConcurrencyUnavailable,
+ };
+ errdefer future.destroy(gpa);
t.mutex.lock();
defer t.mutex.unlock();
@@ -1329,110 +1714,32 @@ fn concurrent(
const thread = std.Thread.spawn(.{ .stack_size = t.stack_size }, worker, .{t}) catch
return error.ConcurrencyUnavailable;
+
thread.detach();
}
- t.run_queue.prepend(&ac.closure.node);
+ t.run_queue.prepend(&future.runnable.node);
+
t.cond.signal();
- return @ptrCast(ac);
+ return @ptrCast(future);
}
-const GroupClosure = struct {
- closure: Closure,
- group: *Io.Group,
- /// Points to sibling `GroupClosure`. Used for walking the group to cancel all.
- node: std.SinglyLinkedList.Node,
- func: *const fn (*Io.Group, context: *anyopaque) Io.Cancelable!void,
- context_alignment: Alignment,
- alloc_len: usize,
-
- fn start(closure: *Closure, t: *Threaded) void {
- const gc: *GroupClosure = @alignCast(@fieldParentPtr("closure", closure));
- const current_thread = Thread.getCurrent(t);
- const group = gc.group;
- const group_state: *std.atomic.Value(usize) = @ptrCast(&group.state);
- const event: *Io.Event = @ptrCast(&group.context);
- current_thread.current_closure = closure;
- current_thread.cancel_protection = .unblocked;
-
- assertResult(closure, gc.func(group, gc.contextPointer()));
-
- current_thread.current_closure = null;
- current_thread.cancel_protection = undefined;
-
- const prev_state = group_state.fetchSub(sync_one_pending, .acq_rel);
- assert((prev_state / sync_one_pending) > 0);
- if (prev_state == (sync_one_pending | sync_is_waiting)) event.set(ioBasic(t));
- }
-
- fn assertResult(closure: *Closure, result: Io.Cancelable!void) void {
- if (result) |_| switch (closure.cancel_status.unpack()) {
- .none, .requested => {},
- .acknowledged => unreachable, // task illegally swallowed error.Canceled
- .signal_id => unreachable,
- } else |err| switch (err) {
- error.Canceled => assert(closure.cancel_status == .acknowledged),
- }
- }
-
- fn contextPointer(gc: *GroupClosure) [*]u8 {
- const base: [*]u8 = @ptrCast(gc);
- const context_offset = gc.context_alignment.forward(@intFromPtr(gc) + @sizeOf(GroupClosure)) - @intFromPtr(gc);
- return base + context_offset;
- }
-
- /// Does not initialize the `node` field.
- fn init(
- gpa: Allocator,
- group: *Io.Group,
- context: []const u8,
- context_alignment: Alignment,
- func: *const fn (*Io.Group, context: *const anyopaque) Io.Cancelable!void,
- ) Allocator.Error!*GroupClosure {
- const max_context_misalignment = context_alignment.toByteUnits() -| @alignOf(GroupClosure);
- const worst_case_context_offset = context_alignment.forward(@sizeOf(GroupClosure) + max_context_misalignment);
- const alloc_len = worst_case_context_offset + context.len;
-
- const gc: *GroupClosure = @ptrCast(@alignCast(try gpa.alignedAlloc(u8, .of(GroupClosure), alloc_len)));
- errdefer comptime unreachable;
-
- gc.* = .{
- .closure = .{
- .cancel_status = .none,
- .start = start,
- },
- .group = group,
- .node = undefined,
- .func = func,
- .context_alignment = context_alignment,
- .alloc_len = alloc_len,
- };
- @memcpy(gc.contextPointer()[0..context.len], context);
- return gc;
- }
-
- fn deinit(gc: *GroupClosure, gpa: Allocator) void {
- const base: [*]align(@alignOf(GroupClosure)) u8 = @ptrCast(gc);
- gpa.free(base[0..gc.alloc_len]);
- }
-
- const sync_is_waiting: usize = 1 << 0;
- const sync_one_pending: usize = 1 << 1;
-};
-
fn groupAsync(
userdata: ?*anyopaque,
- group: *Io.Group,
+ type_erased: *Io.Group,
context: []const u8,
context_alignment: Alignment,
- start: *const fn (*Io.Group, context: *const anyopaque) Io.Cancelable!void,
+ start: *const fn (context: *const anyopaque) Io.Cancelable!void,
) void {
const t: *Threaded = @ptrCast(@alignCast(userdata));
- if (builtin.single_threaded) return start(group, context.ptr) catch unreachable;
+ const g: Group = .{ .ptr = type_erased };
+
+ if (builtin.single_threaded) return groupAsyncEager(start, context.ptr);
const gpa = t.allocator;
- const gc = GroupClosure.init(gpa, group, context, context_alignment, start) catch
- return t.assertGroupResult(start(group, context.ptr));
+ const task = Group.Task.create(gpa, g, context, context_alignment, start) catch |err| switch (err) {
+ error.OutOfMemory => return groupAsyncEager(start, context.ptr),
+ };
t.mutex.lock();
@@ -1440,8 +1747,8 @@ fn groupAsync(
if (busy_count >= @intFromEnum(t.async_limit)) {
t.mutex.unlock();
- gc.deinit(gpa);
- return t.assertGroupResult(start(group, context.ptr));
+ task.destroy(gpa);
+ return groupAsyncEager(start, context.ptr);
}
t.busy_count = busy_count + 1;
@@ -1453,48 +1760,84 @@ fn groupAsync(
t.wait_group.finish();
t.busy_count = busy_count;
t.mutex.unlock();
- gc.deinit(gpa);
- return t.assertGroupResult(start(group, context.ptr));
+ task.destroy(gpa);
+ return groupAsyncEager(start, context.ptr);
};
thread.detach();
}
- // Append to the group linked list inside the mutex to make `Io.Group.async` thread-safe.
- gc.node = .{ .next = @ptrCast(@alignCast(group.token.load(.monotonic))) };
- group.token.store(&gc.node, .monotonic);
-
- t.run_queue.prepend(&gc.closure.node);
-
- // This needs to be done before unlocking the mutex to avoid a race with
- // the associated task finishing.
- const group_state: *std.atomic.Value(usize) = @ptrCast(&group.state);
- const prev_state = group_state.fetchAdd(GroupClosure.sync_one_pending, .monotonic);
- assert((prev_state / GroupClosure.sync_one_pending) < (std.math.maxInt(usize) / GroupClosure.sync_one_pending));
+ // TODO: if this logic is changed to be lock-free, this `fetchAdd` must be released by the queue
+ // prepend so that the task doesn't finish without observing this and try to decrement the count
+ // below zero.
+ _ = g.status().fetchAdd(.{
+ .num_running = 1,
+ .have_awaiter = false,
+ .canceled = false,
+ }, .monotonic);
+ t.run_queue.prepend(&task.runnable.node);
t.mutex.unlock();
t.cond.signal();
}
+fn groupAsyncEager(
+ start: *const fn (context: *const anyopaque) Io.Cancelable!void,
+ context: *const anyopaque,
+) void {
+ const pre_acknowledged = if (Thread.current) |thread| ack: {
+ break :ack switch (thread.status.load(.monotonic).cancelation) {
+ .none, .canceling => false,
+ .canceled => true,
+ .parked => unreachable,
+ .blocked => unreachable,
+ .blocked_windows_dns => unreachable,
+ .blocked_canceling => unreachable,
+ };
+ } else false;
+ const result = start(context);
+ const post_acknowledged = if (Thread.current) |thread| ack: {
+ break :ack switch (thread.status.load(.monotonic).cancelation) {
+ .none, .canceling => false,
+ .canceled => true,
+ .parked => unreachable,
+ .blocked => unreachable,
+ .blocked_windows_dns => unreachable,
+ .blocked_canceling => unreachable,
+ };
+ } else false;
-fn assertGroupResult(t: *Threaded, result: Io.Cancelable!void) void {
- const current_thread: *Thread = .getCurrent(t);
- const current_closure = current_thread.current_closure orelse return;
- GroupClosure.assertResult(current_closure, result);
+ if (result) {
+ if (pre_acknowledged) {
+ assert(post_acknowledged); // group task called `recancel` but was not canceled
+ } else {
+ assert(!post_acknowledged); // group task acknowledged cancelation but did not return `error.Canceled`
+ }
+ } else |err| switch (err) {
+ // Don't swallow the cancelation: make it visible to the `Group.async` caller.
+ error.Canceled => {
+ assert(!pre_acknowledged); // group task called `recancel` but was not canceled
+ assert(post_acknowledged); // group task returned `error.Canceled` but was never canceled
+ recancelInner();
+ },
+ }
}
fn groupConcurrent(
userdata: ?*anyopaque,
- group: *Io.Group,
+ type_erased: *Io.Group,
context: []const u8,
context_alignment: Alignment,
- start: *const fn (*Io.Group, context: *const anyopaque) Io.Cancelable!void,
+ start: *const fn (context: *const anyopaque) Io.Cancelable!void,
) Io.ConcurrentError!void {
if (builtin.single_threaded) return error.ConcurrencyUnavailable;
const t: *Threaded = @ptrCast(@alignCast(userdata));
+ const g: Group = .{ .ptr = type_erased };
const gpa = t.allocator;
- const gc = GroupClosure.init(gpa, group, context, context_alignment, start) catch
- return error.ConcurrencyUnavailable;
+ const task = Group.Task.create(gpa, g, context, context_alignment, start) catch |err| switch (err) {
+ error.OutOfMemory => return error.ConcurrencyUnavailable,
+ };
+ errdefer task.destroy(gpa);
t.mutex.lock();
defer t.mutex.unlock();
@@ -1514,115 +1857,144 @@ fn groupConcurrent(
const thread = std.Thread.spawn(.{ .stack_size = t.stack_size }, worker, .{t}) catch
return error.ConcurrencyUnavailable;
+
thread.detach();
}
- // Append to the group linked list inside the mutex to make `Io.Group.concurrent` thread-safe.
- gc.node = .{ .next = @ptrCast(@alignCast(group.token.load(.monotonic))) };
- group.token.store(&gc.node, .monotonic);
-
- t.run_queue.prepend(&gc.closure.node);
-
- // This needs to be done before unlocking the mutex to avoid a race with
- // the associated task finishing.
- const group_state: *std.atomic.Value(usize) = @ptrCast(&group.state);
- const prev_state = group_state.fetchAdd(GroupClosure.sync_one_pending, .monotonic);
- assert((prev_state / GroupClosure.sync_one_pending) < (std.math.maxInt(usize) / GroupClosure.sync_one_pending));
+ // TODO: if this logic is changed to be lock-free, this `fetchAdd` must be released by the queue
+ // prepend so that the task doesn't finish without observing this and try to decrement the count
+ // below zero.
+ _ = g.status().fetchAdd(.{
+ .num_running = 1,
+ .have_awaiter = false,
+ .canceled = false,
+ }, .monotonic);
+ t.run_queue.prepend(&task.runnable.node);
t.cond.signal();
}
-fn groupAwait(userdata: ?*anyopaque, group: *Io.Group, initial_token: *anyopaque) Io.Cancelable!void {
+fn groupAwait(userdata: ?*anyopaque, type_erased: *Io.Group, initial_token: *anyopaque) Io.Cancelable!void {
+ _ = initial_token; // we need to load `token` *after* the group finishes
const t: *Threaded = @ptrCast(@alignCast(userdata));
- const gpa = t.allocator;
+ const g: Group = .{ .ptr = type_erased };
- _ = initial_token; // we need to load `token` *after* the group finishes
+ var num_completed: std.atomic.Value(u32) = .init(0);
+ g.awaiter().* = &num_completed;
- if (builtin.single_threaded) unreachable; // we never set `group.token` to non-`null`
+ const pre_await_status = g.status().fetchOr(.{
+ .num_running = 0,
+ .have_awaiter = true,
+ .canceled = false,
+ }, .acq_rel); // acquire results if complete; release `g.awaiter()`
- const group_state: *std.atomic.Value(usize) = @ptrCast(&group.state);
- const event: *Io.Event = @ptrCast(&group.context);
- const prev_state = group_state.fetchAdd(GroupClosure.sync_is_waiting, .acquire);
- assert(prev_state & GroupClosure.sync_is_waiting == 0);
- {
- errdefer _ = group_state.fetchSub(GroupClosure.sync_is_waiting, .monotonic);
- // This event.wait can return error.Canceled, in which case this logic does
- // *not* propagate cancel requests to each group member. Instead, the user
- // code will likely do this with a defered call to groupCancel, or,
- // intentionally not do this.
- if ((prev_state / GroupClosure.sync_one_pending) > 0) try event.wait(ioBasic(t));
+ assert(!pre_await_status.have_awaiter);
+ assert(!pre_await_status.canceled);
+ if (pre_await_status.num_running == 0) {
+ // Already done. Since the group is finished, it's illegal to spawn more tasks in it
+ // until we return, so we can access `g.status()` non-atomically.
+ g.status().raw.have_awaiter = false;
+ return;
}
- // Since the group has now finished, it's illegal to add more tasks to it until we return. It's
- // also illegal for us to race with another `await` or `cancel`. Therefore, we must be the only
- // thread who can access `group` right now.
- var it: ?*std.SinglyLinkedList.Node = @ptrCast(@alignCast(group.token.raw));
- group.token.raw = null;
- while (it) |node| {
- it = node.next; // update `it` now, because `deinit` will invalidate `node`
- const gc: *GroupClosure = @fieldParentPtr("node", node);
- gc.deinit(gpa);
+ while (Thread.futexWait(&num_completed.raw, 0, null)) {
+ switch (num_completed.load(.acquire)) { // acquire task results
+ 0 => continue,
+ 1 => break,
+ else => unreachable, // group was reused before `await` returned
+ }
+ } else |err| switch (err) {
+ error.Canceled => {
+ const pre_cancel_status = g.status().fetchOr(.{
+ .num_running = 0,
+ .have_awaiter = false,
+ .canceled = true,
+ }, .acq_rel); // acquire results if complete; release `g.awaiter()`
+ assert(pre_cancel_status.have_awaiter);
+ assert(!pre_cancel_status.canceled);
+
+ // Even if `pre_cancel_status.num_running == 0`, we still need to wait for the signal,
+ // because in that case the last member of the group is already trying to modify it.
+ // However, if we know everything is done, we *can* skip signaling blocked threads.
+ const skip_signals = pre_cancel_status.num_running == 0;
+ g.waitForCancelWithSignaling(t, &num_completed, skip_signals);
+
+ // The group is finished, so it's illegal to spawn more tasks in it until we return, so
+ // we can access `g.status()` non-atomically.
+ g.status().raw.canceled = false;
+ g.status().raw.have_awaiter = false;
+ return error.Canceled;
+ },
}
+
+ // The group is finished, so it's illegal to spawn more tasks in it until we return, so
+ // we can access `g.status()` non-atomically.
+ g.status().raw.have_awaiter = false;
}
-fn groupCancel(userdata: ?*anyopaque, group: *Io.Group, initial_token: *anyopaque) void {
+fn groupCancel(userdata: ?*anyopaque, type_erased: *Io.Group, initial_token: *anyopaque) void {
+ _ = initial_token;
const t: *Threaded = @ptrCast(@alignCast(userdata));
- const gpa = t.allocator;
+ const g: Group = .{ .ptr = type_erased };
- _ = initial_token; // we need to load `token` *after* the group finishes
+ var num_completed: std.atomic.Value(u32) = .init(0);
+ g.awaiter().* = &num_completed;
- if (builtin.single_threaded) unreachable; // we never set `group.token` to non-`null`
+ const pre_cancel_status = g.status().fetchOr(.{
+ .num_running = 0,
+ .have_awaiter = true,
+ .canceled = true,
+ }, .acq_rel); // acquire results if complete; release `g.awaiter()`
- {
- var it: ?*std.SinglyLinkedList.Node = @ptrCast(@alignCast(group.token.load(.monotonic)));
- while (it) |node| : (it = node.next) {
- const gc: *GroupClosure = @fieldParentPtr("node", node);
- gc.closure.requestCancel(t);
- }
+ assert(!pre_cancel_status.have_awaiter);
+ assert(!pre_cancel_status.canceled);
+ if (pre_cancel_status.num_running == 0) {
+ // Already done. Since the group is finished, it's illegal to spawn more tasks in it
+ // until we return, so we can access `g.status()` non-atomically.
+ g.status().raw.have_awaiter = false;
+ g.status().raw.canceled = false;
+ return;
}
- const group_state: *std.atomic.Value(usize) = @ptrCast(&group.state);
- const event: *Io.Event = @ptrCast(&group.context);
- const prev_state = group_state.fetchAdd(GroupClosure.sync_is_waiting, .acquire);
- assert(prev_state & GroupClosure.sync_is_waiting == 0);
- if ((prev_state / GroupClosure.sync_one_pending) > 0) event.waitUncancelable(ioBasic(t));
+ g.waitForCancelWithSignaling(t, &num_completed, false);
- // Since the group has now finished, it's illegal to add more tasks to it until we return. It's
- // also illegal for us to race with another `await` or `cancel`. Therefore, we must be the only
- // thread who can access `group` right now.
- var it: ?*std.SinglyLinkedList.Node = @ptrCast(@alignCast(group.token.raw));
- group.token.raw = null;
- while (it) |node| {
- it = node.next; // update `it` now, because `deinit` will invalidate `node`
- const gc: *GroupClosure = @fieldParentPtr("node", node);
- gc.deinit(gpa);
- }
+ g.status().raw = .{ .num_running = 0, .have_awaiter = false, .canceled = false };
}
fn recancel(userdata: ?*anyopaque) void {
const t: *Threaded = @ptrCast(@alignCast(userdata));
- const current_thread: *Thread = .getCurrent(t);
- const cancel_status = ¤t_thread.current_closure.?.cancel_status;
- switch (@atomicLoad(CancelStatus, cancel_status, .monotonic)) {
- .none => unreachable, // called `recancel` when not canceled
- .requested => unreachable, // called `recancel` when cancelation was already outstanding
- .acknowledged => {},
- _ => unreachable, // invalid state: not in a syscall
+ _ = t;
+ recancelInner();
+}
+fn recancelInner() void {
+ const thread = Thread.current.?; // called `recancel` but was not canceled
+ switch (thread.status.fetchXor(.{
+ .cancelation = @enumFromInt(0b001),
+ .awaitable = .null,
+ }, .monotonic).cancelation) {
+ .canceled => {},
+ .none => unreachable, // called `recancel` but was not canceled
+ .canceling => unreachable, // called `recancel` but cancelation was already pending
+ .parked => unreachable,
+ .blocked => unreachable,
+ .blocked_windows_dns => unreachable,
+ .blocked_canceling => unreachable,
}
- @atomicStore(CancelStatus, cancel_status, .requested, .monotonic);
}
fn swapCancelProtection(userdata: ?*anyopaque, new: Io.CancelProtection) Io.CancelProtection {
const t: *Threaded = @ptrCast(@alignCast(userdata));
- const current_thread: *Thread = .getCurrent(t);
- const old = current_thread.cancel_protection;
- current_thread.cancel_protection = new;
+ _ = t;
+ const thread = Thread.current orelse return .unblocked;
+ const old = thread.cancel_protection;
+ thread.cancel_protection = new;
return old;
}
fn checkCancel(userdata: ?*anyopaque) Io.Cancelable!void {
const t: *Threaded = @ptrCast(@alignCast(userdata));
- return Thread.getCurrent(t).checkCancel();
+ _ = t;
+ return Thread.checkCancel();
}
fn await(
@@ -1633,8 +2005,59 @@ fn await(
) void {
_ = result_alignment;
const t: *Threaded = @ptrCast(@alignCast(userdata));
- const closure: *AsyncClosure = @ptrCast(@alignCast(any_future));
- closure.waitAndDeinit(t, result);
+ const future: *Future = @ptrCast(@alignCast(any_future));
+
+ var num_completed: std.atomic.Value(u32) = .init(0);
+ future.awaiter = &num_completed;
+
+ const pre_await_status = future.status.fetchOr(.{
+ .tag = .pending_awaited,
+ .thread = .null,
+ }, .acq_rel); // acquire results if complete; release `future.awaiter`
+ switch (pre_await_status.tag) {
+ .pending => while (Thread.futexWait(&num_completed.raw, 0, null)) {
+ switch (num_completed.load(.acquire)) { // acquire task results
+ 0 => continue,
+ 1 => break,
+ else => unreachable, // group was reused before `await` returned
+ }
+ } else |err| switch (err) {
+ error.Canceled => {
+ const pre_cancel_status = future.status.fetchOr(.{
+ .tag = .pending_canceled,
+ .thread = .null,
+ }, .acq_rel); // acquire results if complete; release `future.awaiter`
+ switch (pre_cancel_status.tag) {
+ .pending => unreachable, // invalid state: we already awaited
+ .pending_awaited => {
+ const working_thread = pre_cancel_status.thread.unpack();
+ future.waitForCancelWithSignaling(t, &num_completed, @alignCast(working_thread));
+ },
+ .pending_canceled => unreachable, // `await` raced with `cancel`
+ .done => {
+ // The task just finished, but we still need to wait for the signal, because the
+ // task thread already figured out that they need to update `future.awaiter`.
+ future.waitForCancelWithSignaling(t, &num_completed, null);
+ },
+ }
+ // If the future did not acknowledge the cancelation, we need to mark it outstanding
+ // for us. Because `future.status.tag == .done`, the information about whether there
+ // was an acknowledged cancelation is encoded in `future.status.thread`.
+ const final_status = future.status.load(.monotonic);
+ assert(final_status.tag == .done);
+ switch (final_status.thread) {
+ .null => recancelInner(), // cancelation was not acknowledged, so it's ours
+ .all_ones => {}, // cancelation was acknowledged, so it was this task's job to propagate it
+ _ => unreachable,
+ }
+ },
+ },
+ .pending_awaited => unreachable, // `await` raced with `await`
+ .pending_canceled => unreachable, // `await` raced with `cancel`
+ .done => {},
+ }
+ @memcpy(result, future.resultPointer());
+ future.destroy(t.allocator);
}
fn cancel(
@@ -1645,28 +2068,44 @@ fn cancel(
) void {
_ = result_alignment;
const t: *Threaded = @ptrCast(@alignCast(userdata));
- const ac: *AsyncClosure = @ptrCast(@alignCast(any_future));
- ac.closure.requestCancel(t);
- ac.waitAndDeinit(t, result);
+ const future: *Future = @ptrCast(@alignCast(any_future));
+
+ var num_completed: std.atomic.Value(u32) = .init(0);
+ future.awaiter = &num_completed;
+
+ const pre_cancel_status = future.status.fetchOr(.{
+ .tag = .pending_canceled,
+ .thread = .null,
+ }, .acq_rel); // acquire results if complete; release `future.awaiter`
+ switch (pre_cancel_status.tag) {
+ .pending => {
+ const working_thread = pre_cancel_status.thread.unpack();
+ future.waitForCancelWithSignaling(t, &num_completed, @alignCast(working_thread));
+ },
+ .pending_awaited => unreachable, // `await` raced with `await`
+ .pending_canceled => unreachable, // `await` raced with `cancel`
+ .done => {},
+ }
+ @memcpy(result, future.resultPointer());
+ future.destroy(t.allocator);
}
fn futexWait(userdata: ?*anyopaque, ptr: *const u32, expected: u32, timeout: Io.Timeout) Io.Cancelable!void {
if (builtin.single_threaded) unreachable; // Deadlock.
const t: *Threaded = @ptrCast(@alignCast(userdata));
- const current_thread = Thread.getCurrent(t);
const t_io = ioBasic(t);
const timeout_ns: ?u64 = ns: {
const d = (timeout.toDurationFromNow(t_io) catch break :ns 10) orelse break :ns null;
break :ns std.math.lossyCast(u64, d.raw.toNanoseconds());
};
- return Thread.futexWaitTimed(current_thread, ptr, expected, timeout_ns);
+ return Thread.futexWait(ptr, expected, timeout_ns);
}
fn futexWaitUncancelable(userdata: ?*anyopaque, ptr: *const u32, expected: u32) void {
if (builtin.single_threaded) unreachable; // Deadlock.
const t: *Threaded = @ptrCast(@alignCast(userdata));
_ = t;
- Thread.futexWaitUncancelable(ptr, expected);
+ Thread.futexWaitUncancelable(ptr, expected, null);
}
fn futexWake(userdata: ?*anyopaque, ptr: *const u32, max_waiters: u32) void {
@@ -1684,24 +2123,24 @@ const dirCreateDir = switch (native_os) {
fn dirCreateDirPosix(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, permissions: Dir.Permissions) Dir.CreateDirError!void {
const t: *Threaded = @ptrCast(@alignCast(userdata));
- const current_thread = Thread.getCurrent(t);
+ _ = t;
var path_buffer: [posix.PATH_MAX]u8 = undefined;
const sub_path_posix = try pathToPosix(sub_path, &path_buffer);
- try current_thread.beginSyscall();
+ const syscall: Syscall = try .start();
while (true) {
switch (posix.errno(posix.system.mkdirat(dir.handle, sub_path_posix, permissions.toMode()))) {
.SUCCESS => {
- current_thread.endSyscall();
+ syscall.finish();
return;
},
.INTR => {
- try current_thread.checkCancel();
+ try syscall.checkCancel();
continue;
},
else => |e| {
- current_thread.endSyscall();
+ syscall.finish();
switch (e) {
.ACCES => return error.AccessDenied,
.BADF => |err| return errnoBug(err), // File descriptor used after closed.
@@ -1730,20 +2169,20 @@ fn dirCreateDirPosix(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, perm
fn dirCreateDirWasi(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, permissions: Dir.Permissions) Dir.CreateDirError!void {
if (builtin.link_libc) return dirCreateDirPosix(userdata, dir, sub_path, permissions);
const t: *Threaded = @ptrCast(@alignCast(userdata));
- const current_thread = Thread.getCurrent(t);
- try current_thread.beginSyscall();
+ _ = t;
+ const syscall: Syscall = try .start();
while (true) {
switch (std.os.wasi.path_create_directory(dir.handle, sub_path.ptr, sub_path.len)) {
.SUCCESS => {
- current_thread.endSyscall();
+ syscall.finish();
return;
},
.INTR => {
- try current_thread.checkCancel();
+ try syscall.checkCancel();
continue;
},
else => |e| {
- current_thread.endSyscall();
+ syscall.finish();
switch (e) {
.ACCES => return error.AccessDenied,
.BADF => |err| return errnoBug(err), // File descriptor used after closed.
@@ -1770,27 +2209,35 @@ fn dirCreateDirWasi(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, permi
fn dirCreateDirWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, permissions: Dir.Permissions) Dir.CreateDirError!void {
const t: *Threaded = @ptrCast(@alignCast(userdata));
- const current_thread = Thread.getCurrent(t);
- try current_thread.checkCancel();
+ _ = t;
const sub_path_w = try windows.sliceToPrefixedFileW(dir.handle, sub_path);
_ = permissions; // TODO use this value
- const sub_dir_handle = windows.OpenFile(sub_path_w.span(), .{
- .dir = dir.handle,
- .access_mask = .{
- .GENERIC = .{ .READ = true },
- .STANDARD = .{ .SYNCHRONIZE = true },
- },
- .creation = .CREATE,
- .filter = .dir_only,
- }) catch |err| switch (err) {
- error.IsDir => return error.Unexpected,
- error.PipeBusy => return error.Unexpected,
- error.NoDevice => return error.Unexpected,
- error.WouldBlock => return error.Unexpected,
- error.AntivirusInterference => return error.Unexpected,
- else => |e| return e,
+
+ const syscall: Syscall = try .start();
+ const sub_dir_handle = while (true) {
+ break windows.OpenFile(sub_path_w.span(), .{
+ .dir = dir.handle,
+ .access_mask = .{
+ .GENERIC = .{ .READ = true },
+ .STANDARD = .{ .SYNCHRONIZE = true },
+ },
+ .creation = .CREATE,
+ .filter = .dir_only,
+ }) catch |err| switch (err) {
+ error.IsDir => return syscall.fail(error.Unexpected),
+ error.PipeBusy => return syscall.fail(error.Unexpected),
+ error.NoDevice => return syscall.fail(error.Unexpected),
+ error.WouldBlock => return syscall.fail(error.Unexpected),
+ error.AntivirusInterference => return syscall.fail(error.Unexpected),
+ error.OperationCanceled => {
+ try syscall.checkCancel();
+ continue;
+ },
+ else => |e| return syscall.fail(e),
+ };
};
+ syscall.finish();
windows.CloseHandle(sub_dir_handle);
}
@@ -1858,7 +2305,6 @@ fn dirCreateDirPathOpenWindows(
options: Dir.OpenOptions,
) Dir.CreateDirPathOpenError!Dir {
const t: *Threaded = @ptrCast(@alignCast(userdata));
- const current_thread = Thread.getCurrent(t);
const w = windows;
_ = permissions; // TODO apply these permissions
@@ -1870,9 +2316,7 @@ fn dirCreateDirPathOpenWindows(
.path = sub_path,
};
- while (true) {
- try current_thread.checkCancel();
-
+ components: while (true) {
const sub_path_w_array = try w.sliceToPrefixedFileW(dir.handle, component.path);
const sub_path_w = sub_path_w_array.span();
const is_last = it.peekNext() == null;
@@ -1887,7 +2331,9 @@ fn dirCreateDirPathOpenWindows(
.Buffer = @constCast(sub_path_w.ptr),
};
var io_status_block: w.IO_STATUS_BLOCK = undefined;
- const rc = w.ntdll.NtCreateFile(
+
+ const syscall: Syscall = try .start();
+ while (true) switch (w.ntdll.NtCreateFile(
&result.handle,
.{
.SPECIFIC = .{ .FILE_DIRECTORY = .{
@@ -1922,16 +2368,20 @@ fn dirCreateDirPathOpenWindows(
},
null,
0,
- );
-
- switch (rc) {
+ )) {
.SUCCESS => {
+ syscall.finish();
component = it.next() orelse return result;
w.CloseHandle(result.handle);
+ continue :components;
+ },
+ .CANCELLED => {
+ try syscall.checkCancel();
continue;
},
- .OBJECT_NAME_INVALID => return error.BadPathName,
+ .OBJECT_NAME_INVALID => return syscall.fail(error.BadPathName),
.OBJECT_NAME_COLLISION => {
+ syscall.finish();
assert(!is_last);
// stat the file and return an error if it's not a directory
// this is important because otherwise a dangling symlink
@@ -1942,23 +2392,24 @@ fn dirCreateDirPathOpenWindows(
if (fstat.kind != .directory) return error.NotDir;
component = it.next().?;
- continue;
+ continue :components;
},
.OBJECT_NAME_NOT_FOUND,
.OBJECT_PATH_NOT_FOUND,
=> {
+ syscall.finish();
component = it.previous() orelse return error.FileNotFound;
- continue;
+ continue :components;
},
- .NOT_A_DIRECTORY => return error.NotDir,
+ .NOT_A_DIRECTORY => return syscall.fail(error.NotDir),
// This can happen if the directory has 'List folder contents' permission set to 'Deny'
// and the directory is trying to be opened for iteration.
- .ACCESS_DENIED => return error.AccessDenied,
- .INVALID_PARAMETER => |err| return w.statusBug(err),
- else => return w.unexpectedStatus(rc),
- }
+ .ACCESS_DENIED => return syscall.fail(error.AccessDenied),
+ .INVALID_PARAMETER => |s| return syscall.ntstatusBug(s),
+ else => |s| return syscall.unexpectedNtstatus(s),
+ };
}
}
@@ -2000,7 +2451,7 @@ fn dirStatFileLinux(
options: Dir.StatFileOptions,
) Dir.StatFileError!File.Stat {
const t: *Threaded = @ptrCast(@alignCast(userdata));
- const current_thread = Thread.getCurrent(t);
+ _ = t;
const linux = std.os.linux;
const use_c = std.c.versionCheck(if (builtin.abi.isAndroid())
.{ .major = 30, .minor = 0, .patch = 0 }
@@ -2014,20 +2465,20 @@ fn dirStatFileLinux(
const flags: u32 = linux.AT.NO_AUTOMOUNT |
@as(u32, if (!options.follow_symlinks) linux.AT.SYMLINK_NOFOLLOW else 0);
- try current_thread.beginSyscall();
+ const syscall: Syscall = try .start();
while (true) {
var statx = std.mem.zeroes(linux.Statx);
switch (sys.errno(sys.statx(dir.handle, sub_path_posix, flags, linux_statx_request, &statx))) {
.SUCCESS => {
- current_thread.endSyscall();
+ syscall.finish();
return statFromLinux(&statx);
},
.INTR => {
- try current_thread.checkCancel();
+ try syscall.checkCancel();
continue;
},
else => |e| {
- current_thread.endSyscall();
+ syscall.finish();
switch (e) {
.ACCES => return error.AccessDenied,
.BADF => |err| return errnoBug(err), // File descriptor used after closed.
@@ -2052,31 +2503,31 @@ fn dirStatFilePosix(
options: Dir.StatFileOptions,
) Dir.StatFileError!File.Stat {
const t: *Threaded = @ptrCast(@alignCast(userdata));
- const current_thread = Thread.getCurrent(t);
+ _ = 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;
- return posixStatFile(current_thread, dir.handle, sub_path_posix, flags);
+ return posixStatFile(dir.handle, sub_path_posix, flags);
}
-fn posixStatFile(current_thread: *Thread, dir_fd: posix.fd_t, sub_path: [:0]const u8, flags: u32) Dir.StatFileError!File.Stat {
- try current_thread.beginSyscall();
+fn posixStatFile(dir_fd: posix.fd_t, sub_path: [:0]const u8, flags: u32) Dir.StatFileError!File.Stat {
+ const syscall: Syscall = try .start();
while (true) {
var stat = std.mem.zeroes(posix.Stat);
switch (posix.errno(fstatat_sym(dir_fd, sub_path, &stat, flags))) {
.SUCCESS => {
- current_thread.endSyscall();
+ syscall.finish();
return statFromPosix(&stat);
},
.INTR => {
- try current_thread.checkCancel();
+ try syscall.checkCancel();
continue;
},
else => |e| {
- current_thread.endSyscall();
+ syscall.finish();
switch (e) {
.INVAL => |err| return errnoBug(err),
.BADF => |err| return errnoBug(err), // File descriptor used after closed.
@@ -2118,25 +2569,25 @@ fn dirStatFileWasi(
) Dir.StatFileError!File.Stat {
if (builtin.link_libc) return dirStatFilePosix(userdata, dir, sub_path, options);
const t: *Threaded = @ptrCast(@alignCast(userdata));
- const current_thread = Thread.getCurrent(t);
+ _ = 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();
+ const syscall: Syscall = try .start();
while (true) {
switch (wasi.path_filestat_get(dir.handle, flags, sub_path.ptr, sub_path.len, &stat)) {
.SUCCESS => {
- current_thread.endSyscall();
+ syscall.finish();
return statFromWasi(&stat);
},
.INTR => {
- try current_thread.checkCancel();
+ try syscall.checkCancel();
continue;
},
else => |e| {
- current_thread.endSyscall();
+ syscall.finish();
switch (e) {
.INVAL => |err| return errnoBug(err),
.BADF => |err| return errnoBug(err), // File descriptor used after closed.
@@ -2159,24 +2610,23 @@ fn fileLength(userdata: ?*anyopaque, file: File) File.LengthError!u64 {
const t: *Threaded = @ptrCast(@alignCast(userdata));
if (native_os == .linux) {
- const current_thread = Thread.getCurrent(t);
const linux = std.os.linux;
- try current_thread.beginSyscall();
+ const syscall: Syscall = try .start();
while (true) {
var statx = std.mem.zeroes(linux.Statx);
switch (linux.errno(linux.statx(file.handle, "", linux.AT.EMPTY_PATH, .{ .SIZE = true }, &statx))) {
.SUCCESS => {
- current_thread.endSyscall();
+ syscall.finish();
if (!statx.mask.SIZE) return error.Unexpected;
return statx.size;
},
.INTR => {
- try current_thread.checkCancel();
+ try syscall.checkCancel();
continue;
},
else => |e| {
- current_thread.endSyscall();
+ syscall.finish();
switch (e) {
.ACCES => |err| return errnoBug(err),
.BADF => |err| return errnoBug(err), // File descriptor used after closed.
@@ -2209,24 +2659,24 @@ const fileStat = switch (native_os) {
fn fileStatPosix(userdata: ?*anyopaque, file: File) File.StatError!File.Stat {
const t: *Threaded = @ptrCast(@alignCast(userdata));
- const current_thread = Thread.getCurrent(t);
+ _ = t;
if (posix.Stat == void) return error.Streaming;
- try current_thread.beginSyscall();
+ const syscall: Syscall = try .start();
while (true) {
var stat = std.mem.zeroes(posix.Stat);
switch (posix.errno(fstat_sym(file.handle, &stat))) {
.SUCCESS => {
- current_thread.endSyscall();
+ syscall.finish();
return statFromPosix(&stat);
},
.INTR => {
- try current_thread.checkCancel();
+ try syscall.checkCancel();
continue;
},
else => |e| {
- current_thread.endSyscall();
+ syscall.finish();
switch (e) {
.INVAL => |err| return errnoBug(err),
.BADF => |err| return errnoBug(err), // File descriptor used after closed.
@@ -2241,7 +2691,7 @@ fn fileStatPosix(userdata: ?*anyopaque, file: File) File.StatError!File.Stat {
fn fileStatLinux(userdata: ?*anyopaque, file: File) File.StatError!File.Stat {
const t: *Threaded = @ptrCast(@alignCast(userdata));
- const current_thread = Thread.getCurrent(t);
+ _ = t;
const linux = std.os.linux;
const use_c = std.c.versionCheck(if (builtin.abi.isAndroid())
.{ .major = 30, .minor = 0, .patch = 0 }
@@ -2249,20 +2699,20 @@ fn fileStatLinux(userdata: ?*anyopaque, file: File) File.StatError!File.Stat {
.{ .major = 2, .minor = 28, .patch = 0 });
const sys = if (use_c) std.c else std.os.linux;
- try current_thread.beginSyscall();
+ const syscall: Syscall = try .start();
while (true) {
var statx = std.mem.zeroes(linux.Statx);
switch (sys.errno(sys.statx(file.handle, "", linux.AT.EMPTY_PATH, linux_statx_request, &statx))) {
.SUCCESS => {
- current_thread.endSyscall();
+ syscall.finish();
return statFromLinux(&statx);
},
.INTR => {
- try current_thread.checkCancel();
+ try syscall.checkCancel();
continue;
},
else => |e| {
- current_thread.endSyscall();
+ syscall.finish();
switch (e) {
.ACCES => |err| return errnoBug(err),
.BADF => |err| return errnoBug(err), // File descriptor used after closed.
@@ -2282,21 +2732,32 @@ fn fileStatLinux(userdata: ?*anyopaque, file: File) File.StatError!File.Stat {
fn fileStatWindows(userdata: ?*anyopaque, file: File) File.StatError!File.Stat {
const t: *Threaded = @ptrCast(@alignCast(userdata));
- const current_thread = Thread.getCurrent(t);
- try current_thread.checkCancel();
+ _ = t;
var io_status_block: windows.IO_STATUS_BLOCK = undefined;
var info: windows.FILE.ALL_INFORMATION = undefined;
- const rc = windows.ntdll.NtQueryInformationFile(file.handle, &io_status_block, &info, @sizeOf(windows.FILE.ALL_INFORMATION), .All);
- switch (rc) {
- .SUCCESS => {},
- // Buffer overflow here indicates that there is more information available than was able to be stored in the buffer
- // size provided. This is treated as success because the type of variable-length information that this would be relevant for
- // (name, volume name, etc) we don't care about.
- .BUFFER_OVERFLOW => {},
- .INVALID_PARAMETER => |err| return windows.statusBug(err),
- .ACCESS_DENIED => return error.AccessDenied,
- else => return windows.unexpectedStatus(rc),
+ {
+ const syscall: Syscall = try .start();
+ while (true) switch (windows.ntdll.NtQueryInformationFile(
+ file.handle,
+ &io_status_block,
+ &info,
+ @sizeOf(windows.FILE.ALL_INFORMATION),
+ .All,
+ )) {
+ .SUCCESS => break syscall.finish(),
+ // Buffer overflow here indicates that there is more information available than was able to be stored in the buffer
+ // size provided. This is treated as success because the type of variable-length information that this would be relevant for
+ // (name, volume name, etc) we don't care about.
+ .BUFFER_OVERFLOW => break syscall.finish(),
+ .INVALID_PARAMETER => |err| return syscall.ntstatusBug(err),
+ .ACCESS_DENIED => return syscall.fail(error.AccessDenied),
+ .CANCELLED => {
+ try syscall.checkCancel();
+ continue;
+ },
+ else => |s| return syscall.unexpectedNtstatus(s),
+ };
}
return .{
.inode = info.InternalInformation.IndexNumber,
@@ -2304,15 +2765,25 @@ fn fileStatWindows(userdata: ?*anyopaque, file: File) File.StatError!File.Stat {
.permissions = .default_file,
.kind = if (info.BasicInformation.FileAttributes.REPARSE_POINT) reparse_point: {
var tag_info: windows.FILE.ATTRIBUTE_TAG_INFO = undefined;
- const tag_rc = windows.ntdll.NtQueryInformationFile(file.handle, &io_status_block, &tag_info, @sizeOf(windows.FILE.ATTRIBUTE_TAG_INFO), .AttributeTag);
- switch (tag_rc) {
- .SUCCESS => {},
+ const syscall: Syscall = try .start();
+ while (true) switch (windows.ntdll.NtQueryInformationFile(
+ file.handle,
+ &io_status_block,
+ &tag_info,
+ @sizeOf(windows.FILE.ATTRIBUTE_TAG_INFO),
+ .AttributeTag,
+ )) {
+ .SUCCESS => break syscall.finish(),
// INFO_LENGTH_MISMATCH and ACCESS_DENIED are the only documented possible errors
// https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-fscc/d295752f-ce89-4b98-8553-266d37c84f0e
- .INFO_LENGTH_MISMATCH => |err| return windows.statusBug(err),
- .ACCESS_DENIED => return error.AccessDenied,
- else => return windows.unexpectedStatus(rc),
- }
+ .INFO_LENGTH_MISMATCH => |err| return syscall.ntstatusBug(err),
+ .ACCESS_DENIED => return syscall.fail(error.AccessDenied),
+ .CANCELLED => {
+ try syscall.checkCancel();
+ continue;
+ },
+ else => |s| return syscall.unexpectedNtstatus(s),
+ };
if (tag_info.ReparseTag.IsSurrogate) break :reparse_point .sym_link;
// Unknown reparse point
break :reparse_point .unknown;
@@ -2331,22 +2802,22 @@ fn fileStatWasi(userdata: ?*anyopaque, file: File) File.StatError!File.Stat {
if (builtin.link_libc) return fileStatPosix(userdata, file);
const t: *Threaded = @ptrCast(@alignCast(userdata));
- const current_thread = Thread.getCurrent(t);
+ _ = t;
- try current_thread.beginSyscall();
+ const syscall: Syscall = try .start();
while (true) {
var stat: std.os.wasi.filestat_t = undefined;
switch (std.os.wasi.fd_filestat_get(file.handle, &stat)) {
.SUCCESS => {
- current_thread.endSyscall();
+ syscall.finish();
return statFromWasi(&stat);
},
.INTR => {
- try current_thread.checkCancel();
+ try syscall.checkCancel();
continue;
},
else => |e| {
- current_thread.endSyscall();
+ syscall.finish();
switch (e) {
.INVAL => |err| return errnoBug(err),
.BADF => |err| return errnoBug(err), // File descriptor used after closed.
@@ -2373,7 +2844,7 @@ fn dirAccessPosix(
options: Dir.AccessOptions,
) Dir.AccessError!void {
const t: *Threaded = @ptrCast(@alignCast(userdata));
- const current_thread = Thread.getCurrent(t);
+ _ = t;
var path_buffer: [posix.PATH_MAX]u8 = undefined;
const sub_path_posix = try pathToPosix(sub_path, &path_buffer);
@@ -2385,19 +2856,19 @@ 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();
+ const syscall: Syscall = try .start();
while (true) {
switch (posix.errno(posix.system.faccessat(dir.handle, sub_path_posix, mode, flags))) {
.SUCCESS => {
- current_thread.endSyscall();
+ syscall.finish();
return;
},
.INTR => {
- try current_thread.checkCancel();
+ try syscall.checkCancel();
continue;
},
else => |e| {
- current_thread.endSyscall();
+ syscall.finish();
switch (e) {
.ACCES => return error.AccessDenied,
.PERM => return error.PermissionDenied,
@@ -2427,26 +2898,26 @@ fn dirAccessWasi(
) 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);
+ _ = 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();
+ const syscall: Syscall = try .start();
while (true) {
switch (wasi.path_filestat_get(dir.handle, flags, sub_path.ptr, sub_path.len, &stat)) {
.SUCCESS => {
- current_thread.endSyscall();
+ syscall.finish();
break;
},
.INTR => {
- try current_thread.checkCancel();
+ try syscall.checkCancel();
continue;
},
else => |e| {
- current_thread.endSyscall();
+ syscall.finish();
switch (e) {
.INVAL => |err| return errnoBug(err),
.BADF => |err| return errnoBug(err), // File descriptor used after closed.
@@ -2498,8 +2969,7 @@ fn dirAccessWindows(
options: Dir.AccessOptions,
) Dir.AccessError!void {
const t: *Threaded = @ptrCast(@alignCast(userdata));
- const current_thread = Thread.getCurrent(t);
- try current_thread.checkCancel();
+ _ = t;
_ = options; // TODO
@@ -2525,16 +2995,21 @@ fn dirAccessWindows(
.SecurityQualityOfService = null,
};
var basic_info: windows.FILE.BASIC_INFORMATION = undefined;
- switch (windows.ntdll.NtQueryAttributesFile(&attr, &basic_info)) {
- .SUCCESS => return,
- .OBJECT_NAME_NOT_FOUND => return error.FileNotFound,
- .OBJECT_PATH_NOT_FOUND => return error.FileNotFound,
- .OBJECT_NAME_INVALID => |err| return windows.statusBug(err),
- .INVALID_PARAMETER => |err| return windows.statusBug(err),
- .ACCESS_DENIED => return error.AccessDenied,
- .OBJECT_PATH_SYNTAX_BAD => |err| return windows.statusBug(err),
- else => |rc| return windows.unexpectedStatus(rc),
- }
+ const syscall: Syscall = try .start();
+ while (true) switch (windows.ntdll.NtQueryAttributesFile(&attr, &basic_info)) {
+ .SUCCESS => return syscall.finish(),
+ .CANCELLED => {
+ try syscall.checkCancel();
+ continue;
+ },
+ .OBJECT_NAME_NOT_FOUND => return syscall.fail(error.FileNotFound),
+ .OBJECT_PATH_NOT_FOUND => return syscall.fail(error.FileNotFound),
+ .OBJECT_NAME_INVALID => |err| return syscall.ntstatusBug(err),
+ .INVALID_PARAMETER => |err| return syscall.ntstatusBug(err),
+ .ACCESS_DENIED => return syscall.fail(error.AccessDenied),
+ .OBJECT_PATH_SYNTAX_BAD => |err| return syscall.ntstatusBug(err),
+ else => |rc| return syscall.unexpectedNtstatus(rc),
+ };
}
const dirCreateFile = switch (native_os) {
@@ -2550,7 +3025,7 @@ fn dirCreateFilePosix(
flags: File.CreateFlags,
) File.OpenError!File {
const t: *Threaded = @ptrCast(@alignCast(userdata));
- const current_thread = Thread.getCurrent(t);
+ _ = t;
var path_buffer: [posix.PATH_MAX]u8 = undefined;
const sub_path_posix = try pathToPosix(sub_path, &path_buffer);
@@ -2579,49 +3054,51 @@ fn dirCreateFilePosix(
},
};
- try current_thread.beginSyscall();
- const fd: posix.fd_t = while (true) {
- const rc = openat_sym(dir.handle, sub_path_posix, os_flags, flags.permissions.toMode());
- switch (posix.errno(rc)) {
- .SUCCESS => {
- current_thread.endSyscall();
- break @intCast(rc);
- },
- .INTR => {
- try current_thread.checkCancel();
- continue;
- },
- else => |e| {
- current_thread.endSyscall();
- switch (e) {
- .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.FileNotFound, // Linux when accessing procfs.
- .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.FileLocksUnsupported,
- .AGAIN => return error.WouldBlock,
- .TXTBSY => return error.FileBusy,
- .NXIO => return error.NoDevice,
- .ILSEQ => return error.BadPathName,
- else => |err| return posix.unexpectedErrno(err),
- }
- },
+ const fd: posix.fd_t = fd: {
+ const syscall: Syscall = try .start();
+ while (true) {
+ const rc = openat_sym(dir.handle, sub_path_posix, os_flags, flags.permissions.toMode());
+ switch (posix.errno(rc)) {
+ .SUCCESS => {
+ syscall.finish();
+ break :fd @intCast(rc);
+ },
+ .INTR => {
+ try syscall.checkCancel();
+ continue;
+ },
+ else => |e| {
+ syscall.finish();
+ switch (e) {
+ .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.FileNotFound, // Linux when accessing procfs.
+ .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.FileLocksUnsupported,
+ .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);
@@ -2634,19 +3111,19 @@ fn dirCreateFilePosix(
.exclusive => posix.LOCK.EX | lock_nonblocking,
};
- try current_thread.beginSyscall();
+ const syscall: Syscall = try .start();
while (true) {
switch (posix.errno(posix.system.flock(fd, lock_flags))) {
.SUCCESS => {
- current_thread.endSyscall();
+ syscall.finish();
break;
},
.INTR => {
- try current_thread.checkCancel();
+ try syscall.checkCancel();
continue;
},
else => |e| {
- current_thread.endSyscall();
+ syscall.finish();
switch (e) {
.BADF => |err| return errnoBug(err), // File descriptor used after closed.
.INVAL => |err| return errnoBug(err), // invalid parameters
@@ -2661,40 +3138,42 @@ fn dirCreateFilePosix(
}
if (have_flock_open_flags and flags.lock_nonblocking) {
- try current_thread.beginSyscall();
- var fl_flags: usize = while (true) {
- const rc = posix.system.fcntl(fd, posix.F.GETFL, @as(usize, 0));
- switch (posix.errno(rc)) {
- .SUCCESS => {
- current_thread.endSyscall();
- break @intCast(rc);
- },
- .INTR => {
- try current_thread.checkCancel();
- continue;
- },
- else => |err| {
- current_thread.endSyscall();
- return posix.unexpectedErrno(err);
- },
+ var fl_flags: usize = fl: {
+ const syscall: Syscall = try .start();
+ while (true) {
+ const rc = posix.system.fcntl(fd, posix.F.GETFL, @as(usize, 0));
+ switch (posix.errno(rc)) {
+ .SUCCESS => {
+ syscall.finish();
+ break :fl @intCast(rc);
+ },
+ .INTR => {
+ try syscall.checkCancel();
+ continue;
+ },
+ else => |err| {
+ syscall.finish();
+ return posix.unexpectedErrno(err);
+ },
+ }
}
};
fl_flags |= @as(usize, 1 << @bitOffsetOf(posix.O, "NONBLOCK"));
- try current_thread.beginSyscall();
+ const syscall: Syscall = try .start();
while (true) {
switch (posix.errno(posix.system.fcntl(fd, posix.F.SETFL, fl_flags))) {
.SUCCESS => {
- current_thread.endSyscall();
+ syscall.finish();
break;
},
.INTR => {
- try current_thread.checkCancel();
+ try syscall.checkCancel();
continue;
},
else => |err| {
- current_thread.endSyscall();
+ syscall.finish();
return posix.unexpectedErrno(err);
},
}
@@ -2712,28 +3191,41 @@ fn dirCreateFileWindows(
) File.OpenError!File {
const w = windows;
const t: *Threaded = @ptrCast(@alignCast(userdata));
- const current_thread = Thread.getCurrent(t);
- try current_thread.checkCancel();
+ _ = t;
const sub_path_w_array = try w.sliceToPrefixedFileW(dir.handle, sub_path);
const sub_path_w = sub_path_w_array.span();
- const handle = try w.OpenFile(sub_path_w, .{
- .dir = dir.handle,
- .access_mask = .{
- .STANDARD = .{ .SYNCHRONIZE = true },
- .GENERIC = .{
- .WRITE = true,
- .READ = flags.read,
- },
- },
- .creation = if (flags.exclusive)
- .CREATE
- else if (flags.truncate)
- .OVERWRITE_IF
- else
- .OPEN_IF,
- });
+ const handle = handle: {
+ const syscall: Syscall = try .start();
+ while (true) {
+ if (w.OpenFile(sub_path_w, .{
+ .dir = dir.handle,
+ .access_mask = .{
+ .STANDARD = .{ .SYNCHRONIZE = true },
+ .GENERIC = .{
+ .WRITE = true,
+ .READ = flags.read,
+ },
+ },
+ .creation = if (flags.exclusive)
+ .CREATE
+ else if (flags.truncate)
+ .OVERWRITE_IF
+ else
+ .OPEN_IF,
+ })) |handle| {
+ syscall.finish();
+ break :handle handle;
+ } else |err| switch (err) {
+ error.OperationCanceled => {
+ try syscall.checkCancel();
+ continue;
+ },
+ else => |e| return syscall.fail(e),
+ }
+ }
+ };
errdefer w.CloseHandle(handle);
var io_status_block: w.IO_STATUS_BLOCK = undefined;
@@ -2742,7 +3234,8 @@ fn dirCreateFileWindows(
.shared => false,
.exclusive => true,
};
- const status = w.ntdll.NtLockFile(
+ const syscall: Syscall = try .start();
+ while (true) switch (w.ntdll.NtLockFile(
handle,
null,
null,
@@ -2753,16 +3246,16 @@ fn dirCreateFileWindows(
null,
@intFromBool(flags.lock_nonblocking),
@intFromBool(exclusive),
- );
- switch (status) {
- .SUCCESS => {},
- .INSUFFICIENT_RESOURCES => return error.SystemResources,
- .LOCK_NOT_GRANTED => return error.WouldBlock,
- .ACCESS_VIOLATION => |err| return windows.statusBug(err), // bad io_status_block pointer
- else => return windows.unexpectedStatus(status),
- }
-
- return .{ .handle = handle };
+ )) {
+ .SUCCESS => {
+ syscall.finish();
+ return .{ .handle = handle };
+ },
+ .INSUFFICIENT_RESOURCES => return syscall.fail(error.SystemResources),
+ .LOCK_NOT_GRANTED => return syscall.fail(error.WouldBlock),
+ .ACCESS_VIOLATION => |err| return syscall.ntstatusBug(err), // bad io_status_block pointer
+ else => |status| return syscall.unexpectedNtstatus(status),
+ };
}
fn dirCreateFileWasi(
@@ -2772,7 +3265,7 @@ fn dirCreateFileWasi(
flags: File.CreateFlags,
) File.OpenError!File {
const t: *Threaded = @ptrCast(@alignCast(userdata));
- const current_thread = Thread.getCurrent(t);
+ _ = t;
const wasi = std.os.wasi;
const lookup_flags: wasi.lookupflags_t = .{};
const oflags: wasi.oflags_t = .{
@@ -2800,19 +3293,19 @@ fn dirCreateFileWasi(
};
const inheriting: wasi.rights_t = .{};
var fd: posix.fd_t = undefined;
- try current_thread.beginSyscall();
+ const syscall: Syscall = try .start();
while (true) {
switch (wasi.path_open(dir.handle, lookup_flags, sub_path.ptr, sub_path.len, oflags, base, inheriting, fdflags, &fd)) {
.SUCCESS => {
- current_thread.endSyscall();
+ syscall.finish();
return .{ .handle = fd };
},
.INTR => {
- try current_thread.checkCancel();
+ try syscall.checkCancel();
continue;
},
else => |e| {
- current_thread.endSyscall();
+ syscall.finish();
switch (e) {
.FAULT => |err| return errnoBug(err),
.INVAL => return error.BadPathName,
@@ -2855,7 +3348,6 @@ fn dirOpenFilePosix(
flags: File.OpenFlags,
) File.OpenError!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);
@@ -2895,49 +3387,51 @@ fn dirOpenFilePosix(
},
};
- try current_thread.beginSyscall();
- const fd: posix.fd_t = while (true) {
- const rc = openat_sym(dir.handle, sub_path_posix, os_flags, @as(posix.mode_t, 0));
- switch (posix.errno(rc)) {
- .SUCCESS => {
- current_thread.endSyscall();
- break @intCast(rc);
- },
- .INTR => {
- try current_thread.checkCancel();
- continue;
- },
- else => |e| {
- current_thread.endSyscall();
- switch (e) {
- .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.FileNotFound, // Linux when opening procfs files.
- .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.FileLocksUnsupported,
- .AGAIN => return error.WouldBlock,
- .TXTBSY => return error.FileBusy,
- .NXIO => return error.NoDevice,
- .ILSEQ => return error.BadPathName,
- else => |err| return posix.unexpectedErrno(err),
- }
- },
+ const fd: posix.fd_t = fd: {
+ const syscall: Syscall = try .start();
+ while (true) {
+ const rc = openat_sym(dir.handle, sub_path_posix, os_flags, @as(posix.mode_t, 0));
+ switch (posix.errno(rc)) {
+ .SUCCESS => {
+ syscall.finish();
+ break :fd @intCast(rc);
+ },
+ .INTR => {
+ try syscall.checkCancel();
+ continue;
+ },
+ else => |e| {
+ syscall.finish();
+ switch (e) {
+ .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.FileNotFound, // Linux when opening procfs files.
+ .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.FileLocksUnsupported,
+ .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);
@@ -2961,19 +3455,19 @@ fn dirOpenFilePosix(
.shared => posix.LOCK.SH | lock_nonblocking,
.exclusive => posix.LOCK.EX | lock_nonblocking,
};
- try current_thread.beginSyscall();
+ const syscall: Syscall = try .start();
while (true) {
switch (posix.errno(posix.system.flock(fd, lock_flags))) {
.SUCCESS => {
- current_thread.endSyscall();
+ syscall.finish();
break;
},
.INTR => {
- try current_thread.checkCancel();
+ try syscall.checkCancel();
continue;
},
else => |e| {
- current_thread.endSyscall();
+ syscall.finish();
switch (e) {
.BADF => |err| return errnoBug(err), // File descriptor used after closed.
.INVAL => |err| return errnoBug(err), // invalid parameters
@@ -2988,40 +3482,42 @@ fn dirOpenFilePosix(
}
if (have_flock_open_flags and flags.lock_nonblocking) {
- try current_thread.beginSyscall();
- var fl_flags: usize = while (true) {
- const rc = posix.system.fcntl(fd, posix.F.GETFL, @as(usize, 0));
- switch (posix.errno(rc)) {
- .SUCCESS => {
- current_thread.endSyscall();
- break @intCast(rc);
- },
- .INTR => {
- try current_thread.checkCancel();
- continue;
- },
- else => |err| {
- current_thread.endSyscall();
- return posix.unexpectedErrno(err);
- },
+ var fl_flags: usize = fl: {
+ const syscall: Syscall = try .start();
+ while (true) {
+ const rc = posix.system.fcntl(fd, posix.F.GETFL, @as(usize, 0));
+ switch (posix.errno(rc)) {
+ .SUCCESS => {
+ syscall.finish();
+ break :fl @intCast(rc);
+ },
+ .INTR => {
+ try syscall.checkCancel();
+ continue;
+ },
+ else => |err| {
+ syscall.finish();
+ return posix.unexpectedErrno(err);
+ },
+ }
}
};
fl_flags |= @as(usize, 1 << @bitOffsetOf(posix.O, "NONBLOCK"));
- try current_thread.beginSyscall();
+ const syscall: Syscall = try .start();
while (true) {
switch (posix.errno(posix.system.fcntl(fd, posix.F.SETFL, fl_flags))) {
.SUCCESS => {
- current_thread.endSyscall();
+ syscall.finish();
break;
},
.INTR => {
- try current_thread.checkCancel();
+ try syscall.checkCancel();
continue;
},
else => |err| {
- current_thread.endSyscall();
+ syscall.finish();
return posix.unexpectedErrno(err);
},
}
@@ -3038,14 +3534,14 @@ fn dirOpenFileWindows(
flags: File.OpenFlags,
) File.OpenError!File {
const t: *Threaded = @ptrCast(@alignCast(userdata));
+ _ = t;
const sub_path_w_array = try windows.sliceToPrefixedFileW(dir.handle, sub_path);
const sub_path_w = sub_path_w_array.span();
const dir_handle = if (std.fs.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle;
- return dirOpenFileWtf16(t, dir_handle, sub_path_w, flags);
+ return dirOpenFileWtf16(dir_handle, sub_path_w, flags);
}
pub fn dirOpenFileWtf16(
- t: *Threaded,
dir_handle: ?windows.HANDLE,
sub_path_w: [:0]const u16,
flags: File.OpenFlags,
@@ -3054,7 +3550,6 @@ pub fn dirOpenFileWtf16(
if (!allow_directory and std.mem.eql(u16, sub_path_w, &.{'.'})) return error.IsDir;
if (!allow_directory and 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 = .{
@@ -3076,11 +3571,10 @@ pub fn dirOpenFileWtf16(
const max_attempts = 13;
var attempt: u5 = 0;
+ var syscall: Syscall = try .start();
const handle = while (true) {
- try current_thread.checkCancel();
-
var result: w.HANDLE = undefined;
- const rc = w.ntdll.NtCreateFile(
+ switch (w.ntdll.NtCreateFile(
&result,
.{
.STANDARD = .{ .SYNCHRONIZE = true },
@@ -3102,49 +3596,59 @@ pub fn dirOpenFileWtf16(
},
null,
0,
- );
- switch (rc) {
- .SUCCESS => break result,
- .OBJECT_NAME_INVALID => return error.BadPathName,
- .OBJECT_NAME_NOT_FOUND => return error.FileNotFound,
- .OBJECT_PATH_NOT_FOUND => return error.FileNotFound,
- .BAD_NETWORK_PATH => return error.NetworkNotFound, // \\server was not found
- .BAD_NETWORK_NAME => return error.NetworkNotFound, // \\server was found but \\server\share wasn't
- .NO_MEDIA_IN_DEVICE => return error.NoDevice,
- .INVALID_PARAMETER => |err| return w.statusBug(err),
+ )) {
+ .SUCCESS => {
+ syscall.finish();
+ break result;
+ },
+ .OBJECT_NAME_INVALID => return syscall.fail(error.BadPathName),
+ .OBJECT_NAME_NOT_FOUND => return syscall.fail(error.FileNotFound),
+ .OBJECT_PATH_NOT_FOUND => return syscall.fail(error.FileNotFound),
+ .BAD_NETWORK_PATH => return syscall.fail(error.NetworkNotFound), // \\server was not found
+ .BAD_NETWORK_NAME => return syscall.fail(error.NetworkNotFound), // \\server was found but \\server\share wasn't
+ .NO_MEDIA_IN_DEVICE => return syscall.fail(error.NoDevice),
+ .INVALID_PARAMETER => |err| return syscall.ntstatusBug(err),
+ .CANCELLED => {
+ try syscall.checkCancel();
+ continue;
+ },
.SHARING_VIOLATION => {
// This occurs if the file attempting to be opened is a running
// executable. However, there's a kernel bug: the error may be
// incorrectly returned for an indeterminate amount of time
// after an executable file is closed. Here we work around the
// kernel bug with retry attempts.
+ syscall.finish();
if (max_attempts - attempt == 0) return error.SharingViolation;
_ = w.kernel32.SleepEx((@as(u32, 1) << attempt) >> 1, w.TRUE);
attempt += 1;
+ syscall = try .start();
continue;
},
- .ACCESS_DENIED => return error.AccessDenied,
- .PIPE_BUSY => return error.PipeBusy,
- .PIPE_NOT_AVAILABLE => return error.NoDevice,
- .OBJECT_PATH_SYNTAX_BAD => |err| return w.statusBug(err),
- .OBJECT_NAME_COLLISION => return error.PathAlreadyExists,
- .FILE_IS_A_DIRECTORY => return error.IsDir,
- .NOT_A_DIRECTORY => return error.NotDir,
- .USER_MAPPED_FILE => return error.AccessDenied,
- .INVALID_HANDLE => |err| return w.statusBug(err),
+ .ACCESS_DENIED => return syscall.fail(error.AccessDenied),
+ .PIPE_BUSY => return syscall.fail(error.PipeBusy),
+ .PIPE_NOT_AVAILABLE => return syscall.fail(error.NoDevice),
+ .OBJECT_PATH_SYNTAX_BAD => |err| return syscall.ntstatusBug(err),
+ .OBJECT_NAME_COLLISION => return syscall.fail(error.PathAlreadyExists),
+ .FILE_IS_A_DIRECTORY => return syscall.fail(error.IsDir),
+ .NOT_A_DIRECTORY => return syscall.fail(error.NotDir),
+ .USER_MAPPED_FILE => return syscall.fail(error.AccessDenied),
+ .INVALID_HANDLE => |err| return syscall.ntstatusBug(err),
.DELETE_PENDING => {
// This error means that there *was* a file in this location on
// the file system, but it was deleted. However, the OS is not
// finished with the deletion operation, and so this CreateFile
// call has failed. Here, we simulate the kernel bug being
// fixed by sleeping and retrying until the error goes away.
+ syscall.finish();
if (max_attempts - attempt == 0) return error.SharingViolation;
_ = w.kernel32.SleepEx((@as(u32, 1) << attempt) >> 1, w.TRUE);
attempt += 1;
+ syscall = try .start();
continue;
},
- .VIRUS_INFECTED, .VIRUS_DELETED => return error.AntivirusInterference,
- else => return w.unexpectedStatus(rc),
+ .VIRUS_INFECTED, .VIRUS_DELETED => return syscall.fail(error.AntivirusInterference),
+ else => |rc| return syscall.unexpectedNtstatus(rc),
}
};
errdefer w.CloseHandle(handle);
@@ -3154,7 +3658,8 @@ pub fn dirOpenFileWtf16(
.shared => false,
.exclusive => true,
};
- const status = w.ntdll.NtLockFile(
+ syscall = try .start();
+ while (true) switch (w.ntdll.NtLockFile(
handle,
null,
null,
@@ -3165,14 +3670,13 @@ pub fn dirOpenFileWtf16(
null,
@intFromBool(flags.lock_nonblocking),
@intFromBool(exclusive),
- );
- switch (status) {
- .SUCCESS => {},
- .INSUFFICIENT_RESOURCES => return error.SystemResources,
- .LOCK_NOT_GRANTED => return error.WouldBlock,
- .ACCESS_VIOLATION => |err| return windows.statusBug(err), // bad io_status_block pointer
- else => return windows.unexpectedStatus(status),
- }
+ )) {
+ .SUCCESS => break syscall.finish(),
+ .INSUFFICIENT_RESOURCES => return syscall.fail(error.SystemResources),
+ .LOCK_NOT_GRANTED => return syscall.fail(error.WouldBlock),
+ .ACCESS_VIOLATION => |err| return syscall.ntstatusBug(err), // bad io_status_block pointer
+ else => |status| return syscall.unexpectedNtstatus(status),
+ };
return .{ .handle = handle };
}
@@ -3184,7 +3688,6 @@ fn dirOpenFileWasi(
) File.OpenError!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
@@ -3214,19 +3717,19 @@ fn dirOpenFileWasi(
const inheriting: wasi.rights_t = .{};
const fdflags: wasi.fdflags_t = .{};
var fd: posix.fd_t = undefined;
- try current_thread.beginSyscall();
+ const syscall: Syscall = try .start();
while (true) {
switch (wasi.path_open(dir.handle, lookup_flags, sub_path.ptr, sub_path.len, oflags, base, inheriting, fdflags, &fd)) {
.SUCCESS => {
- current_thread.endSyscall();
+ syscall.finish();
break;
},
.INTR => {
- try current_thread.checkCancel();
+ try syscall.checkCancel();
continue;
},
else => |e| {
- current_thread.endSyscall();
+ syscall.finish();
switch (e) {
.FAULT => |err| return errnoBug(err),
.BADF => |err| return errnoBug(err), // File descriptor used after closed.
@@ -3283,14 +3786,13 @@ fn dirOpenDirPosix(
options: Dir.OpenOptions,
) Dir.OpenError!Dir {
const t: *Threaded = @ptrCast(@alignCast(userdata));
+ _ = t;
if (is_windows) {
const sub_path_w = try windows.sliceToPrefixedFileW(dir.handle, sub_path);
- return dirOpenDirWindows(t, dir, sub_path_w.span(), options);
+ return dirOpenDirWindows(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);
@@ -3311,20 +3813,20 @@ fn dirOpenDirPosix(
if (@hasField(posix.O, "PATH") and !options.iterate)
flags.PATH = true;
- try current_thread.beginSyscall();
+ const syscall: Syscall = try .start();
while (true) {
const rc = openat_sym(dir.handle, sub_path_posix, flags, @as(usize, 0));
switch (posix.errno(rc)) {
.SUCCESS => {
- current_thread.endSyscall();
+ syscall.finish();
return .{ .handle = @intCast(rc) };
},
.INTR => {
- try current_thread.checkCancel();
+ try syscall.checkCancel();
continue;
},
else => |e| {
- current_thread.endSyscall();
+ syscall.finish();
switch (e) {
.FAULT => |err| return errnoBug(err),
.INVAL => return error.BadPathName,
@@ -3356,27 +3858,27 @@ fn dirOpenDirHaiku(
options: Dir.OpenOptions,
) Dir.OpenError!Dir {
const t: *Threaded = @ptrCast(@alignCast(userdata));
- const current_thread = Thread.getCurrent(t);
+ _ = t;
var path_buffer: [posix.PATH_MAX]u8 = undefined;
const sub_path_posix = try pathToPosix(sub_path, &path_buffer);
_ = options;
- try current_thread.beginSyscall();
+ const syscall: Syscall = try .start();
while (true) {
const rc = posix.system._kern_open_dir(dir.handle, sub_path_posix);
if (rc >= 0) {
- current_thread.endSyscall();
+ syscall.finish();
return .{ .handle = rc };
}
switch (@as(posix.E, @enumFromInt(rc))) {
.INTR => {
- try current_thread.checkCancel();
+ try syscall.checkCancel();
continue;
},
else => |e| {
- current_thread.endSyscall();
+ syscall.finish();
switch (e) {
.FAULT => |err| return errnoBug(err),
.INVAL => |err| return errnoBug(err),
@@ -3400,12 +3902,10 @@ fn dirOpenDirHaiku(
}
pub fn dirOpenDirWindows(
- t: *Io.Threaded,
dir: Dir,
sub_path_w: [:0]const u16,
options: Dir.OpenOptions,
) Dir.OpenError!Dir {
- const current_thread = Thread.getCurrent(t);
const w = windows;
const path_len_bytes: u16 = @intCast(sub_path_w.len * 2);
@@ -3416,8 +3916,9 @@ pub fn dirOpenDirWindows(
};
var io_status_block: w.IO_STATUS_BLOCK = undefined;
var result: Dir = .{ .handle = undefined };
- try current_thread.checkCancel();
- const rc = w.ntdll.NtCreateFile(
+
+ const syscall: Syscall = try .start();
+ while (true) switch (w.ntdll.NtCreateFile(
&result.handle,
// TODO remove some of these flags if options.access_sub_paths is false
.{
@@ -3453,21 +3954,26 @@ pub fn dirOpenDirWindows(
},
null,
0,
- );
-
- switch (rc) {
- .SUCCESS => return result,
- .OBJECT_NAME_INVALID => return error.BadPathName,
- .OBJECT_NAME_NOT_FOUND => return error.FileNotFound,
+ )) {
+ .SUCCESS => {
+ syscall.finish();
+ return result;
+ },
+ .CANCELLED => {
+ try syscall.checkCancel();
+ continue;
+ },
+ .OBJECT_NAME_INVALID => return syscall.fail(error.BadPathName),
+ .OBJECT_NAME_NOT_FOUND => return syscall.fail(error.FileNotFound),
.OBJECT_NAME_COLLISION => |err| return w.statusBug(err),
- .OBJECT_PATH_NOT_FOUND => return error.FileNotFound,
- .NOT_A_DIRECTORY => return error.NotDir,
+ .OBJECT_PATH_NOT_FOUND => return syscall.fail(error.FileNotFound),
+ .NOT_A_DIRECTORY => return syscall.fail(error.NotDir),
// This can happen if the directory has 'List folder contents' permission set to 'Deny'
// and the directory is trying to be opened for iteration.
- .ACCESS_DENIED => return error.AccessDenied,
- .INVALID_PARAMETER => |err| return w.statusBug(err),
- else => return w.unexpectedStatus(rc),
- }
+ .ACCESS_DENIED => return syscall.fail(error.AccessDenied),
+ .INVALID_PARAMETER => |err| return syscall.ntstatusBug(err),
+ else => |rc| return syscall.unexpectedNtstatus(rc),
+ };
}
fn dirClose(userdata: ?*anyopaque, dirs: []const Dir) void {
@@ -3490,7 +3996,7 @@ const dirRead = switch (native_os) {
fn dirReadLinux(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) Dir.Reader.Error!usize {
const linux = std.os.linux;
const t: *Threaded = @ptrCast(@alignCast(userdata));
- const current_thread = Thread.getCurrent(t);
+ _ = t;
var buffer_index: usize = 0;
while (buffer.len - buffer_index != 0) {
if (dr.end - dr.index == 0) {
@@ -3498,26 +4004,26 @@ fn dirReadLinux(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) Dir
// buffered data.
if (buffer_index != 0) break;
if (dr.state == .reset) {
- posixSeekTo(current_thread, dr.dir.handle, 0) catch |err| switch (err) {
+ posixSeekTo(dr.dir.handle, 0) catch |err| switch (err) {
error.Unseekable => return error.Unexpected,
else => |e| return e,
};
dr.state = .reading;
}
- try current_thread.beginSyscall();
+ const syscall: Syscall = try .start();
const n = while (true) {
const rc = linux.getdents64(dr.dir.handle, dr.buffer.ptr, dr.buffer.len);
switch (linux.errno(rc)) {
.SUCCESS => {
- current_thread.endSyscall();
+ syscall.finish();
break rc;
},
.INTR => {
- try current_thread.checkCancel();
+ try syscall.checkCancel();
continue;
},
else => |e| {
- current_thread.endSyscall();
+ syscall.finish();
switch (e) {
.BADF => |err| return errnoBug(err), // Dir is invalid or was opened without iteration ability.
.FAULT => |err| return errnoBug(err),
@@ -3587,7 +4093,7 @@ fn dirReadLinux(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) Dir
fn dirReadDarwin(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) Dir.Reader.Error!usize {
const t: *Threaded = @ptrCast(@alignCast(userdata));
- const current_thread = Thread.getCurrent(t);
+ _ = t;
const Header = extern struct {
seek: i64,
};
@@ -3606,27 +4112,27 @@ fn dirReadDarwin(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) Di
// buffered data.
if (buffer_index != 0) break;
if (dr.state == .reset) {
- posixSeekTo(current_thread, dr.dir.handle, 0) catch |err| switch (err) {
+ posixSeekTo(dr.dir.handle, 0) catch |err| switch (err) {
error.Unseekable => return error.Unexpected,
else => |e| return e,
};
dr.state = .reading;
}
const dents_buffer = dr.buffer[header_end..];
- try current_thread.beginSyscall();
+ const syscall: Syscall = try .start();
const n: usize = while (true) {
const rc = posix.system.getdirentries(dr.dir.handle, dents_buffer.ptr, dents_buffer.len, &header.seek);
switch (posix.errno(rc)) {
.SUCCESS => {
- current_thread.endSyscall();
+ syscall.finish();
break @intCast(rc);
},
.INTR => {
- try current_thread.checkCancel();
+ try syscall.checkCancel();
continue;
},
else => |e| {
- current_thread.endSyscall();
+ syscall.finish();
switch (e) {
.BADF => |err| return errnoBug(err), // Dir is invalid or was opened without iteration ability.
.FAULT => |err| return errnoBug(err),
@@ -3675,7 +4181,7 @@ fn dirReadDarwin(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) Di
fn dirReadBsd(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) Dir.Reader.Error!usize {
const t: *Threaded = @ptrCast(@alignCast(userdata));
- const current_thread = Thread.getCurrent(t);
+ _ = t;
var buffer_index: usize = 0;
while (buffer.len - buffer_index != 0) {
if (dr.end - dr.index == 0) {
@@ -3683,26 +4189,26 @@ fn dirReadBsd(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) Dir.R
// buffered data.
if (buffer_index != 0) break;
if (dr.state == .reset) {
- posixSeekTo(current_thread, dr.dir.handle, 0) catch |err| switch (err) {
+ posixSeekTo(dr.dir.handle, 0) catch |err| switch (err) {
error.Unseekable => return error.Unexpected,
else => |e| return e,
};
dr.state = .reading;
}
- try current_thread.beginSyscall();
+ const syscall: Syscall = try .start();
const n: usize = while (true) {
const rc = posix.system.getdents(dr.dir.handle, dr.buffer.ptr, dr.buffer.len);
switch (posix.errno(rc)) {
.SUCCESS => {
- current_thread.endSyscall();
+ syscall.finish();
break @intCast(rc);
},
.INTR => {
- try current_thread.checkCancel();
+ try syscall.checkCancel();
continue;
},
else => |e| {
- current_thread.endSyscall();
+ syscall.finish();
switch (e) {
.BADF => |err| return errnoBug(err), // Dir is invalid or was opened without iteration ability
.FAULT => |err| return errnoBug(err),
@@ -3769,7 +4275,7 @@ fn dirReadBsd(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) Dir.R
fn dirReadIllumos(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) Dir.Reader.Error!usize {
const t: *Threaded = @ptrCast(@alignCast(userdata));
- const current_thread = Thread.getCurrent(t);
+ _ = t;
var buffer_index: usize = 0;
while (buffer.len - buffer_index != 0) {
if (dr.end - dr.index == 0) {
@@ -3777,26 +4283,26 @@ fn dirReadIllumos(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) D
// buffered data.
if (buffer_index != 0) break;
if (dr.state == .reset) {
- posixSeekTo(current_thread, dr.dir.handle, 0) catch |err| switch (err) {
+ posixSeekTo(dr.dir.handle, 0) catch |err| switch (err) {
error.Unseekable => return error.Unexpected,
else => |e| return e,
};
dr.state = .reading;
}
- try current_thread.beginSyscall();
+ const syscall: Syscall = try .start();
const n: usize = while (true) {
const rc = posix.system.getdents(dr.dir.handle, dr.buffer.ptr, dr.buffer.len);
switch (posix.errno(rc)) {
.SUCCESS => {
- current_thread.endSyscall();
+ syscall.finish();
break rc;
},
.INTR => {
- try current_thread.checkCancel();
+ try syscall.checkCancel();
continue;
},
else => |e| {
- current_thread.endSyscall();
+ syscall.finish();
switch (e) {
.BADF => |err| return errnoBug(err), // Dir is invalid or was opened without iteration ability
.FAULT => |err| return errnoBug(err),
@@ -3822,7 +4328,7 @@ fn dirReadIllumos(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) D
if (std.mem.eql(u8, name, ".") or std.mem.eql(u8, name, "..")) continue;
// illumos dirent doesn't expose type, so we have to call stat to get it.
- const stat = try posixStatFile(current_thread, dr.dir.handle, name, posix.AT.SYMLINK_NOFOLLOW);
+ const stat = try posixStatFile(dr.dir.handle, name, posix.AT.SYMLINK_NOFOLLOW);
buffer[buffer_index] = .{
.name = name,
@@ -3843,7 +4349,7 @@ fn dirReadHaiku(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) Dir
fn dirReadWindows(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) Dir.Reader.Error!usize {
const t: *Threaded = @ptrCast(@alignCast(userdata));
- const current_thread = Thread.getCurrent(t);
+ _ = t;
const w = windows;
// We want to be able to use the `dr.buffer` for both the NtQueryDirectoryFile call (which
@@ -3907,9 +4413,9 @@ fn dirReadWindows(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) D
// buffered data.
if (buffer_index != 0) break;
- try current_thread.checkCancel();
var io_status_block: w.IO_STATUS_BLOCK = undefined;
- const rc = w.ntdll.NtQueryDirectoryFile(
+ const syscall: Syscall = try .start();
+ const rc = while (true) switch (w.ntdll.NtQueryDirectoryFile(
dr.dir.handle,
null,
null,
@@ -3921,7 +4427,16 @@ fn dirReadWindows(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) D
w.FALSE,
null,
@intFromBool(dr.state == .reset),
- );
+ )) {
+ .CANCELLED => {
+ try syscall.checkCancel();
+ continue;
+ },
+ else => |rc| {
+ syscall.finish();
+ break rc;
+ },
+ };
dr.state = .reading;
if (io_status_block.Information == 0) {
dr.state = .finished;
@@ -3993,7 +4508,7 @@ fn dirReadWasi(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) Dir.
// complexity here.
const wasi = std.os.wasi;
const t: *Threaded = @ptrCast(@alignCast(userdata));
- const current_thread = Thread.getCurrent(t);
+ _ = t;
const Header = extern struct {
cookie: u64,
};
@@ -4019,19 +4534,19 @@ fn dirReadWasi(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) Dir.
}
const dents_buffer = dr.buffer[header_end..];
var n: usize = undefined;
- try current_thread.beginSyscall();
+ const syscall: Syscall = try .start();
while (true) {
switch (wasi.fd_readdir(dr.dir.handle, dents_buffer.ptr, dents_buffer.len, header.cookie, &n)) {
.SUCCESS => {
- current_thread.endSyscall();
+ syscall.finish();
break;
},
.INTR => {
- try current_thread.checkCancel();
+ try syscall.checkCancel();
continue;
},
else => |e| {
- current_thread.endSyscall();
+ syscall.finish();
switch (e) {
.BADF => |err| return errnoBug(err), // Dir is invalid or was opened without iteration ability.
.FAULT => |err| return errnoBug(err),
@@ -4107,34 +4622,42 @@ const dirRealPathFile = switch (native_os) {
fn dirRealPathFileWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, out_buffer: []u8) Dir.RealPathFileError!usize {
const t: *Threaded = @ptrCast(@alignCast(userdata));
- const current_thread = Thread.getCurrent(t);
-
- try current_thread.checkCancel();
+ _ = t;
var path_name_w = try windows.sliceToPrefixedFileW(dir.handle, sub_path);
- const h_file = blk: {
- const res = windows.OpenFile(path_name_w.span(), .{
- .dir = dir.handle,
- .access_mask = .{
- .GENERIC = .{ .READ = true },
- .STANDARD = .{ .SYNCHRONIZE = true },
- },
- .creation = .OPEN,
- .filter = .any,
- }) catch |err| switch (err) {
- error.WouldBlock => unreachable,
- else => |e| return e,
- };
- break :blk res;
+ const h_file = handle: {
+ const syscall: Syscall = try .start();
+ while (true) {
+ if (windows.OpenFile(path_name_w.span(), .{
+ .dir = dir.handle,
+ .access_mask = .{
+ .GENERIC = .{ .READ = true },
+ .STANDARD = .{ .SYNCHRONIZE = true },
+ },
+ .creation = .OPEN,
+ .filter = .any,
+ })) |handle| {
+ syscall.finish();
+ break :handle handle;
+ } else |err| switch (err) {
+ error.WouldBlock => unreachable,
+ error.OperationCanceled => {
+ try syscall.checkCancel();
+ continue;
+ },
+ else => |e| return syscall.fail(e),
+ }
+ }
};
defer windows.CloseHandle(h_file);
- return realPathWindows(current_thread, h_file, out_buffer);
+ return realPathWindows(h_file, out_buffer);
}
-fn realPathWindows(current_thread: *Thread, h_file: windows.HANDLE, out_buffer: []u8) File.RealPathError!usize {
- _ = current_thread; // TODO move GetFinalPathNameByHandle logic into std.Io.Threaded and add cancel checks
+fn realPathWindows(h_file: windows.HANDLE, out_buffer: []u8) File.RealPathError!usize {
var wide_buf: [windows.PATH_MAX_WIDE]u16 = undefined;
+ // TODO move GetFinalPathNameByHandle logic into std.Io.Threaded and add cancel checks
+ try Thread.checkCancel();
const wide_slice = try windows.GetFinalPathNameByHandle(h_file, .{}, &wide_buf);
const len = std.unicode.calcWtf8Len(wide_slice);
@@ -4148,26 +4671,26 @@ fn dirRealPathFilePosix(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, o
if (native_os == .wasi) return error.OperationUnsupported;
const t: *Threaded = @ptrCast(@alignCast(userdata));
- const current_thread = Thread.getCurrent(t);
+ _ = t;
var path_buffer: [posix.PATH_MAX]u8 = undefined;
const sub_path_posix = try pathToPosix(sub_path, &path_buffer);
if (builtin.link_libc and dir.handle == posix.AT.FDCWD) {
if (out_buffer.len < posix.PATH_MAX) return error.NameTooLong;
- try current_thread.beginSyscall();
+ const syscall: Syscall = try .start();
while (true) {
if (std.c.realpath(sub_path_posix, out_buffer.ptr)) |redundant_pointer| {
- current_thread.endSyscall();
+ syscall.finish();
assert(redundant_pointer == out_buffer.ptr);
return std.mem.indexOfScalar(u8, out_buffer, 0) orelse out_buffer.len;
}
const err: posix.E = @enumFromInt(std.c._errno().*);
if (err == .INTR) {
- try current_thread.checkCancel();
+ try syscall.checkCancel();
continue;
}
- current_thread.endSyscall();
+ syscall.finish();
switch (err) {
.INVAL => return errnoBug(err),
.BADF => return errnoBug(err),
@@ -4191,20 +4714,20 @@ fn dirRealPathFilePosix(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, o
const mode: posix.mode_t = 0;
- try current_thread.beginSyscall();
+ const syscall: Syscall = try .start();
const fd: posix.fd_t = while (true) {
const rc = openat_sym(dir.handle, sub_path_posix, flags, mode);
switch (posix.errno(rc)) {
.SUCCESS => {
- current_thread.endSyscall();
+ syscall.finish();
break @intCast(rc);
},
.INTR => {
- try current_thread.checkCancel();
+ try syscall.checkCancel();
continue;
},
else => |e| {
- current_thread.endSyscall();
+ syscall.finish();
switch (e) {
.FAULT => |err| return errnoBug(err),
.INVAL => return error.BadPathName,
@@ -4234,7 +4757,7 @@ fn dirRealPathFilePosix(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, o
}
};
defer posix.close(fd);
- return realPathPosix(current_thread, fd, out_buffer);
+ return realPathPosix(fd, out_buffer);
}
const dirRealPath = switch (native_os) {
@@ -4245,14 +4768,14 @@ const dirRealPath = switch (native_os) {
fn dirRealPathPosix(userdata: ?*anyopaque, dir: Dir, out_buffer: []u8) Dir.RealPathError!usize {
if (native_os == .wasi) return error.OperationUnsupported;
const t: *Threaded = @ptrCast(@alignCast(userdata));
- const current_thread = Thread.getCurrent(t);
- return realPathPosix(current_thread, dir.handle, out_buffer);
+ _ = t;
+ return realPathPosix(dir.handle, out_buffer);
}
fn dirRealPathWindows(userdata: ?*anyopaque, dir: Dir, out_buffer: []u8) Dir.RealPathError!usize {
const t: *Threaded = @ptrCast(@alignCast(userdata));
- const current_thread = Thread.getCurrent(t);
- return realPathWindows(current_thread, dir.handle, out_buffer);
+ _ = t;
+ return realPathWindows(dir.handle, out_buffer);
}
const fileRealPath = switch (native_os) {
@@ -4263,35 +4786,35 @@ const fileRealPath = switch (native_os) {
fn fileRealPathWindows(userdata: ?*anyopaque, file: File, out_buffer: []u8) File.RealPathError!usize {
if (native_os == .wasi) return error.OperationUnsupported;
const t: *Threaded = @ptrCast(@alignCast(userdata));
- const current_thread = Thread.getCurrent(t);
- return realPathWindows(current_thread, file.handle, out_buffer);
+ _ = t;
+ return realPathWindows(file.handle, out_buffer);
}
fn fileRealPathPosix(userdata: ?*anyopaque, file: File, out_buffer: []u8) File.RealPathError!usize {
if (native_os == .wasi) return error.OperationUnsupported;
const t: *Threaded = @ptrCast(@alignCast(userdata));
- const current_thread = Thread.getCurrent(t);
- return realPathPosix(current_thread, file.handle, out_buffer);
+ _ = t;
+ return realPathPosix(file.handle, out_buffer);
}
-fn realPathPosix(current_thread: *Thread, fd: posix.fd_t, out_buffer: []u8) File.RealPathError!usize {
+fn realPathPosix(fd: posix.fd_t, out_buffer: []u8) File.RealPathError!usize {
switch (native_os) {
.netbsd, .dragonfly, .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => {
var sufficient_buffer: [posix.PATH_MAX]u8 = undefined;
@memset(&sufficient_buffer, 0);
- try current_thread.beginSyscall();
+ const syscall: Syscall = try .start();
while (true) {
switch (posix.errno(posix.system.fcntl(fd, posix.F.GETPATH, &sufficient_buffer))) {
.SUCCESS => {
- current_thread.endSyscall();
+ syscall.finish();
break;
},
.INTR => {
- try current_thread.checkCancel();
+ try syscall.checkCancel();
continue;
},
else => |e| {
- current_thread.endSyscall();
+ syscall.finish();
switch (e) {
.ACCES => return error.AccessDenied,
.BADF => return error.FileNotFound,
@@ -4313,21 +4836,21 @@ fn realPathPosix(current_thread: *Thread, fd: posix.fd_t, out_buffer: []u8) File
var procfs_buf: ["/proc/self/path/-2147483648\x00".len]u8 = undefined;
const template = if (native_os == .illumos) "/proc/self/path/{d}" else "/proc/self/fd/{d}";
const proc_path = std.fmt.bufPrintSentinel(&procfs_buf, template, .{fd}, 0) catch unreachable;
- try current_thread.beginSyscall();
+ const syscall: Syscall = try .start();
while (true) {
const rc = posix.system.readlink(proc_path, out_buffer.ptr, out_buffer.len);
switch (posix.errno(rc)) {
.SUCCESS => {
- current_thread.endSyscall();
+ syscall.finish();
const len: usize = @bitCast(rc);
return len;
},
.INTR => {
- try current_thread.checkCancel();
+ try syscall.checkCancel();
continue;
},
else => |e| {
- current_thread.endSyscall();
+ syscall.finish();
switch (e) {
.ACCES => return error.AccessDenied,
.FAULT => |err| return errnoBug(err),
@@ -4347,23 +4870,23 @@ fn realPathPosix(current_thread: *Thread, fd: posix.fd_t, out_buffer: []u8) File
.freebsd => {
var k_file: std.c.kinfo_file = undefined;
k_file.structsize = std.c.KINFO_FILE_SIZE;
- try current_thread.beginSyscall();
+ const syscall: Syscall = try .start();
while (true) {
switch (posix.errno(std.c.fcntl(fd, std.c.F.KINFO, @intFromPtr(&k_file)))) {
.SUCCESS => {
- current_thread.endSyscall();
+ syscall.finish();
break;
},
.INTR => {
- try current_thread.checkCancel();
+ try syscall.checkCancel();
continue;
},
.BADF => {
- current_thread.endSyscall();
+ syscall.finish();
return error.FileNotFound;
},
else => |err| {
- current_thread.endSyscall();
+ syscall.finish();
return posix.unexpectedErrno(err);
},
}
@@ -4394,21 +4917,21 @@ fn dirDeleteFileWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8) D
fn dirDeleteFileWasi(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8) Dir.DeleteFileError!void {
if (builtin.link_libc) return dirDeleteFilePosix(userdata, dir, sub_path);
const t: *Threaded = @ptrCast(@alignCast(userdata));
- const current_thread = Thread.getCurrent(t);
- try current_thread.beginSyscall();
+ _ = t;
+ const syscall: Syscall = try .start();
while (true) {
const res = std.os.wasi.path_unlink_file(dir.handle, sub_path.ptr, sub_path.len);
switch (res) {
.SUCCESS => {
- current_thread.endSyscall();
+ syscall.finish();
return;
},
.INTR => {
- try current_thread.checkCancel();
+ try syscall.checkCancel();
continue;
},
else => |e| {
- current_thread.endSyscall();
+ syscall.finish();
switch (e) {
.ACCES => return error.AccessDenied,
.PERM => return error.PermissionDenied,
@@ -4435,20 +4958,20 @@ fn dirDeleteFileWasi(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8) Dir.
fn dirDeleteFilePosix(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8) Dir.DeleteFileError!void {
const t: *Threaded = @ptrCast(@alignCast(userdata));
- const current_thread = Thread.getCurrent(t);
+ _ = t;
var path_buffer: [posix.PATH_MAX]u8 = undefined;
const sub_path_posix = try pathToPosix(sub_path, &path_buffer);
- try current_thread.beginSyscall();
+ const syscall: Syscall = try .start();
while (true) {
switch (posix.errno(posix.system.unlinkat(dir.handle, sub_path_posix, 0))) {
.SUCCESS => {
- current_thread.endSyscall();
+ syscall.finish();
return;
},
.INTR => {
- try current_thread.checkCancel();
+ try syscall.checkCancel();
continue;
},
// Some systems return permission errors when trying to delete a
@@ -4460,15 +4983,15 @@ fn dirDeleteFilePosix(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8) Dir
// Don't follow symlinks to match unlinkat (which acts on symlinks rather than follows them).
var st = std.mem.zeroes(posix.Stat);
while (true) {
- try current_thread.checkCancel();
+ try syscall.checkCancel();
switch (posix.errno(fstatat_sym(dir.handle, sub_path_posix, &st, posix.AT.SYMLINK_NOFOLLOW))) {
.SUCCESS => {
- current_thread.endSyscall();
+ syscall.finish();
break;
},
.INTR => continue,
else => {
- current_thread.endSyscall();
+ syscall.finish();
return error.PermissionDenied;
},
}
@@ -4480,12 +5003,12 @@ fn dirDeleteFilePosix(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8) Dir
return error.PermissionDenied;
},
else => {
- current_thread.endSyscall();
+ syscall.finish();
return error.PermissionDenied;
},
},
else => |e| {
- current_thread.endSyscall();
+ syscall.finish();
switch (e) {
.ACCES => return error.AccessDenied,
.BUSY => return error.FileBusy,
@@ -4525,74 +5048,74 @@ fn dirDeleteDirWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8) Di
fn dirDeleteWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, remove_dir: bool) (Dir.DeleteDirError || Dir.DeleteFileError)!void {
const t: *Threaded = @ptrCast(@alignCast(userdata));
- const current_thread = Thread.getCurrent(t);
+ _ = t;
const w = windows;
- try current_thread.checkCancel();
-
const sub_path_w_buf = try w.sliceToPrefixedFileW(dir.handle, sub_path);
const sub_path_w = sub_path_w_buf.span();
const path_len_bytes = @as(u16, @intCast(sub_path_w.len * 2));
var nt_name: w.UNICODE_STRING = .{
.Length = path_len_bytes,
- .MaximumLength = path_len_bytes,
- // The Windows API makes this mutable, but it will not mutate here.
- .Buffer = @constCast(sub_path_w.ptr),
- };
-
- if (sub_path_w[0] == '.' and sub_path_w[1] == 0) {
- // Windows does not recognize this, but it does work with empty string.
- nt_name.Length = 0;
- }
- if (sub_path_w[0] == '.' and sub_path_w[1] == '.' and sub_path_w[2] == 0) {
- // Can't remove the parent directory with an open handle.
- return error.FileBusy;
- }
-
- var io_status_block: w.IO_STATUS_BLOCK = undefined;
- var tmp_handle: w.HANDLE = undefined;
- var rc = w.ntdll.NtCreateFile(
- &tmp_handle,
- .{ .STANDARD = .{
- .RIGHTS = .{ .DELETE = true },
- .SYNCHRONIZE = true,
- } },
- &.{
- .Length = @sizeOf(w.OBJECT_ATTRIBUTES),
- .RootDirectory = if (std.fs.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle,
- .Attributes = .{},
- .ObjectName = &nt_name,
- .SecurityDescriptor = null,
- .SecurityQualityOfService = null,
- },
- &io_status_block,
- null,
- .{},
- .VALID_FLAGS,
- .OPEN,
- .{
- .DIRECTORY_FILE = remove_dir,
- .NON_DIRECTORY_FILE = !remove_dir,
- .OPEN_REPARSE_POINT = true, // would we ever want to delete the target instead?
- },
- null,
- 0,
- );
- switch (rc) {
- .SUCCESS => {},
- .OBJECT_NAME_INVALID => |err| return w.statusBug(err),
- .OBJECT_NAME_NOT_FOUND => return error.FileNotFound,
- .OBJECT_PATH_NOT_FOUND => return error.FileNotFound,
- .BAD_NETWORK_PATH => return error.NetworkNotFound, // \\server was not found
- .BAD_NETWORK_NAME => return error.NetworkNotFound, // \\server was found but \\server\share wasn't
- .INVALID_PARAMETER => |err| return w.statusBug(err),
- .FILE_IS_A_DIRECTORY => return error.IsDir,
- .NOT_A_DIRECTORY => return error.NotDir,
- .SHARING_VIOLATION => return error.FileBusy,
- .ACCESS_DENIED => return error.AccessDenied,
- .DELETE_PENDING => return,
- else => return w.unexpectedStatus(rc),
+ .MaximumLength = path_len_bytes,
+ // The Windows API makes this mutable, but it will not mutate here.
+ .Buffer = @constCast(sub_path_w.ptr),
+ };
+
+ if (sub_path_w[0] == '.' and sub_path_w[1] == 0) {
+ // Windows does not recognize this, but it does work with empty string.
+ nt_name.Length = 0;
+ }
+ if (sub_path_w[0] == '.' and sub_path_w[1] == '.' and sub_path_w[2] == 0) {
+ // Can't remove the parent directory with an open handle.
+ return error.FileBusy;
+ }
+
+ var io_status_block: w.IO_STATUS_BLOCK = undefined;
+ var tmp_handle: w.HANDLE = undefined;
+ {
+ const syscall: Syscall = try .start();
+ while (true) switch (w.ntdll.NtCreateFile(
+ &tmp_handle,
+ .{ .STANDARD = .{
+ .RIGHTS = .{ .DELETE = true },
+ .SYNCHRONIZE = true,
+ } },
+ &.{
+ .Length = @sizeOf(w.OBJECT_ATTRIBUTES),
+ .RootDirectory = if (std.fs.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle,
+ .Attributes = .{},
+ .ObjectName = &nt_name,
+ .SecurityDescriptor = null,
+ .SecurityQualityOfService = null,
+ },
+ &io_status_block,
+ null,
+ .{},
+ .VALID_FLAGS,
+ .OPEN,
+ .{
+ .DIRECTORY_FILE = remove_dir,
+ .NON_DIRECTORY_FILE = !remove_dir,
+ .OPEN_REPARSE_POINT = true, // would we ever want to delete the target instead?
+ },
+ null,
+ 0,
+ )) {
+ .SUCCESS => break syscall.finish(),
+ .OBJECT_NAME_INVALID => |err| return syscall.ntstatusBug(err),
+ .OBJECT_NAME_NOT_FOUND => return syscall.fail(error.FileNotFound),
+ .OBJECT_PATH_NOT_FOUND => return syscall.fail(error.FileNotFound),
+ .BAD_NETWORK_PATH => return syscall.fail(error.NetworkNotFound), // \\server was not found
+ .BAD_NETWORK_NAME => return syscall.fail(error.NetworkNotFound), // \\server was found but \\server\share wasn't
+ .INVALID_PARAMETER => |err| return syscall.ntstatusBug(err),
+ .FILE_IS_A_DIRECTORY => return syscall.fail(error.IsDir),
+ .NOT_A_DIRECTORY => return syscall.fail(error.NotDir),
+ .SHARING_VIOLATION => return syscall.fail(error.FileBusy),
+ .ACCESS_DENIED => return syscall.fail(error.AccessDenied),
+ .DELETE_PENDING => return syscall.finish(),
+ else => |rc| return syscall.unexpectedNtstatus(rc),
+ };
}
defer w.CloseHandle(tmp_handle);
@@ -4607,9 +5130,7 @@ fn dirDeleteWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, remov
//
// The strategy here is just to try using FileDispositionInformationEx and fall back to
// FileDispositionInformation if the return value lets us know that some aspect of it is not supported.
- const need_fallback = need_fallback: {
- try current_thread.checkCancel();
-
+ const rc = rc: {
// Deletion with posix semantics if the filesystem supports it.
const info: w.FILE.DISPOSITION.INFORMATION.EX = .{ .Flags = .{
.DELETE = true,
@@ -4617,29 +5138,32 @@ fn dirDeleteWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, remov
.IGNORE_READONLY_ATTRIBUTE = true,
} };
- rc = w.ntdll.NtSetInformationFile(
+ const syscall: Syscall = try .start();
+ while (true) switch (w.ntdll.NtSetInformationFile(
tmp_handle,
&io_status_block,
&info,
@sizeOf(w.FILE.DISPOSITION.INFORMATION.EX),
.DispositionEx,
- );
- switch (rc) {
- .SUCCESS => return,
+ )) {
+ .CANCELLED => {
+ try syscall.checkCancel();
+ continue;
+ },
// The filesystem does not support FileDispositionInformationEx
.INVALID_PARAMETER,
// The operating system does not support FileDispositionInformationEx
.INVALID_INFO_CLASS,
// The operating system does not support one of the flags
.NOT_SUPPORTED,
- => break :need_fallback true,
- // For all other statuses, fall down to the switch below to handle them.
- else => break :need_fallback false,
- }
- };
+ => break, // use fallback path below; `syscall` still active
- if (need_fallback) {
- try current_thread.checkCancel();
+ // For all other statuses, fall down to the switch below to handle them.
+ else => |rc| {
+ syscall.finish();
+ break :rc rc;
+ },
+ };
// Deletion with file pending semantics, which requires waiting or moving
// files to get them removed (from here).
@@ -4647,14 +5171,23 @@ fn dirDeleteWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, remov
.DeleteFile = w.TRUE,
};
- rc = w.ntdll.NtSetInformationFile(
+ while (true) switch (w.ntdll.NtSetInformationFile(
tmp_handle,
&io_status_block,
&file_dispo,
@sizeOf(w.FILE.DISPOSITION.INFORMATION),
.Disposition,
- );
- }
+ )) {
+ .CANCELLED => {
+ try syscall.checkCancel();
+ continue;
+ },
+ else => |rc| {
+ syscall.finish();
+ break :rc rc;
+ },
+ };
+ };
switch (rc) {
.SUCCESS => {},
.DIRECTORY_NOT_EMPTY => return error.DirNotEmpty,
@@ -4670,22 +5203,22 @@ fn dirDeleteDirWasi(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8) Dir.D
if (builtin.link_libc) return dirDeleteDirPosix(userdata, dir, sub_path);
const t: *Threaded = @ptrCast(@alignCast(userdata));
- const current_thread = Thread.getCurrent(t);
+ _ = t;
- try current_thread.beginSyscall();
+ const syscall: Syscall = try .start();
while (true) {
const res = std.os.wasi.path_remove_directory(dir.handle, sub_path.ptr, sub_path.len);
switch (res) {
.SUCCESS => {
- current_thread.endSyscall();
+ syscall.finish();
return;
},
.INTR => {
- try current_thread.checkCancel();
+ try syscall.checkCancel();
continue;
},
else => |e| {
- current_thread.endSyscall();
+ syscall.finish();
switch (e) {
.ACCES => return error.AccessDenied,
.PERM => return error.PermissionDenied,
@@ -4712,24 +5245,24 @@ fn dirDeleteDirWasi(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8) Dir.D
fn dirDeleteDirPosix(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8) Dir.DeleteDirError!void {
const t: *Threaded = @ptrCast(@alignCast(userdata));
- const current_thread = Thread.getCurrent(t);
+ _ = t;
var path_buffer: [posix.PATH_MAX]u8 = undefined;
const sub_path_posix = try pathToPosix(sub_path, &path_buffer);
- try current_thread.beginSyscall();
+ const syscall: Syscall = try .start();
while (true) {
switch (posix.errno(posix.system.unlinkat(dir.handle, sub_path_posix, posix.AT.REMOVEDIR))) {
.SUCCESS => {
- current_thread.endSyscall();
+ syscall.finish();
return;
},
.INTR => {
- try current_thread.checkCancel();
+ try syscall.checkCancel();
continue;
},
else => |e| {
- current_thread.endSyscall();
+ syscall.finish();
switch (e) {
.ACCES => return error.AccessDenied,
.PERM => return error.PermissionDenied,
@@ -4770,7 +5303,7 @@ fn dirRenameWindows(
) Dir.RenameError!void {
const w = windows;
const t: *Threaded = @ptrCast(@alignCast(userdata));
- const current_thread = Thread.getCurrent(t);
+ _ = t;
const old_path_w_buf = try windows.sliceToPrefixedFileW(old_dir.handle, old_sub_path);
const old_path_w = old_path_w_buf.span();
@@ -4778,23 +5311,33 @@ fn dirRenameWindows(
const new_path_w = new_path_w_buf.span();
const replace_if_exists = true;
- try current_thread.checkCancel();
-
- const src_fd = w.OpenFile(old_path_w, .{
- .dir = old_dir.handle,
- .access_mask = .{
- .GENERIC = .{ .WRITE = true },
- .STANDARD = .{
- .RIGHTS = .{ .DELETE = true },
- .SYNCHRONIZE = true,
- },
- },
- .creation = .OPEN,
- .filter = .any, // This function is supposed to rename both files and directories.
- .follow_symlinks = false,
- }) catch |err| switch (err) {
- error.WouldBlock => unreachable, // Not possible without `.share_access_nonblocking = true`.
- else => |e| return e,
+ const src_fd = src_fd: {
+ const syscall: Syscall = try .start();
+ while (true) {
+ if (w.OpenFile(old_path_w, .{
+ .dir = old_dir.handle,
+ .access_mask = .{
+ .GENERIC = .{ .WRITE = true },
+ .STANDARD = .{
+ .RIGHTS = .{ .DELETE = true },
+ .SYNCHRONIZE = true,
+ },
+ },
+ .creation = .OPEN,
+ .filter = .any, // This function is supposed to rename both files and directories.
+ .follow_symlinks = false,
+ })) |handle| {
+ syscall.finish();
+ break :src_fd handle;
+ } else |err| switch (err) {
+ error.WouldBlock => unreachable, // Not possible without `.share_access_nonblocking = true`.
+ error.OperationCanceled => {
+ try syscall.checkCancel();
+ continue;
+ },
+ else => |e| return e,
+ }
+ }
};
defer w.CloseHandle(src_fd);
@@ -4887,18 +5430,18 @@ fn dirRenameWasi(
if (builtin.link_libc) return dirRenamePosix(userdata, old_dir, old_sub_path, new_dir, new_sub_path);
const t: *Threaded = @ptrCast(@alignCast(userdata));
- const current_thread = Thread.getCurrent(t);
+ _ = t;
- try current_thread.beginSyscall();
+ const syscall: Syscall = try .start();
while (true) {
switch (std.os.wasi.path_rename(old_dir.handle, old_sub_path.ptr, old_sub_path.len, new_dir.handle, new_sub_path.ptr, new_sub_path.len)) {
- .SUCCESS => return current_thread.endSyscall(),
+ .SUCCESS => return syscall.finish(),
.INTR => {
- try current_thread.checkCancel();
+ try syscall.checkCancel();
continue;
},
else => |e| {
- current_thread.endSyscall();
+ syscall.finish();
switch (e) {
.ACCES => return error.AccessDenied,
.PERM => return error.PermissionDenied,
@@ -4935,7 +5478,7 @@ fn dirRenamePosix(
new_sub_path: []const u8,
) Dir.RenameError!void {
const t: *Threaded = @ptrCast(@alignCast(userdata));
- const current_thread = Thread.getCurrent(t);
+ _ = t;
var old_path_buffer: [posix.PATH_MAX]u8 = undefined;
var new_path_buffer: [posix.PATH_MAX]u8 = undefined;
@@ -4943,16 +5486,16 @@ fn dirRenamePosix(
const old_sub_path_posix = try pathToPosix(old_sub_path, &old_path_buffer);
const new_sub_path_posix = try pathToPosix(new_sub_path, &new_path_buffer);
- try current_thread.beginSyscall();
+ const syscall: Syscall = try .start();
while (true) {
switch (posix.errno(posix.system.renameat(old_dir.handle, old_sub_path_posix, new_dir.handle, new_sub_path_posix))) {
- .SUCCESS => return current_thread.endSyscall(),
+ .SUCCESS => return syscall.finish(),
.INTR => {
- try current_thread.checkCancel();
+ try syscall.checkCancel();
continue;
},
else => |e| {
- current_thread.endSyscall();
+ syscall.finish();
switch (e) {
.ACCES => return error.AccessDenied,
.PERM => return error.PermissionDenied,
@@ -4994,11 +5537,9 @@ fn dirSymLinkWindows(
flags: Dir.SymLinkFlags,
) Dir.SymLinkError!void {
const t: *Threaded = @ptrCast(@alignCast(userdata));
- const current_thread = Thread.getCurrent(t);
+ _ = t;
const w = windows;
- try current_thread.checkCancel();
-
// Target path does not use sliceToPrefixedFileW because certain paths
// are handled differently when creating a symlink than they would be
// when converting to an NT namespaced path. CreateSymbolicLink in
@@ -5028,22 +5569,34 @@ fn dirSymLinkWindows(
Flags: w.ULONG,
};
- const symlink_handle = w.OpenFile(sym_link_path_w.span(), .{
- .access_mask = .{
- .GENERIC = .{ .READ = true, .WRITE = true },
- .STANDARD = .{ .SYNCHRONIZE = true },
- },
- .dir = dir.handle,
- .creation = .CREATE,
- .filter = if (flags.is_directory) .dir_only else .non_directory_only,
- }) catch |err| switch (err) {
- error.IsDir => return error.PathAlreadyExists,
- error.NotDir => return error.Unexpected,
- error.WouldBlock => return error.Unexpected,
- error.PipeBusy => return error.Unexpected,
- error.NoDevice => return error.Unexpected,
- error.AntivirusInterference => return error.Unexpected,
- else => |e| return e,
+ const symlink_handle = handle: {
+ const syscall: Syscall = try .start();
+ while (true) {
+ if (w.OpenFile(sym_link_path_w.span(), .{
+ .access_mask = .{
+ .GENERIC = .{ .READ = true, .WRITE = true },
+ .STANDARD = .{ .SYNCHRONIZE = true },
+ },
+ .dir = dir.handle,
+ .creation = .CREATE,
+ .filter = if (flags.is_directory) .dir_only else .non_directory_only,
+ })) |handle| {
+ syscall.finish();
+ break :handle handle;
+ } else |err| switch (err) {
+ error.IsDir => return syscall.fail(error.PathAlreadyExists),
+ error.NotDir => return syscall.fail(error.Unexpected),
+ error.WouldBlock => return syscall.fail(error.Unexpected),
+ error.PipeBusy => return syscall.fail(error.Unexpected),
+ error.NoDevice => return syscall.fail(error.Unexpected),
+ error.AntivirusInterference => return syscall.fail(error.Unexpected),
+ error.OperationCanceled => {
+ try syscall.checkCancel();
+ continue;
+ },
+ else => |e| return e,
+ }
+ }
};
defer w.CloseHandle(symlink_handle);
@@ -5121,18 +5674,18 @@ fn dirSymLinkWasi(
if (builtin.link_libc) return dirSymLinkPosix(userdata, dir, target_path, sym_link_path, flags);
const t: *Threaded = @ptrCast(@alignCast(userdata));
- const current_thread = Thread.getCurrent(t);
+ _ = t;
- try current_thread.beginSyscall();
+ const syscall: Syscall = try .start();
while (true) {
switch (std.os.wasi.path_symlink(target_path.ptr, target_path.len, dir.handle, sym_link_path.ptr, sym_link_path.len)) {
- .SUCCESS => return current_thread.endSyscall(),
+ .SUCCESS => return syscall.finish(),
.INTR => {
- try current_thread.checkCancel();
+ try syscall.checkCancel();
continue;
},
else => |e| {
- current_thread.endSyscall();
+ syscall.finish();
switch (e) {
.FAULT => |err| return errnoBug(err),
.INVAL => |err| return errnoBug(err),
@@ -5167,7 +5720,7 @@ fn dirSymLinkPosix(
) Dir.SymLinkError!void {
_ = flags;
const t: *Threaded = @ptrCast(@alignCast(userdata));
- const current_thread = Thread.getCurrent(t);
+ _ = t;
var target_path_buffer: [posix.PATH_MAX]u8 = undefined;
var sym_link_path_buffer: [posix.PATH_MAX]u8 = undefined;
@@ -5175,16 +5728,16 @@ fn dirSymLinkPosix(
const target_path_posix = try pathToPosix(target_path, &target_path_buffer);
const sym_link_path_posix = try pathToPosix(sym_link_path, &sym_link_path_buffer);
- try current_thread.beginSyscall();
+ const syscall: Syscall = try .start();
while (true) {
switch (posix.errno(posix.system.symlinkat(target_path_posix, dir.handle, sym_link_path_posix))) {
- .SUCCESS => return current_thread.endSyscall(),
+ .SUCCESS => return syscall.finish(),
.INTR => {
- try current_thread.checkCancel();
+ try syscall.checkCancel();
continue;
},
else => |e| {
- current_thread.endSyscall();
+ syscall.finish();
switch (e) {
.FAULT => |err| return errnoBug(err),
.INVAL => |err| return errnoBug(err),
@@ -5216,14 +5769,24 @@ const dirReadLink = switch (native_os) {
fn dirReadLinkWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, buffer: []u8) Dir.ReadLinkError!usize {
const t: *Threaded = @ptrCast(@alignCast(userdata));
- const current_thread = Thread.getCurrent(t);
+ _ = t;
const w = windows;
- try current_thread.checkCancel();
-
var sub_path_w_buf = try windows.sliceToPrefixedFileW(dir.handle, sub_path);
- const result_w = try w.ReadLink(dir.handle, sub_path_w_buf.span(), &sub_path_w_buf.data);
+ const syscall: Syscall = try .start();
+ const result_w = while (true) {
+ if (w.ReadLink(dir.handle, sub_path_w_buf.span(), &sub_path_w_buf.data)) |res| {
+ syscall.finish();
+ break res;
+ } else |err| switch (err) {
+ error.OperationCanceled => {
+ try syscall.checkCancel();
+ continue;
+ },
+ else => |e| return syscall.fail(e),
+ }
+ };
const len = std.unicode.calcWtf8Len(result_w);
if (len > buffer.len) return error.NameTooLong;
@@ -5235,22 +5798,22 @@ fn dirReadLinkWasi(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, buffer
if (builtin.link_libc) return dirReadLinkPosix(userdata, dir, sub_path, buffer);
const t: *Threaded = @ptrCast(@alignCast(userdata));
- const current_thread = Thread.getCurrent(t);
+ _ = t;
var n: usize = undefined;
- try current_thread.beginSyscall();
+ const syscall: Syscall = try .start();
while (true) {
switch (std.os.wasi.path_readlink(dir.handle, sub_path.ptr, sub_path.len, buffer.ptr, buffer.len, &n)) {
.SUCCESS => {
- current_thread.endSyscall();
+ syscall.finish();
return n;
},
.INTR => {
- try current_thread.checkCancel();
+ try syscall.checkCancel();
continue;
},
else => |e| {
- current_thread.endSyscall();
+ syscall.finish();
switch (e) {
.ACCES => return error.AccessDenied,
.FAULT => |err| return errnoBug(err),
@@ -5272,26 +5835,26 @@ fn dirReadLinkWasi(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, buffer
fn dirReadLinkPosix(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, buffer: []u8) Dir.ReadLinkError!usize {
const t: *Threaded = @ptrCast(@alignCast(userdata));
- const current_thread = Thread.getCurrent(t);
+ _ = t;
var sub_path_buffer: [posix.PATH_MAX]u8 = undefined;
const sub_path_posix = try pathToPosix(sub_path, &sub_path_buffer);
- try current_thread.beginSyscall();
+ const syscall: Syscall = try .start();
while (true) {
const rc = posix.system.readlinkat(dir.handle, sub_path_posix, buffer.ptr, buffer.len);
switch (posix.errno(rc)) {
.SUCCESS => {
- current_thread.endSyscall();
+ syscall.finish();
const len: usize = @bitCast(rc);
return len;
},
.INTR => {
- try current_thread.checkCancel();
+ try syscall.checkCancel();
continue;
},
else => |e| {
- current_thread.endSyscall();
+ syscall.finish();
switch (e) {
.ACCES => return error.AccessDenied,
.FAULT => |err| return errnoBug(err),
@@ -5326,8 +5889,8 @@ fn dirSetPermissionsWindows(userdata: ?*anyopaque, dir: Dir, permissions: Dir.Pe
fn dirSetPermissionsPosix(userdata: ?*anyopaque, dir: Dir, permissions: Dir.Permissions) Dir.SetPermissionsError!void {
if (@sizeOf(Dir.Permissions) == 0) return;
const t: *Threaded = @ptrCast(@alignCast(userdata));
- const current_thread = Thread.getCurrent(t);
- return setPermissionsPosix(current_thread, dir.handle, permissions.toMode());
+ _ = t;
+ return setPermissionsPosix(dir.handle, permissions.toMode());
}
fn dirSetFilePermissions(
@@ -5340,7 +5903,6 @@ fn dirSetFilePermissions(
if (@sizeOf(Dir.Permissions) == 0) return;
if (is_windows) @panic("TODO implement dirSetFilePermissions windows");
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);
@@ -5348,12 +5910,11 @@ fn dirSetFilePermissions(
const mode = permissions.toMode();
const flags: u32 = if (!options.follow_symlinks) posix.AT.SYMLINK_NOFOLLOW else 0;
- return posixFchmodat(t, current_thread, dir.handle, sub_path_posix, mode, flags);
+ return posixFchmodat(t, dir.handle, sub_path_posix, mode, flags);
}
fn posixFchmodat(
t: *Threaded,
- current_thread: *Thread,
dir_fd: posix.fd_t,
path: [*:0]const u8,
mode: posix.mode_t,
@@ -5362,20 +5923,20 @@ fn posixFchmodat(
// No special handling for linux is needed if we can use the libc fallback
// or `flags` is empty. Glibc only added the fallback in 2.32.
if (have_fchmodat_flags or flags == 0) {
- try current_thread.beginSyscall();
+ const syscall: Syscall = try .start();
while (true) {
const rc = if (have_fchmodat_flags or builtin.link_libc)
posix.system.fchmodat(dir_fd, path, mode, flags)
else
posix.system.fchmodat(dir_fd, path, mode);
switch (posix.errno(rc)) {
- .SUCCESS => return current_thread.endSyscall(),
+ .SUCCESS => return syscall.finish(),
.INTR => {
- try current_thread.checkCancel();
+ try syscall.checkCancel();
continue;
},
else => |e| {
- current_thread.endSyscall();
+ syscall.finish();
switch (e) {
.BADF => |err| return errnoBug(err),
.FAULT => |err| return errnoBug(err),
@@ -5400,20 +5961,20 @@ fn posixFchmodat(
}
if (@atomicLoad(UseFchmodat2, &t.use_fchmodat2, .monotonic) == .disabled)
- return fchmodatFallback(current_thread, dir_fd, path, mode);
+ return fchmodatFallback(dir_fd, path, mode);
comptime assert(native_os == .linux);
- try current_thread.beginSyscall();
+ const syscall: Syscall = try .start();
while (true) {
switch (std.os.linux.errno(std.os.linux.fchmodat2(dir_fd, path, mode, flags))) {
- .SUCCESS => return current_thread.endSyscall(),
+ .SUCCESS => return syscall.finish(),
.INTR => {
- try current_thread.checkCancel();
+ try syscall.checkCancel();
continue;
},
else => |e| {
- current_thread.endSyscall();
+ syscall.finish();
switch (e) {
.BADF => |err| return errnoBug(err),
.FAULT => |err| return errnoBug(err),
@@ -5429,7 +5990,7 @@ fn posixFchmodat(
.ROFS => return error.ReadOnlyFileSystem,
.NOSYS => {
@atomicStore(UseFchmodat2, &t.use_fchmodat2, .disabled, .monotonic);
- return fchmodatFallback(current_thread, dir_fd, path, mode);
+ return fchmodatFallback(dir_fd, path, mode);
},
else => |err| return posix.unexpectedErrno(err),
}
@@ -5439,7 +6000,6 @@ fn posixFchmodat(
}
fn fchmodatFallback(
- current_thread: *Thread,
dir_fd: posix.fd_t,
path: [*:0]const u8,
mode: posix.mode_t,
@@ -5457,64 +6017,68 @@ fn fchmodatFallback(
// 2. Stat the fd and check if it isn't a symbolic link.
// 3. Generate the procfs reference to the fd via `/proc/self/fd/{fd}`.
// 4. Pass the procfs path to `chmod` with the `mode`.
- try current_thread.beginSyscall();
- const path_fd: posix.fd_t = while (true) {
- const rc = posix.system.openat(dir_fd, path, .{
- .PATH = true,
- .NOFOLLOW = true,
- .CLOEXEC = true,
- }, @as(posix.mode_t, 0));
- switch (posix.errno(rc)) {
- .SUCCESS => {
- current_thread.endSyscall();
- break @intCast(rc);
- },
- .INTR => {
- try current_thread.checkCancel();
- continue;
- },
- else => |e| {
- current_thread.endSyscall();
- switch (e) {
- .FAULT => |err| return errnoBug(err),
- .INVAL => |err| return errnoBug(err),
- .ACCES => return error.AccessDenied,
- .PERM => return error.PermissionDenied,
- .LOOP => return error.SymLinkLoop,
- .MFILE => return error.ProcessFdQuotaExceeded,
- .NAMETOOLONG => return error.NameTooLong,
- .NFILE => return error.SystemFdQuotaExceeded,
- .NOENT => return error.FileNotFound,
- .NOMEM => return error.SystemResources,
- else => |err| return posix.unexpectedErrno(err),
- }
- },
+ const path_fd: posix.fd_t = fd: {
+ const syscall: Syscall = try .start();
+ while (true) {
+ const rc = posix.system.openat(dir_fd, path, .{
+ .PATH = true,
+ .NOFOLLOW = true,
+ .CLOEXEC = true,
+ }, @as(posix.mode_t, 0));
+ switch (posix.errno(rc)) {
+ .SUCCESS => {
+ syscall.finish();
+ break :fd @intCast(rc);
+ },
+ .INTR => {
+ try syscall.checkCancel();
+ continue;
+ },
+ else => |e| {
+ syscall.finish();
+ switch (e) {
+ .FAULT => |err| return errnoBug(err),
+ .INVAL => |err| return errnoBug(err),
+ .ACCES => return error.AccessDenied,
+ .PERM => return error.PermissionDenied,
+ .LOOP => return error.SymLinkLoop,
+ .MFILE => return error.ProcessFdQuotaExceeded,
+ .NAMETOOLONG => return error.NameTooLong,
+ .NFILE => return error.SystemFdQuotaExceeded,
+ .NOENT => return error.FileNotFound,
+ .NOMEM => return error.SystemResources,
+ else => |err| return posix.unexpectedErrno(err),
+ }
+ },
+ }
}
};
defer posix.close(path_fd);
- try current_thread.beginSyscall();
- const path_mode = while (true) {
- var statx = std.mem.zeroes(std.os.linux.Statx);
- switch (sys.errno(sys.statx(path_fd, "", posix.AT.EMPTY_PATH, .{ .TYPE = true }, &statx))) {
- .SUCCESS => {
- current_thread.endSyscall();
- if (!statx.mask.TYPE) return error.Unexpected;
- break statx.mode;
- },
- .INTR => {
- try current_thread.checkCancel();
- continue;
- },
- else => |e| {
- current_thread.endSyscall();
- switch (e) {
- .ACCES => return error.AccessDenied,
- .LOOP => return error.SymLinkLoop,
- .NOMEM => return error.SystemResources,
- else => |err| return posix.unexpectedErrno(err),
- }
- },
+ const path_mode = mode: {
+ const syscall: Syscall = try .start();
+ while (true) {
+ var statx = std.mem.zeroes(std.os.linux.Statx);
+ switch (sys.errno(sys.statx(path_fd, "", posix.AT.EMPTY_PATH, .{ .TYPE = true }, &statx))) {
+ .SUCCESS => {
+ syscall.finish();
+ if (!statx.mask.TYPE) return error.Unexpected;
+ break :mode statx.mode;
+ },
+ .INTR => {
+ try syscall.checkCancel();
+ continue;
+ },
+ else => |e| {
+ syscall.finish();
+ switch (e) {
+ .ACCES => return error.AccessDenied,
+ .LOOP => return error.SymLinkLoop,
+ .NOMEM => return error.SystemResources,
+ else => |err| return posix.unexpectedErrno(err),
+ }
+ },
+ }
}
};
@@ -5524,16 +6088,16 @@ fn fchmodatFallback(
var procfs_buf: ["/proc/self/fd/-2147483648\x00".len]u8 = undefined;
const proc_path = std.fmt.bufPrintSentinel(&procfs_buf, "/proc/self/fd/{d}", .{path_fd}, 0) catch unreachable;
- try current_thread.beginSyscall();
+ const syscall: Syscall = try .start();
while (true) {
switch (posix.errno(posix.system.chmod(proc_path, mode))) {
- .SUCCESS => return current_thread.endSyscall(),
+ .SUCCESS => return syscall.finish(),
.INTR => {
- try current_thread.checkCancel();
+ try syscall.checkCancel();
continue;
},
else => |e| {
- current_thread.endSyscall();
+ syscall.finish();
switch (e) {
.NOENT => return error.OperationUnsupported, // procfs not mounted.
.BADF => |err| return errnoBug(err),
@@ -5569,24 +6133,24 @@ fn dirSetOwnerUnsupported(userdata: ?*anyopaque, dir: Dir, owner: ?File.Uid, gro
fn dirSetOwnerPosix(userdata: ?*anyopaque, dir: Dir, owner: ?File.Uid, group: ?File.Gid) Dir.SetOwnerError!void {
if (!have_fchown) return error.Unexpected; // Unsupported OS, don't call this function.
const t: *Threaded = @ptrCast(@alignCast(userdata));
- const current_thread = Thread.getCurrent(t);
+ _ = t;
const uid = owner orelse ~@as(posix.uid_t, 0);
const gid = group orelse ~@as(posix.gid_t, 0);
- return posixFchown(current_thread, dir.handle, uid, gid);
+ return posixFchown(dir.handle, uid, gid);
}
-fn posixFchown(current_thread: *Thread, fd: posix.fd_t, uid: posix.uid_t, gid: posix.gid_t) File.SetOwnerError!void {
+fn posixFchown(fd: posix.fd_t, uid: posix.uid_t, gid: posix.gid_t) File.SetOwnerError!void {
comptime assert(have_fchown);
- try current_thread.beginSyscall();
+ const syscall: Syscall = try .start();
while (true) {
switch (posix.errno(posix.system.fchown(fd, uid, gid))) {
- .SUCCESS => return current_thread.endSyscall(),
+ .SUCCESS => return syscall.finish(),
.INTR => {
- try current_thread.checkCancel();
+ try syscall.checkCancel();
continue;
},
else => |e| {
- current_thread.endSyscall();
+ syscall.finish();
switch (e) {
.BADF => |err| return errnoBug(err), // likely fd refers to directory opened without `Dir.OpenOptions.iterate`
.FAULT => |err| return errnoBug(err),
@@ -5616,12 +6180,11 @@ fn dirSetFileOwner(
) Dir.SetFileOwnerError!void {
if (!have_fchown) return error.Unexpected; // Unsupported OS, don't call this function.
const t: *Threaded = @ptrCast(@alignCast(userdata));
- const current_thread = Thread.getCurrent(t);
+ _ = t;
var path_buffer: [posix.PATH_MAX]u8 = undefined;
const sub_path_posix = try pathToPosix(sub_path, &path_buffer);
- _ = current_thread;
_ = dir;
_ = sub_path_posix;
_ = owner;
@@ -5638,35 +6201,43 @@ const fileSync = switch (native_os) {
fn fileSyncWindows(userdata: ?*anyopaque, file: File) File.SyncError!void {
const t: *Threaded = @ptrCast(@alignCast(userdata));
- const current_thread = Thread.getCurrent(t);
-
- try current_thread.checkCancel();
-
- if (windows.kernel32.FlushFileBuffers(file.handle) != 0)
- return;
+ _ = t;
- switch (windows.GetLastError()) {
- .SUCCESS => return,
- .INVALID_HANDLE => unreachable,
- .ACCESS_DENIED => return error.AccessDenied, // a sync was performed but the system couldn't update the access time
- .UNEXP_NET_ERR => return error.InputOutput,
- else => |err| return windows.unexpectedError(err),
+ const syscall: Syscall = try .start();
+ while (true) {
+ if (windows.kernel32.FlushFileBuffers(file.handle) != 0) {
+ return syscall.finish();
+ }
+ switch (windows.GetLastError()) {
+ .SUCCESS => unreachable, // `FlushFileBuffers` returned nonzero
+ .INVALID_HANDLE => unreachable,
+ .ACCESS_DENIED => return syscall.fail(error.AccessDenied), // a sync was performed but the system couldn't update the access time
+ .UNEXP_NET_ERR => return syscall.fail(error.InputOutput),
+ .OPERATION_ABORTED => {
+ try syscall.checkCancel();
+ continue;
+ },
+ else => |err| {
+ syscall.finish();
+ return windows.unexpectedError(err);
+ },
+ }
}
}
fn fileSyncPosix(userdata: ?*anyopaque, file: File) File.SyncError!void {
const t: *Threaded = @ptrCast(@alignCast(userdata));
- const current_thread = Thread.getCurrent(t);
- try current_thread.beginSyscall();
+ _ = t;
+ const syscall: Syscall = try .start();
while (true) {
switch (posix.errno(posix.system.fsync(file.handle))) {
- .SUCCESS => return current_thread.endSyscall(),
+ .SUCCESS => return syscall.finish(),
.INTR => {
- try current_thread.checkCancel();
+ try syscall.checkCancel();
continue;
},
else => |e| {
- current_thread.endSyscall();
+ syscall.finish();
switch (e) {
.BADF => |err| return errnoBug(err),
.INVAL => |err| return errnoBug(err),
@@ -5683,17 +6254,17 @@ fn fileSyncPosix(userdata: ?*anyopaque, file: File) File.SyncError!void {
fn fileSyncWasi(userdata: ?*anyopaque, file: File) File.SyncError!void {
const t: *Threaded = @ptrCast(@alignCast(userdata));
- const current_thread = Thread.getCurrent(t);
- try current_thread.beginSyscall();
+ _ = t;
+ const syscall: Syscall = try .start();
while (true) {
switch (std.os.wasi.fd_sync(file.handle)) {
- .SUCCESS => return current_thread.endSyscall(),
+ .SUCCESS => return syscall.finish(),
.INTR => {
- try current_thread.checkCancel();
+ try syscall.checkCancel();
continue;
},
else => |e| {
- current_thread.endSyscall();
+ syscall.finish();
switch (e) {
.BADF => |err| return errnoBug(err),
.INVAL => |err| return errnoBug(err),
@@ -5710,33 +6281,46 @@ fn fileSyncWasi(userdata: ?*anyopaque, file: File) File.SyncError!void {
fn fileIsTty(userdata: ?*anyopaque, file: File) Io.Cancelable!bool {
const t: *Threaded = @ptrCast(@alignCast(userdata));
- const current_thread = Thread.getCurrent(t);
- return isTty(current_thread, file);
+ _ = t;
+ return isTty(file);
}
-fn isTty(current_thread: *Thread, file: File) Io.Cancelable!bool {
+fn isTty(file: File) Io.Cancelable!bool {
if (is_windows) {
- if (try isCygwinPty(current_thread, file)) return true;
- try current_thread.checkCancel();
+ if (try isCygwinPty(file)) return true;
var out: windows.DWORD = undefined;
- return windows.kernel32.GetConsoleMode(file.handle, &out) != 0;
+ const syscall: Syscall = try .start();
+ while (windows.kernel32.GetConsoleMode(file.handle, &out) == 0) {
+ switch (windows.GetLastError()) {
+ .OPERATION_ABORTED => {
+ try syscall.checkCancel();
+ continue;
+ },
+ else => {
+ syscall.finish();
+ return false;
+ },
+ }
+ }
+ syscall.finish();
+ return true;
}
if (builtin.link_libc) {
- try current_thread.beginSyscall();
+ const syscall: Syscall = try .start();
while (true) {
const rc = posix.system.isatty(file.handle);
switch (posix.errno(rc - 1)) {
.SUCCESS => {
- current_thread.endSyscall();
+ syscall.finish();
return true;
},
.INTR => {
- try current_thread.checkCancel();
+ try syscall.checkCancel();
continue;
},
else => {
- current_thread.endSyscall();
+ syscall.finish();
return false;
},
}
@@ -5760,22 +6344,22 @@ fn isTty(current_thread: *Thread, file: File) Io.Cancelable!bool {
if (native_os == .linux) {
const linux = std.os.linux;
- try current_thread.beginSyscall();
+ const syscall: Syscall = try .start();
while (true) {
var wsz: posix.winsize = undefined;
const fd: usize = @bitCast(@as(isize, file.handle));
const rc = linux.syscall3(.ioctl, fd, linux.T.IOCGWINSZ, @intFromPtr(&wsz));
switch (linux.errno(rc)) {
.SUCCESS => {
- current_thread.endSyscall();
+ syscall.finish();
return true;
},
.INTR => {
- try current_thread.checkCancel();
+ try syscall.checkCancel();
continue;
},
else => {
- current_thread.endSyscall();
+ syscall.finish();
return false;
},
}
@@ -5787,53 +6371,99 @@ fn isTty(current_thread: *Thread, file: File) Io.Cancelable!bool {
fn fileEnableAnsiEscapeCodes(userdata: ?*anyopaque, file: File) File.EnableAnsiEscapeCodesError!void {
const t: *Threaded = @ptrCast(@alignCast(userdata));
- const current_thread = Thread.getCurrent(t);
+ _ = t;
- if (is_windows) {
- try current_thread.checkCancel();
+ if (!is_windows) {
+ if (try supportsAnsiEscapeCodes(file)) return;
+ return error.NotTerminalDevice;
+ }
- // For Windows Terminal, VT Sequences processing is enabled by default.
- var original_console_mode: windows.DWORD = 0;
- if (windows.kernel32.GetConsoleMode(file.handle, &original_console_mode) != 0) {
- if (original_console_mode & windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING != 0) return;
+ // For Windows Terminal, VT Sequences processing is enabled by default.
+ var original_console_mode: windows.DWORD = 0;
- // For Windows Console, VT Sequences processing support was added in Windows 10 build 14361, but disabled by default.
- // https://devblogs.microsoft.com/commandline/tmux-support-arrives-for-bash-on-ubuntu-on-windows/
- //
- // Note: In Microsoft's example for enabling virtual terminal processing, it
- // shows attempting to enable `DISABLE_NEWLINE_AUTO_RETURN` as well:
- // https://learn.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences#example-of-enabling-virtual-terminal-processing
- // This is avoided because in the old Windows Console, that flag causes \n (as opposed to \r\n)
- // to behave unexpectedly (the cursor moves down 1 row but remains on the same column).
- // Additionally, the default console mode in Windows Terminal does not have
- // `DISABLE_NEWLINE_AUTO_RETURN` set, so by only enabling `ENABLE_VIRTUAL_TERMINAL_PROCESSING`
- // we end up matching the mode of Windows Terminal.
- const requested_console_modes = windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING;
- const console_mode = original_console_mode | requested_console_modes;
- try current_thread.checkCancel();
- if (windows.kernel32.SetConsoleMode(file.handle, console_mode) != 0) return;
- }
- if (try isCygwinPty(current_thread, file)) return;
- } else {
- if (try supportsAnsiEscapeCodes(current_thread, file)) return;
+ {
+ const syscall: Syscall = try .start();
+ while (windows.kernel32.GetConsoleMode(file.handle, &original_console_mode) == 0) {
+ switch (windows.GetLastError()) {
+ .OPERATION_ABORTED => {
+ try syscall.checkCancel();
+ continue;
+ },
+ else => {
+ syscall.finish();
+ if (try isCygwinPty(file)) return;
+ return error.NotTerminalDevice;
+ },
+ }
+ }
+ syscall.finish();
+ }
+
+ if (original_console_mode & windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING != 0) return;
+
+ // For Windows Console, VT Sequences processing support was added in Windows 10 build 14361, but disabled by default.
+ // https://devblogs.microsoft.com/commandline/tmux-support-arrives-for-bash-on-ubuntu-on-windows/
+ //
+ // Note: In Microsoft's example for enabling virtual terminal processing, it
+ // shows attempting to enable `DISABLE_NEWLINE_AUTO_RETURN` as well:
+ // https://learn.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences#example-of-enabling-virtual-terminal-processing
+ // This is avoided because in the old Windows Console, that flag causes \n (as opposed to \r\n)
+ // to behave unexpectedly (the cursor moves down 1 row but remains on the same column).
+ // Additionally, the default console mode in Windows Terminal does not have
+ // `DISABLE_NEWLINE_AUTO_RETURN` set, so by only enabling `ENABLE_VIRTUAL_TERMINAL_PROCESSING`
+ // we end up matching the mode of Windows Terminal.
+ const requested_console_modes = windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING;
+ const console_mode = original_console_mode | requested_console_modes;
+
+ {
+ const syscall: Syscall = try .start();
+ while (windows.kernel32.SetConsoleMode(file.handle, console_mode) == 0) {
+ switch (windows.GetLastError()) {
+ .OPERATION_ABORTED => {
+ try syscall.checkCancel();
+ continue;
+ },
+ else => {
+ syscall.finish();
+ if (try isCygwinPty(file)) return;
+ return error.NotTerminalDevice;
+ },
+ }
+ }
+ syscall.finish();
}
- return error.NotTerminalDevice;
}
fn fileSupportsAnsiEscapeCodes(userdata: ?*anyopaque, file: File) Io.Cancelable!bool {
const t: *Threaded = @ptrCast(@alignCast(userdata));
- const current_thread = Thread.getCurrent(t);
- return supportsAnsiEscapeCodes(current_thread, file);
+ _ = t;
+ return supportsAnsiEscapeCodes(file);
}
-fn supportsAnsiEscapeCodes(current_thread: *Thread, file: File) Io.Cancelable!bool {
+fn supportsAnsiEscapeCodes(file: File) Io.Cancelable!bool {
if (is_windows) {
- try current_thread.checkCancel();
var console_mode: windows.DWORD = 0;
- if (windows.kernel32.GetConsoleMode(file.handle, &console_mode) != 0) {
- if (console_mode & windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING != 0) return true;
+
+ const syscall: Syscall = try .start();
+ while (windows.kernel32.GetConsoleMode(file.handle, &console_mode) == 0) {
+ switch (windows.GetLastError()) {
+ .OPERATION_ABORTED => {
+ try syscall.checkCancel();
+ continue;
+ },
+ else => {
+ syscall.finish();
+ break;
+ },
+ }
+ } else {
+ syscall.finish();
+ if (console_mode & windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING != 0) {
+ return true;
+ }
}
- return isCygwinPty(current_thread, file);
+
+ return isCygwinPty(file);
}
if (native_os == .wasi) {
@@ -5843,12 +6473,12 @@ fn supportsAnsiEscapeCodes(current_thread: *Thread, file: File) Io.Cancelable!bo
return false;
}
- if (try isTty(current_thread, file)) return true;
+ if (try isTty(file)) return true;
return false;
}
-fn isCygwinPty(current_thread: *Thread, file: File) Io.Cancelable!bool {
+fn isCygwinPty(file: File) Io.Cancelable!bool {
if (!is_windows) return false;
const handle = file.handle;
@@ -5863,20 +6493,26 @@ fn isCygwinPty(current_thread: *Thread, file: File) Io.Cancelable!bool {
// This allows us to avoid the more costly NtQueryInformationFile call
// for handles that aren't named pipes.
{
- try current_thread.checkCancel();
var io_status: windows.IO_STATUS_BLOCK = undefined;
var device_info: windows.FILE.FS_DEVICE_INFORMATION = undefined;
- const rc = windows.ntdll.NtQueryVolumeInformationFile(
+ const syscall: Syscall = try .start();
+ while (true) switch (windows.ntdll.NtQueryVolumeInformationFile(
handle,
- &io_status,
- &device_info,
- @sizeOf(windows.FILE.FS_DEVICE_INFORMATION),
- .Device,
- );
- switch (rc) {
- .SUCCESS => {},
- else => return false,
- }
+ &io_status,
+ &device_info,
+ @sizeOf(windows.FILE.FS_DEVICE_INFORMATION),
+ .Device,
+ )) {
+ .SUCCESS => break syscall.finish(),
+ .CANCELLED => {
+ try syscall.checkCancel();
+ continue;
+ },
+ else => {
+ syscall.finish();
+ return false;
+ },
+ };
if (device_info.DeviceType.FileDevice != .NAMED_PIPE) return false;
}
@@ -5891,19 +6527,25 @@ fn isCygwinPty(current_thread: *Thread, file: File) Io.Cancelable!bool {
var name_info_bytes align(@alignOf(windows.FILE.NAME_INFORMATION)) = [_]u8{0} ** (name_bytes_offset + num_name_bytes);
var io_status_block: windows.IO_STATUS_BLOCK = undefined;
- try current_thread.checkCancel();
- const rc = windows.ntdll.NtQueryInformationFile(
+ const syscall: Syscall = try .start();
+ while (true) switch (windows.ntdll.NtQueryInformationFile(
handle,
&io_status_block,
&name_info_bytes,
@intCast(name_info_bytes.len),
.Name,
- );
- switch (rc) {
- .SUCCESS => {},
+ )) {
+ .SUCCESS => break syscall.finish(),
+ .CANCELLED => {
+ try syscall.checkCancel();
+ continue;
+ },
.INVALID_PARAMETER => unreachable,
- else => return false,
- }
+ else => {
+ syscall.finish();
+ return false;
+ },
+ };
const name_info: *const windows.FILE_NAME_INFO = @ptrCast(&name_info_bytes);
const name_bytes = name_info_bytes[name_bytes_offset .. name_bytes_offset + name_info.FileNameLength];
@@ -5916,47 +6558,49 @@ fn isCygwinPty(current_thread: *Thread, file: File) Io.Cancelable!bool {
fn fileSetLength(userdata: ?*anyopaque, file: File, length: u64) File.SetLengthError!void {
const t: *Threaded = @ptrCast(@alignCast(userdata));
- const current_thread = Thread.getCurrent(t);
+ _ = t;
const signed_len: i64 = @bitCast(length);
if (signed_len < 0) return error.FileTooBig; // Avoid ambiguous EINVAL errors.
if (is_windows) {
- try current_thread.checkCancel();
-
var io_status_block: windows.IO_STATUS_BLOCK = undefined;
const eof_info: windows.FILE.END_OF_FILE_INFORMATION = .{
.EndOfFile = signed_len,
};
- const status = windows.ntdll.NtSetInformationFile(
+ const syscall: Syscall = try .start();
+ while (true) switch (windows.ntdll.NtSetInformationFile(
file.handle,
&io_status_block,
&eof_info,
@sizeOf(windows.FILE.END_OF_FILE_INFORMATION),
.EndOfFile,
- );
- switch (status) {
- .SUCCESS => return,
- .INVALID_HANDLE => |err| return windows.statusBug(err), // Handle not open for writing.
- .ACCESS_DENIED => return error.AccessDenied,
- .USER_MAPPED_FILE => return error.AccessDenied,
- .INVALID_PARAMETER => return error.FileTooBig,
- else => return windows.unexpectedStatus(status),
- }
+ )) {
+ .SUCCESS => return syscall.finish(),
+ .CANCELLED => {
+ try syscall.checkCancel();
+ continue;
+ },
+ .INVALID_HANDLE => |err| return syscall.ntstatusBug(err), // Handle not open for writing.
+ .ACCESS_DENIED => return syscall.fail(error.AccessDenied),
+ .USER_MAPPED_FILE => return syscall.fail(error.AccessDenied),
+ .INVALID_PARAMETER => return syscall.fail(error.FileTooBig),
+ else => |status| return syscall.unexpectedNtstatus(status),
+ };
}
if (native_os == .wasi and !builtin.link_libc) {
- try current_thread.beginSyscall();
+ const syscall: Syscall = try .start();
while (true) {
switch (std.os.wasi.fd_filestat_set_size(file.handle, length)) {
- .SUCCESS => return current_thread.endSyscall(),
+ .SUCCESS => return syscall.finish(),
.INTR => {
- try current_thread.checkCancel();
+ try syscall.checkCancel();
continue;
},
else => |e| {
- current_thread.endSyscall();
+ syscall.finish();
switch (e) {
.FBIG => return error.FileTooBig,
.IO => return error.InputOutput,
@@ -5972,16 +6616,16 @@ fn fileSetLength(userdata: ?*anyopaque, file: File, length: u64) File.SetLengthE
}
}
- try current_thread.beginSyscall();
+ const syscall: Syscall = try .start();
while (true) {
switch (posix.errno(ftruncate_sym(file.handle, signed_len))) {
- .SUCCESS => return current_thread.endSyscall(),
+ .SUCCESS => return syscall.finish(),
.INTR => {
- try current_thread.checkCancel();
+ try syscall.checkCancel();
continue;
},
else => |e| {
- current_thread.endSyscall();
+ syscall.finish();
switch (e) {
.FBIG => return error.FileTooBig,
.IO => return error.InputOutput,
@@ -5999,19 +6643,18 @@ fn fileSetLength(userdata: ?*anyopaque, file: File, length: u64) File.SetLengthE
fn fileSetOwner(userdata: ?*anyopaque, file: File, owner: ?File.Uid, group: ?File.Gid) File.SetOwnerError!void {
if (!have_fchown) return error.Unexpected; // Unsupported OS, don't call this function.
const t: *Threaded = @ptrCast(@alignCast(userdata));
- const current_thread = Thread.getCurrent(t);
+ _ = t;
const uid = owner orelse ~@as(posix.uid_t, 0);
const gid = group orelse ~@as(posix.gid_t, 0);
- return posixFchown(current_thread, file.handle, uid, gid);
+ return posixFchown(file.handle, uid, gid);
}
fn fileSetPermissions(userdata: ?*anyopaque, file: File, permissions: File.Permissions) File.SetPermissionsError!void {
if (@sizeOf(File.Permissions) == 0) return;
const t: *Threaded = @ptrCast(@alignCast(userdata));
- const current_thread = Thread.getCurrent(t);
+ _ = t;
switch (native_os) {
.windows => {
- try current_thread.checkCancel();
var io_status_block: windows.IO_STATUS_BLOCK = undefined;
const info: windows.FILE.BASIC_INFORMATION = .{
.CreationTime = 0,
@@ -6020,37 +6663,41 @@ fn fileSetPermissions(userdata: ?*anyopaque, file: File, permissions: File.Permi
.ChangeTime = 0,
.FileAttributes = permissions.toAttributes(),
};
- const status = windows.ntdll.NtSetInformationFile(
+ const syscall: Syscall = try .start();
+ while (true) switch (windows.ntdll.NtSetInformationFile(
file.handle,
&io_status_block,
&info,
@sizeOf(windows.FILE.BASIC_INFORMATION),
.Basic,
- );
- switch (status) {
- .SUCCESS => return,
- .INVALID_HANDLE => |err| return windows.statusBug(err),
- .ACCESS_DENIED => return error.AccessDenied,
- else => return windows.unexpectedStatus(status),
- }
+ )) {
+ .SUCCESS => return syscall.finish(),
+ .CANCELLED => {
+ try syscall.checkCancel();
+ continue;
+ },
+ .INVALID_HANDLE => |err| return syscall.ntstatusBug(err),
+ .ACCESS_DENIED => return syscall.fail(error.AccessDenied),
+ else => |status| return syscall.unexpectedNtstatus(status),
+ };
},
.wasi => return error.Unexpected, // Unsupported OS.
- else => return setPermissionsPosix(current_thread, file.handle, permissions.toMode()),
+ else => return setPermissionsPosix(file.handle, permissions.toMode()),
}
}
-fn setPermissionsPosix(current_thread: *Thread, fd: posix.fd_t, mode: posix.mode_t) File.SetPermissionsError!void {
+fn setPermissionsPosix(fd: posix.fd_t, mode: posix.mode_t) File.SetPermissionsError!void {
comptime assert(have_fchmod);
- try current_thread.beginSyscall();
+ const syscall: Syscall = try .start();
while (true) {
switch (posix.errno(posix.system.fchmod(fd, mode))) {
- .SUCCESS => return current_thread.endSyscall(),
+ .SUCCESS => return syscall.finish(),
.INTR => {
- try current_thread.checkCancel();
+ try syscall.checkCancel();
continue;
},
else => |e| {
- current_thread.endSyscall();
+ syscall.finish();
switch (e) {
.BADF => |err| return errnoBug(err),
.FAULT => |err| return errnoBug(err),
@@ -6077,7 +6724,7 @@ fn dirSetTimestamps(
options: Dir.SetTimestampsOptions,
) Dir.SetTimestampsError!void {
const t: *Threaded = @ptrCast(@alignCast(userdata));
- const current_thread = Thread.getCurrent(t);
+ _ = t;
if (is_windows) {
@panic("TODO implement dirSetTimestamps windows");
@@ -6101,20 +6748,20 @@ fn dirSetTimestamps(
var path_buffer: [posix.PATH_MAX]u8 = undefined;
const sub_path_posix = try pathToPosix(sub_path, &path_buffer);
- try current_thread.beginSyscall();
+ const syscall: Syscall = try .start();
while (true) switch (posix.errno(posix.system.utimensat(dir.handle, sub_path_posix, times, flags))) {
- .SUCCESS => return current_thread.endSyscall(),
+ .SUCCESS => return syscall.finish(),
.INTR => {
- try current_thread.checkCancel();
+ try syscall.checkCancel();
continue;
},
- .BADF => |err| return current_thread.endSyscallErrnoBug(err), // always a race condition
- .FAULT => |err| return current_thread.endSyscallErrnoBug(err),
- .INVAL => |err| return current_thread.endSyscallErrnoBug(err),
- .ACCES => return current_thread.endSyscallError(error.AccessDenied),
- .PERM => return current_thread.endSyscallError(error.PermissionDenied),
- .ROFS => return current_thread.endSyscallError(error.ReadOnlyFileSystem),
- else => |err| return current_thread.endSyscallUnexpectedErrno(err),
+ .BADF => |err| return syscall.errnoBug(err), // always a race condition
+ .FAULT => |err| return syscall.errnoBug(err),
+ .INVAL => |err| return syscall.errnoBug(err),
+ .ACCES => return syscall.fail(error.AccessDenied),
+ .PERM => return syscall.fail(error.PermissionDenied),
+ .ROFS => return syscall.fail(error.ReadOnlyFileSystem),
+ else => |err| return syscall.unexpectedErrno(err),
};
}
@@ -6124,11 +6771,9 @@ fn fileSetTimestamps(
options: File.SetTimestampsOptions,
) File.SetTimestampsError!void {
const t: *Threaded = @ptrCast(@alignCast(userdata));
- const current_thread = Thread.getCurrent(t);
+ _ = t;
if (is_windows) {
- try current_thread.checkCancel();
-
var access_time_buffer: windows.FILETIME = undefined;
var modify_time_buffer: windows.FILETIME = undefined;
var system_time_buffer: windows.LARGE_INTEGER = undefined;
@@ -6156,13 +6801,22 @@ fn fileSetTimestamps(
};
// https://github.com/ziglang/zig/issues/1840
- const rc = windows.kernel32.SetFileTime(file.handle, null, access_ptr, modify_ptr);
- if (rc == 0) {
- switch (windows.GetLastError()) {
- else => |err| return windows.unexpectedError(err),
+ const syscall: Syscall = try .start();
+ while (true) {
+ switch (windows.kernel32.SetFileTime(file.handle, null, access_ptr, modify_ptr)) {
+ 0 => switch (windows.GetLastError()) {
+ .OPERATION_ABORTED => {
+ try syscall.checkCancel();
+ continue;
+ },
+ else => |err| {
+ syscall.finish();
+ return windows.unexpectedError(err);
+ },
+ },
+ else => return syscall.finish(),
}
}
- return;
}
if (native_os == .wasi and !builtin.link_libc) {
@@ -6188,20 +6842,20 @@ fn fileSetTimestamps(
},
}
- try current_thread.beginSyscall();
+ const syscall: Syscall = try .start();
while (true) switch (std.os.wasi.fd_filestat_set_times(file.handle, atime, mtime, flags)) {
- .SUCCESS => return current_thread.endSyscall(),
+ .SUCCESS => return syscall.finish(),
.INTR => {
- try current_thread.checkCancel();
+ try syscall.checkCancel();
continue;
},
- .BADF => |err| return current_thread.endSyscallErrnoBug(err), // File descriptor use-after-free.
- .FAULT => |err| return current_thread.endSyscallErrnoBug(err),
- .INVAL => |err| return current_thread.endSyscallErrnoBug(err),
- .ACCES => return current_thread.endSyscallError(error.AccessDenied),
- .PERM => return current_thread.endSyscallError(error.PermissionDenied),
- .ROFS => return current_thread.endSyscallError(error.ReadOnlyFileSystem),
- else => |err| return current_thread.endSyscallUnexpectedErrno(err),
+ .BADF => |err| return syscall.errnoBug(err), // File descriptor use-after-free.
+ .FAULT => |err| return syscall.errnoBug(err),
+ .INVAL => |err| return syscall.errnoBug(err),
+ .ACCES => return syscall.fail(error.AccessDenied),
+ .PERM => return syscall.fail(error.PermissionDenied),
+ .ROFS => return syscall.fail(error.ReadOnlyFileSystem),
+ else => |err| return syscall.unexpectedErrno(err),
};
}
@@ -6214,20 +6868,20 @@ fn fileSetTimestamps(
break :p ×_buffer;
};
- try current_thread.beginSyscall();
+ const syscall: Syscall = try .start();
while (true) switch (posix.errno(posix.system.futimens(file.handle, times))) {
- .SUCCESS => return current_thread.endSyscall(),
+ .SUCCESS => return syscall.finish(),
.INTR => {
- try current_thread.checkCancel();
+ try syscall.checkCancel();
continue;
},
- .BADF => |err| return current_thread.endSyscallErrnoBug(err), // always a race condition
- .FAULT => |err| return current_thread.endSyscallErrnoBug(err),
- .INVAL => |err| return current_thread.endSyscallErrnoBug(err),
- .ACCES => return current_thread.endSyscallError(error.AccessDenied),
- .PERM => return current_thread.endSyscallError(error.PermissionDenied),
- .ROFS => return current_thread.endSyscallError(error.ReadOnlyFileSystem),
- else => |err| return current_thread.endSyscallUnexpectedErrno(err),
+ .BADF => |err| return syscall.errnoBug(err), // always a race condition
+ .FAULT => |err| return syscall.errnoBug(err),
+ .INVAL => |err| return syscall.errnoBug(err),
+ .ACCES => return syscall.fail(error.AccessDenied),
+ .PERM => return syscall.fail(error.PermissionDenied),
+ .ROFS => return syscall.fail(error.ReadOnlyFileSystem),
+ else => |err| return syscall.unexpectedErrno(err),
};
}
@@ -6237,34 +6891,33 @@ const windows_lock_range_len: windows.LARGE_INTEGER = 1;
fn fileLock(userdata: ?*anyopaque, file: File, lock: File.Lock) File.LockError!void {
if (native_os == .wasi) return error.FileLocksUnsupported;
const t: *Threaded = @ptrCast(@alignCast(userdata));
- const current_thread = Thread.getCurrent(t);
+ _ = t;
if (is_windows) {
const exclusive = switch (lock) {
.none => {
// To match the non-Windows behavior, unlock
var io_status_block: windows.IO_STATUS_BLOCK = undefined;
- const status = windows.ntdll.NtUnlockFile(
+ while (true) switch (windows.ntdll.NtUnlockFile(
file.handle,
&io_status_block,
&windows_lock_range_off,
&windows_lock_range_len,
0,
- );
- switch (status) {
- .SUCCESS => {},
- .RANGE_NOT_LOCKED => {},
+ )) {
+ .SUCCESS => return,
+ .CANCELLED => continue,
+ .RANGE_NOT_LOCKED => return,
.ACCESS_VIOLATION => |err| return windows.statusBug(err), // bad io_status_block pointer
- else => return windows.unexpectedStatus(status),
- }
- return;
+ else => |status| return windows.unexpectedStatus(status),
+ };
},
.shared => false,
.exclusive => true,
};
- try current_thread.checkCancel();
var io_status_block: windows.IO_STATUS_BLOCK = undefined;
- const status = windows.ntdll.NtLockFile(
+ const syscall: Syscall = try .start();
+ while (true) switch (windows.ntdll.NtLockFile(
file.handle,
null,
null,
@@ -6275,14 +6928,17 @@ fn fileLock(userdata: ?*anyopaque, file: File, lock: File.Lock) File.LockError!v
null,
windows.FALSE,
@intFromBool(exclusive),
- );
- switch (status) {
- .SUCCESS => return,
- .INSUFFICIENT_RESOURCES => return error.SystemResources,
- .LOCK_NOT_GRANTED => |err| return windows.statusBug(err), // passed FailImmediately=false
- .ACCESS_VIOLATION => |err| return windows.statusBug(err), // bad io_status_block pointer
- else => return windows.unexpectedStatus(status),
- }
+ )) {
+ .SUCCESS => return syscall.finish(),
+ .CANCELLED => {
+ try syscall.checkCancel();
+ continue;
+ },
+ .INSUFFICIENT_RESOURCES => return syscall.fail(error.SystemResources),
+ .LOCK_NOT_GRANTED => |err| return syscall.ntstatusBug(err), // passed FailImmediately=false
+ .ACCESS_VIOLATION => |err| return syscall.ntstatusBug(err), // bad io_status_block pointer
+ else => |status| return syscall.unexpectedNtstatus(status),
+ };
}
const operation: i32 = switch (lock) {
@@ -6290,16 +6946,16 @@ fn fileLock(userdata: ?*anyopaque, file: File, lock: File.Lock) File.LockError!v
.shared => posix.LOCK.SH,
.exclusive => posix.LOCK.EX,
};
- try current_thread.beginSyscall();
+ const syscall: Syscall = try .start();
while (true) {
switch (posix.errno(posix.system.flock(file.handle, operation))) {
- .SUCCESS => return current_thread.endSyscall(),
+ .SUCCESS => return syscall.finish(),
.INTR => {
- try current_thread.checkCancel();
+ try syscall.checkCancel();
continue;
},
else => |e| {
- current_thread.endSyscall();
+ syscall.finish();
switch (e) {
.BADF => |err| return errnoBug(err),
.INVAL => |err| return errnoBug(err), // invalid parameters
@@ -6316,33 +6972,33 @@ fn fileLock(userdata: ?*anyopaque, file: File, lock: File.Lock) File.LockError!v
fn fileTryLock(userdata: ?*anyopaque, file: File, lock: File.Lock) File.LockError!bool {
if (native_os == .wasi) return error.FileLocksUnsupported;
const t: *Threaded = @ptrCast(@alignCast(userdata));
- const current_thread = Thread.getCurrent(t);
+ _ = t;
if (is_windows) {
const exclusive = switch (lock) {
.none => {
// To match the non-Windows behavior, unlock
var io_status_block: windows.IO_STATUS_BLOCK = undefined;
- const status = windows.ntdll.NtUnlockFile(
+ while (true) switch (windows.ntdll.NtUnlockFile(
file.handle,
&io_status_block,
&windows_lock_range_off,
&windows_lock_range_len,
0,
- );
- switch (status) {
+ )) {
.SUCCESS => return true,
+ .CANCELLED => continue,
.RANGE_NOT_LOCKED => return false,
.ACCESS_VIOLATION => |err| return windows.statusBug(err), // bad io_status_block pointer
- else => return windows.unexpectedStatus(status),
- }
+ else => |status| return windows.unexpectedStatus(status),
+ };
},
.shared => false,
.exclusive => true,
};
- try current_thread.checkCancel();
var io_status_block: windows.IO_STATUS_BLOCK = undefined;
- const status = windows.ntdll.NtLockFile(
+ const syscall: Syscall = try .start();
+ while (true) switch (windows.ntdll.NtLockFile(
file.handle,
null,
null,
@@ -6353,14 +7009,23 @@ fn fileTryLock(userdata: ?*anyopaque, file: File, lock: File.Lock) File.LockErro
null,
windows.TRUE,
@intFromBool(exclusive),
- );
- switch (status) {
- .SUCCESS => return true,
- .INSUFFICIENT_RESOURCES => return error.SystemResources,
- .LOCK_NOT_GRANTED => return false,
- .ACCESS_VIOLATION => |err| return windows.statusBug(err), // bad io_status_block pointer
- else => return windows.unexpectedStatus(status),
- }
+ )) {
+ .SUCCESS => {
+ syscall.finish();
+ return true;
+ },
+ .LOCK_NOT_GRANTED => {
+ syscall.finish();
+ return false;
+ },
+ .CANCELLED => {
+ try syscall.checkCancel();
+ continue;
+ },
+ .INSUFFICIENT_RESOURCES => return syscall.fail(error.SystemResources),
+ .ACCESS_VIOLATION => |err| return syscall.ntstatusBug(err), // bad io_status_block pointer
+ else => |status| return syscall.unexpectedNtstatus(status),
+ };
}
const operation: i32 = switch (lock) {
@@ -6368,23 +7033,23 @@ fn fileTryLock(userdata: ?*anyopaque, file: File, lock: File.Lock) File.LockErro
.shared => posix.LOCK.SH | posix.LOCK.NB,
.exclusive => posix.LOCK.EX | posix.LOCK.NB,
};
- try current_thread.beginSyscall();
+ const syscall: Syscall = try .start();
while (true) {
switch (posix.errno(posix.system.flock(file.handle, operation))) {
.SUCCESS => {
- current_thread.endSyscall();
+ syscall.finish();
return true;
},
.INTR => {
- try current_thread.checkCancel();
+ try syscall.checkCancel();
continue;
},
.AGAIN => {
- current_thread.endSyscall();
+ syscall.finish();
return false;
},
else => |e| {
- current_thread.endSyscall();
+ syscall.finish();
switch (e) {
.BADF => |err| return errnoBug(err),
.INVAL => |err| return errnoBug(err), // invalid parameters
@@ -6404,20 +7069,19 @@ fn fileUnlock(userdata: ?*anyopaque, file: File) void {
if (is_windows) {
var io_status_block: windows.IO_STATUS_BLOCK = undefined;
- const status = windows.ntdll.NtUnlockFile(
+ while (true) switch (windows.ntdll.NtUnlockFile(
file.handle,
&io_status_block,
&windows_lock_range_off,
&windows_lock_range_len,
0,
- );
- if (is_debug) switch (status) {
- .SUCCESS => {},
- .RANGE_NOT_LOCKED => unreachable, // Function asserts unlocked.
- .ACCESS_VIOLATION => unreachable, // bad io_status_block pointer
- else => unreachable, // Resource deallocation must succeed.
+ )) {
+ .SUCCESS => return,
+ .CANCELLED => continue,
+ .RANGE_NOT_LOCKED => if (is_debug) unreachable else return, // Function asserts unlocked.
+ .ACCESS_VIOLATION => if (is_debug) unreachable else return, // bad io_status_block pointer
+ else => if (is_debug) unreachable else return, // Resource deallocation must succeed.
};
- return;
}
while (true) {
@@ -6437,17 +7101,17 @@ fn fileUnlock(userdata: ?*anyopaque, file: File) void {
fn fileDowngradeLock(userdata: ?*anyopaque, file: File) File.DowngradeLockError!void {
if (native_os == .wasi) return;
const t: *Threaded = @ptrCast(@alignCast(userdata));
- const current_thread = Thread.getCurrent(t);
+ _ = t;
if (is_windows) {
- try current_thread.checkCancel();
// On Windows it works like a semaphore + exclusivity flag. To
// implement this function, we first obtain another lock in shared
// mode. This changes the exclusivity flag, but increments the
// semaphore to 2. So we follow up with an NtUnlockFile which
// decrements the semaphore but does not modify the exclusivity flag.
var io_status_block: windows.IO_STATUS_BLOCK = undefined;
- switch (windows.ntdll.NtLockFile(
+ const syscall: Syscall = try .start();
+ while (true) switch (windows.ntdll.NtLockFile(
file.handle,
null,
null,
@@ -6459,43 +7123,46 @@ fn fileDowngradeLock(userdata: ?*anyopaque, file: File) File.DowngradeLockError!
windows.TRUE,
windows.FALSE,
)) {
- .SUCCESS => {},
- .INSUFFICIENT_RESOURCES => |err| return windows.statusBug(err),
- .LOCK_NOT_GRANTED => |err| return windows.statusBug(err), // File was not locked in exclusive mode.
- .ACCESS_VIOLATION => |err| return windows.statusBug(err), // bad io_status_block pointer
- else => |status| return windows.unexpectedStatus(status),
- }
- const status = windows.ntdll.NtUnlockFile(
+ .SUCCESS => break syscall.finish(),
+ .CANCELLED => {
+ try syscall.checkCancel();
+ continue;
+ },
+ .INSUFFICIENT_RESOURCES => |err| return syscall.ntstatusBug(err),
+ .LOCK_NOT_GRANTED => |err| return syscall.ntstatusBug(err), // File was not locked in exclusive mode.
+ .ACCESS_VIOLATION => |err| return syscall.ntstatusBug(err), // bad io_status_block pointer
+ else => |status| return syscall.unexpectedNtstatus(status),
+ };
+ while (true) switch (windows.ntdll.NtUnlockFile(
file.handle,
&io_status_block,
&windows_lock_range_off,
&windows_lock_range_len,
0,
- );
- if (is_debug) switch (status) {
- .SUCCESS => {},
- .RANGE_NOT_LOCKED => unreachable, // File was not locked.
- .ACCESS_VIOLATION => unreachable, // bad io_status_block pointer
- else => unreachable, // Resource deallocation must succeed.
+ )) {
+ .SUCCESS => return,
+ .CANCELLED => continue,
+ .RANGE_NOT_LOCKED => if (is_debug) unreachable else return, // File was not locked.
+ .ACCESS_VIOLATION => if (is_debug) unreachable else return, // bad io_status_block pointer
+ else => if (is_debug) unreachable else return, // Resource deallocation must succeed.
};
- return;
}
const operation = posix.LOCK.SH | posix.LOCK.NB;
- try current_thread.beginSyscall();
+ const syscall: Syscall = try .start();
while (true) {
switch (posix.errno(posix.system.flock(file.handle, operation))) {
.SUCCESS => {
- current_thread.endSyscall();
+ syscall.finish();
return;
},
.INTR => {
- try current_thread.checkCancel();
+ try syscall.checkCancel();
continue;
},
else => |e| {
- current_thread.endSyscall();
+ syscall.finish();
switch (e) {
.AGAIN => |err| return errnoBug(err), // File was not locked in exclusive mode.
.BADF => |err| return errnoBug(err),
@@ -6517,7 +7184,7 @@ fn dirOpenDirWasi(
) Dir.OpenError!Dir {
if (builtin.link_libc) return dirOpenDirPosix(userdata, dir, sub_path, options);
const t: *Threaded = @ptrCast(@alignCast(userdata));
- const current_thread = Thread.getCurrent(t);
+ _ = t;
const wasi = std.os.wasi;
var base: std.os.wasi.rights_t = .{
@@ -6547,19 +7214,19 @@ fn dirOpenDirWasi(
const oflags: wasi.oflags_t = .{ .DIRECTORY = true };
const fdflags: wasi.fdflags_t = .{};
var fd: posix.fd_t = undefined;
- try current_thread.beginSyscall();
+ const syscall: Syscall = try .start();
while (true) {
switch (wasi.path_open(dir.handle, lookup_flags, sub_path.ptr, sub_path.len, oflags, base, base, fdflags, &fd)) {
.SUCCESS => {
- current_thread.endSyscall();
+ syscall.finish();
return .{ .handle = fd };
},
.INTR => {
- try current_thread.checkCancel();
+ try syscall.checkCancel();
continue;
},
else => |e| {
- current_thread.endSyscall();
+ syscall.finish();
switch (e) {
.FAULT => |err| return errnoBug(err),
.INVAL => return error.BadPathName,
@@ -6594,13 +7261,13 @@ fn dirHardLink(
) Dir.HardLinkError!void {
if (is_windows) return error.OperationUnsupported;
const t: *Threaded = @ptrCast(@alignCast(userdata));
- const current_thread = Thread.getCurrent(t);
+ _ = t;
if (native_os == .wasi and !builtin.link_libc) {
const flags: std.os.wasi.lookupflags_t = .{
.SYMLINK_FOLLOW = options.follow_symlinks,
};
- try current_thread.beginSyscall();
+ const syscall: Syscall = try .start();
while (true) {
switch (std.os.wasi.path_link(
old_dir.handle,
@@ -6611,13 +7278,13 @@ fn dirHardLink(
new_sub_path.ptr,
new_sub_path.len,
)) {
- .SUCCESS => return current_thread.endSyscall(),
+ .SUCCESS => return syscall.finish(),
.INTR => {
- try current_thread.checkCancel();
+ try syscall.checkCancel();
continue;
},
else => |e| {
- current_thread.endSyscall();
+ syscall.finish();
switch (e) {
.ACCES => return error.AccessDenied,
.DQUOT => return error.DiskQuota,
@@ -6651,7 +7318,7 @@ fn dirHardLink(
const flags: u32 = if (!options.follow_symlinks) posix.AT.SYMLINK_NOFOLLOW else 0;
- try current_thread.beginSyscall();
+ const syscall: Syscall = try .start();
while (true) {
switch (posix.errno(posix.system.linkat(
old_dir.handle,
@@ -6660,13 +7327,13 @@ fn dirHardLink(
new_sub_path_posix,
flags,
))) {
- .SUCCESS => return current_thread.endSyscall(),
+ .SUCCESS => return syscall.finish(),
.INTR => {
- try current_thread.checkCancel();
+ try syscall.checkCancel();
continue;
},
else => |e| {
- current_thread.endSyscall();
+ syscall.finish();
switch (e) {
.ACCES => return error.AccessDenied,
.DQUOT => return error.DiskQuota,
@@ -6705,7 +7372,7 @@ const fileReadStreaming = switch (native_os) {
fn fileReadStreamingPosix(userdata: ?*anyopaque, file: File, data: []const []u8) File.Reader.Error!usize {
const t: *Threaded = @ptrCast(@alignCast(userdata));
- const current_thread = Thread.getCurrent(t);
+ _ = t;
var iovecs_buffer: [max_iovecs_len]posix.iovec = undefined;
var i: usize = 0;
@@ -6721,20 +7388,20 @@ fn fileReadStreamingPosix(userdata: ?*anyopaque, file: File, data: []const []u8)
assert(dest[0].len > 0);
if (native_os == .wasi and !builtin.link_libc) {
- try current_thread.beginSyscall();
+ const syscall: Syscall = try .start();
while (true) {
var nread: usize = undefined;
switch (std.os.wasi.fd_read(file.handle, dest.ptr, dest.len, &nread)) {
.SUCCESS => {
- current_thread.endSyscall();
+ syscall.finish();
return nread;
},
.INTR => {
- try current_thread.checkCancel();
+ try syscall.checkCancel();
continue;
},
else => |e| {
- current_thread.endSyscall();
+ syscall.finish();
switch (e) {
.INVAL => |err| return errnoBug(err),
.FAULT => |err| return errnoBug(err),
@@ -6754,20 +7421,20 @@ fn fileReadStreamingPosix(userdata: ?*anyopaque, file: File, data: []const []u8)
}
}
- try current_thread.beginSyscall();
+ const syscall: Syscall = try .start();
while (true) {
const rc = posix.system.readv(file.handle, dest.ptr, @intCast(dest.len));
switch (posix.errno(rc)) {
.SUCCESS => {
- current_thread.endSyscall();
+ syscall.finish();
return @intCast(rc);
},
.INTR => {
- try current_thread.checkCancel();
+ try syscall.checkCancel();
continue;
},
else => |e| {
- current_thread.endSyscall();
+ syscall.finish();
switch (e) {
.INVAL => |err| return errnoBug(err),
.FAULT => |err| return errnoBug(err),
@@ -6792,7 +7459,7 @@ fn fileReadStreamingPosix(userdata: ?*anyopaque, file: File, data: []const []u8)
fn fileReadStreamingWindows(userdata: ?*anyopaque, file: File, data: []const []u8) File.Reader.Error!usize {
const t: *Threaded = @ptrCast(@alignCast(userdata));
- const current_thread = Thread.getCurrent(t);
+ _ = t;
const DWORD = windows.DWORD;
var index: usize = 0;
@@ -6801,28 +7468,41 @@ fn fileReadStreamingWindows(userdata: ?*anyopaque, file: File, data: []const []u
const buffer = data[index];
const want_read_count: DWORD = @min(std.math.maxInt(DWORD), buffer.len);
+ const syscall: Syscall = try .start();
while (true) {
- try current_thread.checkCancel();
var n: DWORD = undefined;
- if (windows.kernel32.ReadFile(file.handle, buffer.ptr, want_read_count, &n, null) != 0)
+ if (windows.kernel32.ReadFile(file.handle, buffer.ptr, want_read_count, &n, null) != 0) {
+ syscall.finish();
return n;
+ }
switch (windows.GetLastError()) {
- .IO_PENDING => |err| return windows.errorBug(err),
- .OPERATION_ABORTED => continue,
- .BROKEN_PIPE => return 0,
- .HANDLE_EOF => return 0,
- .NETNAME_DELETED => return error.ConnectionResetByPeer,
- .LOCK_VIOLATION => return error.LockViolation,
- .ACCESS_DENIED => return error.AccessDenied,
- .INVALID_HANDLE => return error.NotOpenForReading,
- else => |err| return windows.unexpectedError(err),
+ .IO_PENDING => |err| {
+ syscall.finish();
+ return windows.errorBug(err);
+ },
+ .OPERATION_ABORTED => {
+ try syscall.checkCancel();
+ continue;
+ },
+ .BROKEN_PIPE, .HANDLE_EOF => {
+ syscall.finish();
+ return 0;
+ },
+ .NETNAME_DELETED => return syscall.fail(error.ConnectionResetByPeer),
+ .LOCK_VIOLATION => return syscall.fail(error.LockViolation),
+ .ACCESS_DENIED => return syscall.fail(error.AccessDenied),
+ .INVALID_HANDLE => return syscall.fail(error.NotOpenForReading),
+ else => |err| {
+ syscall.finish();
+ return windows.unexpectedError(err);
+ },
}
}
}
fn fileReadPositionalPosix(userdata: ?*anyopaque, file: File, data: []const []u8, offset: u64) File.ReadPositionalError!usize {
const t: *Threaded = @ptrCast(@alignCast(userdata));
- const current_thread = Thread.getCurrent(t);
+ _ = t;
if (!have_preadv) @compileError("TODO implement fileReadPositionalPosix for cursed operating systems that don't support preadv (it's only Haiku)");
@@ -6840,20 +7520,20 @@ fn fileReadPositionalPosix(userdata: ?*anyopaque, file: File, data: []const []u8
assert(dest[0].len > 0);
if (native_os == .wasi and !builtin.link_libc) {
- try current_thread.beginSyscall();
+ const syscall: Syscall = try .start();
while (true) {
var nread: usize = undefined;
switch (std.os.wasi.fd_pread(file.handle, dest.ptr, dest.len, offset, &nread)) {
.SUCCESS => {
- current_thread.endSyscall();
+ syscall.finish();
return nread;
},
.INTR => {
- try current_thread.checkCancel();
+ try syscall.checkCancel();
continue;
},
else => |e| {
- current_thread.endSyscall();
+ syscall.finish();
switch (e) {
.INVAL => |err| return errnoBug(err),
.FAULT => |err| return errnoBug(err),
@@ -6877,20 +7557,20 @@ fn fileReadPositionalPosix(userdata: ?*anyopaque, file: File, data: []const []u8
}
}
- try current_thread.beginSyscall();
+ const syscall: Syscall = try .start();
while (true) {
const rc = preadv_sym(file.handle, dest.ptr, @intCast(dest.len), @bitCast(offset));
switch (posix.errno(rc)) {
.SUCCESS => {
- current_thread.endSyscall();
+ syscall.finish();
return @bitCast(rc);
},
.INTR => {
- try current_thread.checkCancel();
+ try syscall.checkCancel();
continue;
},
else => |e| {
- current_thread.endSyscall();
+ syscall.finish();
switch (e) {
.INVAL => |err| return errnoBug(err),
.FAULT => |err| return errnoBug(err),
@@ -6923,7 +7603,7 @@ const fileReadPositional = switch (native_os) {
fn fileReadPositionalWindows(userdata: ?*anyopaque, file: File, data: []const []u8, offset: u64) File.ReadPositionalError!usize {
const t: *Threaded = @ptrCast(@alignCast(userdata));
- const current_thread = Thread.getCurrent(t);
+ _ = t;
const DWORD = windows.DWORD;
@@ -6945,45 +7625,58 @@ fn fileReadPositionalWindows(userdata: ?*anyopaque, file: File, data: []const []
.hEvent = null,
};
+ const syscall: Syscall = try .start();
while (true) {
- try current_thread.checkCancel();
var n: DWORD = undefined;
- if (windows.kernel32.ReadFile(file.handle, buffer.ptr, want_read_count, &n, &overlapped) != 0)
+ if (windows.kernel32.ReadFile(file.handle, buffer.ptr, want_read_count, &n, &overlapped) != 0) {
+ syscall.finish();
return n;
+ }
switch (windows.GetLastError()) {
- .IO_PENDING => |err| return windows.errorBug(err),
- .OPERATION_ABORTED => continue,
- .BROKEN_PIPE => return 0,
- .HANDLE_EOF => return 0,
- .NETNAME_DELETED => return error.ConnectionResetByPeer,
- .LOCK_VIOLATION => return error.LockViolation,
- .ACCESS_DENIED => return error.AccessDenied,
- .INVALID_HANDLE => return error.NotOpenForReading,
- else => |err| return windows.unexpectedError(err),
+ .IO_PENDING => |err| {
+ syscall.finish();
+ return windows.errorBug(err);
+ },
+ .OPERATION_ABORTED => {
+ try syscall.checkCancel();
+ continue;
+ },
+ .BROKEN_PIPE, .HANDLE_EOF => {
+ syscall.finish();
+ return 0;
+ },
+ .NETNAME_DELETED => return syscall.fail(error.ConnectionResetByPeer),
+ .LOCK_VIOLATION => return syscall.fail(error.LockViolation),
+ .ACCESS_DENIED => return syscall.fail(error.AccessDenied),
+ .INVALID_HANDLE => return syscall.fail(error.NotOpenForReading),
+ else => |err| {
+ syscall.finish();
+ return windows.unexpectedError(err);
+ },
}
}
}
fn fileSeekBy(userdata: ?*anyopaque, file: File, offset: i64) File.SeekError!void {
const t: *Threaded = @ptrCast(@alignCast(userdata));
- const current_thread = Thread.getCurrent(t);
+ _ = t;
const fd = file.handle;
if (native_os == .linux and !builtin.link_libc and @sizeOf(usize) == 4) {
var result: u64 = undefined;
- try current_thread.beginSyscall();
+ const syscall: Syscall = try .start();
while (true) {
switch (posix.errno(posix.system.llseek(fd, @bitCast(offset), &result, posix.SEEK.CUR))) {
.SUCCESS => {
- current_thread.endSyscall();
+ syscall.finish();
return;
},
.INTR => {
- try current_thread.checkCancel();
+ try syscall.checkCancel();
continue;
},
else => |e| {
- current_thread.endSyscall();
+ syscall.finish();
switch (e) {
.BADF => |err| return errnoBug(err), // File descriptor used after closed.
.INVAL => return error.Unseekable,
@@ -6998,25 +7691,43 @@ fn fileSeekBy(userdata: ?*anyopaque, file: File, offset: i64) File.SeekError!voi
}
if (native_os == .windows) {
- try current_thread.checkCancel();
- return windows.SetFilePointerEx_CURRENT(fd, offset);
+ const syscall: Syscall = try .start();
+ while (true) {
+ if (windows.kernel32.SetFilePointerEx(fd, offset, null, windows.FILE_CURRENT) != 0) {
+ return syscall.finish();
+ }
+ switch (windows.GetLastError()) {
+ .OPERATION_ABORTED => {
+ try syscall.checkCancel();
+ continue;
+ },
+ .INVALID_FUNCTION => return syscall.fail(error.Unseekable),
+ .NEGATIVE_SEEK => return syscall.fail(error.Unseekable),
+ .INVALID_PARAMETER => unreachable,
+ .INVALID_HANDLE => unreachable,
+ else => |err| {
+ syscall.finish();
+ return windows.unexpectedError(err);
+ },
+ }
+ }
}
if (native_os == .wasi and !builtin.link_libc) {
var new_offset: std.os.wasi.filesize_t = undefined;
- try current_thread.beginSyscall();
+ const syscall: Syscall = try .start();
while (true) {
switch (std.os.wasi.fd_seek(fd, offset, .CUR, &new_offset)) {
.SUCCESS => {
- current_thread.endSyscall();
+ syscall.finish();
return;
},
.INTR => {
- try current_thread.checkCancel();
+ try syscall.checkCancel();
continue;
},
else => |e| {
- current_thread.endSyscall();
+ syscall.finish();
switch (e) {
.BADF => |err| return errnoBug(err), // File descriptor used after closed.
.INVAL => return error.Unseekable,
@@ -7033,19 +7744,19 @@ fn fileSeekBy(userdata: ?*anyopaque, file: File, offset: i64) File.SeekError!voi
if (posix.SEEK == void) return error.Unseekable;
- try current_thread.beginSyscall();
+ const syscall: Syscall = try .start();
while (true) {
switch (posix.errno(lseek_sym(fd, offset, posix.SEEK.CUR))) {
.SUCCESS => {
- current_thread.endSyscall();
+ syscall.finish();
return;
},
.INTR => {
- try current_thread.checkCancel();
+ try syscall.checkCancel();
continue;
},
else => |e| {
- current_thread.endSyscall();
+ syscall.finish();
switch (e) {
.BADF => |err| return errnoBug(err), // File descriptor used after closed.
.INVAL => return error.Unseekable,
@@ -7061,29 +7772,52 @@ fn fileSeekBy(userdata: ?*anyopaque, file: File, offset: i64) File.SeekError!voi
fn fileSeekTo(userdata: ?*anyopaque, file: File, offset: u64) File.SeekError!void {
const t: *Threaded = @ptrCast(@alignCast(userdata));
- const current_thread = Thread.getCurrent(t);
+ _ = t;
const fd = file.handle;
if (native_os == .windows) {
- try current_thread.checkCancel();
- return windows.SetFilePointerEx_BEGIN(fd, offset);
+ // "The starting point is zero or the beginning of the file. If [FILE_BEGIN]
+ // is specified, then the liDistanceToMove parameter is interpreted as an unsigned value."
+ // https://docs.microsoft.com/en-us/windows/desktop/api/fileapi/nf-fileapi-setfilepointerex
+ const ipos: windows.LARGE_INTEGER = @bitCast(offset);
+
+ const syscall: Syscall = try .start();
+ while (true) {
+ if (windows.kernel32.SetFilePointerEx(fd, ipos, null, windows.FILE_BEGIN) != 0) {
+ return syscall.finish();
+ }
+ switch (windows.GetLastError()) {
+ .OPERATION_ABORTED => {
+ try syscall.checkCancel();
+ continue;
+ },
+ .INVALID_FUNCTION => return syscall.fail(error.Unseekable),
+ .NEGATIVE_SEEK => return syscall.fail(error.Unseekable),
+ .INVALID_PARAMETER => unreachable,
+ .INVALID_HANDLE => unreachable,
+ else => |err| {
+ syscall.finish();
+ return windows.unexpectedError(err);
+ },
+ }
+ }
}
if (native_os == .wasi and !builtin.link_libc) {
- try current_thread.beginSyscall();
+ const syscall: Syscall = try .start();
while (true) {
var new_offset: std.os.wasi.filesize_t = undefined;
switch (std.os.wasi.fd_seek(fd, @bitCast(offset), .SET, &new_offset)) {
.SUCCESS => {
- current_thread.endSyscall();
+ syscall.finish();
return;
},
.INTR => {
- try current_thread.checkCancel();
+ try syscall.checkCancel();
continue;
},
else => |e| {
- current_thread.endSyscall();
+ syscall.finish();
switch (e) {
.BADF => |err| return errnoBug(err), // File descriptor used after closed.
.INVAL => return error.Unseekable,
@@ -7100,25 +7834,25 @@ fn fileSeekTo(userdata: ?*anyopaque, file: File, offset: u64) File.SeekError!voi
if (posix.SEEK == void) return error.Unseekable;
- return posixSeekTo(current_thread, fd, offset);
+ return posixSeekTo(fd, offset);
}
-fn posixSeekTo(current_thread: *Thread, fd: posix.fd_t, offset: u64) File.SeekError!void {
+fn posixSeekTo(fd: posix.fd_t, offset: u64) File.SeekError!void {
if (native_os == .linux and !builtin.link_libc and @sizeOf(usize) == 4) {
- try current_thread.beginSyscall();
+ const syscall: Syscall = try .start();
while (true) {
var result: u64 = undefined;
switch (posix.errno(posix.system.llseek(fd, offset, &result, posix.SEEK.SET))) {
.SUCCESS => {
- current_thread.endSyscall();
+ syscall.finish();
return;
},
.INTR => {
- try current_thread.checkCancel();
+ try syscall.checkCancel();
continue;
},
else => |e| {
- current_thread.endSyscall();
+ syscall.finish();
switch (e) {
.BADF => |err| return errnoBug(err), // File descriptor used after closed.
.INVAL => return error.Unseekable,
@@ -7132,19 +7866,19 @@ fn posixSeekTo(current_thread: *Thread, fd: posix.fd_t, offset: u64) File.SeekEr
}
}
- try current_thread.beginSyscall();
+ const syscall: Syscall = try .start();
while (true) {
switch (posix.errno(lseek_sym(fd, @bitCast(offset), posix.SEEK.SET))) {
.SUCCESS => {
- current_thread.endSyscall();
+ syscall.finish();
return;
},
.INTR => {
- try current_thread.checkCancel();
+ try syscall.checkCancel();
continue;
},
else => |e| {
- current_thread.endSyscall();
+ syscall.finish();
switch (e) {
.BADF => |err| return errnoBug(err), // File descriptor used after closed.
.INVAL => return error.Unseekable,
@@ -7170,7 +7904,7 @@ fn processExecutableOpen(userdata: ?*anyopaque, flags: File.OpenFlags) std.proce
const image_path_unicode_string = &windows.peb().ProcessParameters.ImagePathName;
const image_path_name = image_path_unicode_string.Buffer.?[0 .. image_path_unicode_string.Length / 2 :0];
const prefixed_path_w = try windows.wToPrefixedFileW(null, image_path_name);
- return dirOpenFileWtf16(t, null, prefixed_path_w.span(), flags);
+ return dirOpenFileWtf16(null, prefixed_path_w.span(), flags);
},
.driverkit,
.ios,
@@ -7234,22 +7968,21 @@ fn processExecutablePath(userdata: ?*anyopaque, out_buffer: []u8) std.process.Ex
else => |e| return e,
},
.freebsd, .dragonfly => {
- const current_thread = Thread.getCurrent(t);
var mib: [4]c_int = .{ posix.CTL.KERN, posix.KERN.PROC, posix.KERN.PROC_PATHNAME, -1 };
var out_len: usize = out_buffer.len;
- try current_thread.beginSyscall();
+ const syscall: Syscall = try .start();
while (true) {
switch (posix.errno(posix.system.sysctl(&mib, mib.len, out_buffer.ptr, &out_len, null, 0))) {
.SUCCESS => {
- current_thread.endSyscall();
+ syscall.finish();
return out_len - 1; // discard terminating NUL
},
.INTR => {
- try current_thread.checkCancel();
+ try syscall.checkCancel();
continue;
},
else => |e| {
- current_thread.endSyscall();
+ syscall.finish();
switch (e) {
.FAULT => |err| return errnoBug(err),
.PERM => return error.PermissionDenied,
@@ -7262,22 +7995,21 @@ fn processExecutablePath(userdata: ?*anyopaque, out_buffer: []u8) std.process.Ex
}
},
.netbsd => {
- const current_thread = Thread.getCurrent(t);
var mib = [4]c_int{ posix.CTL.KERN, posix.KERN.PROC_ARGS, -1, posix.KERN.PROC_PATHNAME };
var out_len: usize = out_buffer.len;
- try current_thread.beginSyscall();
+ const syscall: Syscall = try .start();
while (true) {
switch (posix.errno(posix.system.sysctl(&mib, mib.len, out_buffer.ptr, &out_len, null, 0))) {
.SUCCESS => {
- current_thread.endSyscall();
+ syscall.finish();
return out_len - 1; // discard terminating NUL
},
.INTR => {
- try current_thread.checkCancel();
+ try syscall.checkCancel();
continue;
},
else => |e| {
- current_thread.endSyscall();
+ syscall.finish();
switch (e) {
.FAULT => |err| return errnoBug(err),
.PERM => return error.PermissionDenied,
@@ -7295,20 +8027,19 @@ fn processExecutablePath(userdata: ?*anyopaque, out_buffer: []u8) std.process.Ex
const argv0 = std.mem.span(t.argv0.value orelse return error.OperationUnsupported);
if (std.mem.findScalar(u8, argv0, '/') != null) {
// argv[0] is a path (relative or absolute): use realpath(3) directly
- const current_thread = Thread.getCurrent(t);
var resolved_buf: [std.c.PATH_MAX]u8 = undefined;
- try current_thread.beginSyscall();
+ const syscall: Syscall = try .start();
while (true) {
if (std.c.realpath(argv0, &resolved_buf)) |p| {
assert(p == &resolved_buf);
- break current_thread.endSyscall();
+ break syscall.finish();
} else switch (@as(std.c.E, @enumFromInt(std.c._errno().*))) {
.INTR => {
- try current_thread.checkCancel();
+ try syscall.checkCancel();
continue;
},
else => |e| {
- current_thread.endSyscall();
+ syscall.finish();
switch (e) {
.ACCES => return error.AccessDenied,
.INVAL => |err| return errnoBug(err), // the pathname argument is a null pointer
@@ -7332,7 +8063,6 @@ fn processExecutablePath(userdata: ?*anyopaque, out_buffer: []u8) std.process.Ex
// argv[0] is not empty (and not a path): search PATH
t.scanEnviron();
const PATH = t.environ.string.PATH orelse return error.FileNotFound;
- const current_thread = Thread.getCurrent(t);
var it = std.mem.tokenizeScalar(u8, PATH, ':');
it: while (it.next()) |dir| {
var resolved_path_buf: [std.c.PATH_MAX]u8 = undefined;
@@ -7341,34 +8071,34 @@ fn processExecutablePath(userdata: ?*anyopaque, out_buffer: []u8) std.process.Ex
}, 0) catch continue;
var resolved_buf: [std.c.PATH_MAX]u8 = undefined;
- try current_thread.beginSyscall();
+ const syscall: Syscall = try .start();
while (true) {
if (std.c.realpath(resolved_path, &resolved_buf)) |p| {
assert(p == &resolved_buf);
- break current_thread.endSyscall();
+ break syscall.finish();
} else switch (@as(std.c.E, @enumFromInt(std.c._errno().*))) {
.INTR => {
- try current_thread.checkCancel();
+ try syscall.checkCancel();
continue;
},
.NAMETOOLONG => {
- current_thread.endSyscall();
+ syscall.finish();
return error.NameTooLong;
},
.NOMEM => {
- current_thread.endSyscall();
+ syscall.finish();
return error.SystemResources;
},
.IO => {
- current_thread.endSyscall();
+ syscall.finish();
return error.InputOutput;
},
.ACCES, .LOOP, .NOENT, .NOTDIR => {
- current_thread.endSyscall();
+ syscall.finish();
continue :it;
},
else => |err| {
- current_thread.endSyscall();
+ syscall.finish();
return posix.unexpectedErrno(err);
},
}
@@ -7383,8 +8113,6 @@ fn processExecutablePath(userdata: ?*anyopaque, out_buffer: []u8) std.process.Ex
return error.FileNotFound;
},
.windows => {
- const current_thread = Thread.getCurrent(t);
- try current_thread.checkCancel();
const w = windows;
const image_path_unicode_string = &w.peb().ProcessParameters.ImagePathName;
const image_path_name = image_path_unicode_string.Buffer.?[0 .. image_path_unicode_string.Length / 2 :0];
@@ -7394,24 +8122,34 @@ fn processExecutablePath(userdata: ?*anyopaque, out_buffer: []u8) std.process.Ex
// that the symlink points to, though, so we need to get the realpath.
var path_name_w_buf = try w.wToPrefixedFileW(null, image_path_name);
- const h_file = blk: {
- const res = w.OpenFile(path_name_w_buf.span(), .{
- .dir = null,
- .access_mask = .{
- .GENERIC = .{ .READ = true },
- .STANDARD = .{ .SYNCHRONIZE = true },
- },
- .creation = .OPEN,
- .filter = .any,
- }) catch |err| switch (err) {
- error.WouldBlock => unreachable,
- else => |e| return e,
- };
- break :blk res;
+ const h_file = handle: {
+ const syscall: Syscall = try .start();
+ while (true) {
+ if (w.OpenFile(path_name_w_buf.span(), .{
+ .dir = null,
+ .access_mask = .{
+ .GENERIC = .{ .READ = true },
+ .STANDARD = .{ .SYNCHRONIZE = true },
+ },
+ .creation = .OPEN,
+ .filter = .any,
+ })) |handle| {
+ syscall.finish();
+ break :handle handle;
+ } else |err| switch (err) {
+ error.WouldBlock => unreachable,
+ error.OperationCanceled => {
+ try syscall.checkCancel();
+ continue;
+ },
+ else => |e| return e,
+ }
+ }
};
defer w.CloseHandle(h_file);
// TODO move GetFinalPathNameByHandle logic into std.Io.Threaded and add cancel checks
+ try Thread.checkCancel();
const wide_slice = try w.GetFinalPathNameByHandle(h_file, .{}, &path_name_w_buf.data);
const len = std.unicode.calcWtf8Len(wide_slice);
@@ -7434,19 +8172,19 @@ fn fileWritePositional(
offset: u64,
) File.WritePositionalError!usize {
const t: *Threaded = @ptrCast(@alignCast(userdata));
- const current_thread = Thread.getCurrent(t);
+ _ = t;
if (is_windows) {
if (header.len != 0) {
- return writeFilePositionalWindows(current_thread, file.handle, header, offset);
+ return writeFilePositionalWindows(file.handle, header, offset);
}
for (data[0 .. data.len - 1]) |buf| {
if (buf.len == 0) continue;
- return writeFilePositionalWindows(current_thread, file.handle, buf, offset);
+ return writeFilePositionalWindows(file.handle, buf, offset);
}
const pattern = data[data.len - 1];
if (pattern.len == 0 or splat == 0) return 0;
- return writeFilePositionalWindows(current_thread, file.handle, pattern, offset);
+ return writeFilePositionalWindows(file.handle, pattern, offset);
}
var iovecs: [max_iovecs_len]posix.iovec_const = undefined;
@@ -7484,19 +8222,19 @@ fn fileWritePositional(
if (native_os == .wasi and !builtin.link_libc) {
var n_written: usize = undefined;
- try current_thread.beginSyscall();
+ const syscall: Syscall = try .start();
while (true) {
switch (std.os.wasi.fd_pwrite(file.handle, &iovecs, iovlen, offset, &n_written)) {
.SUCCESS => {
- current_thread.endSyscall();
+ syscall.finish();
return n_written;
},
.INTR => {
- try current_thread.checkCancel();
+ try syscall.checkCancel();
continue;
},
else => |e| {
- current_thread.endSyscall();
+ syscall.finish();
switch (e) {
.INVAL => |err| return errnoBug(err),
.FAULT => |err| return errnoBug(err),
@@ -7520,20 +8258,20 @@ fn fileWritePositional(
}
}
- try current_thread.beginSyscall();
+ const syscall: Syscall = try .start();
while (true) {
const rc = pwritev_sym(file.handle, &iovecs, @intCast(iovlen), @bitCast(offset));
switch (posix.errno(rc)) {
.SUCCESS => {
- current_thread.endSyscall();
+ syscall.finish();
return @intCast(rc);
},
.INTR => {
- try current_thread.checkCancel();
+ try syscall.checkCancel();
continue;
},
else => |e| {
- current_thread.endSyscall();
+ syscall.finish();
switch (e) {
.INVAL => |err| return errnoBug(err),
.FAULT => |err| return errnoBug(err),
@@ -7560,13 +8298,10 @@ fn fileWritePositional(
}
fn writeFilePositionalWindows(
- current_thread: *Thread,
handle: windows.HANDLE,
bytes: []const u8,
offset: u64,
) File.WritePositionalError!usize {
- try current_thread.checkCancel();
-
var bytes_written: windows.DWORD = undefined;
var overlapped: windows.OVERLAPPED = .{
.Internal = 0,
@@ -7580,21 +8315,31 @@ fn writeFilePositionalWindows(
.hEvent = null,
};
const adjusted_len = std.math.lossyCast(u32, bytes.len);
- if (windows.kernel32.WriteFile(handle, bytes.ptr, adjusted_len, &bytes_written, &overlapped) == 0) {
+ const syscall: Syscall = try .start();
+ while (true) {
+ if (windows.kernel32.WriteFile(handle, bytes.ptr, adjusted_len, &bytes_written, &overlapped) != 0) {
+ syscall.finish();
+ return bytes_written;
+ }
switch (windows.GetLastError()) {
- .INVALID_USER_BUFFER => return error.SystemResources,
- .NOT_ENOUGH_MEMORY => return error.SystemResources,
- .OPERATION_ABORTED => return error.Canceled,
- .NOT_ENOUGH_QUOTA => return error.SystemResources,
- .NO_DATA => return error.BrokenPipe,
- .INVALID_HANDLE => return error.NotOpenForWriting,
- .LOCK_VIOLATION => return error.LockViolation,
- .ACCESS_DENIED => return error.AccessDenied,
- .WORKING_SET_QUOTA => return error.SystemResources,
- else => |err| return windows.unexpectedError(err),
+ .OPERATION_ABORTED => {
+ try syscall.checkCancel();
+ continue;
+ },
+ .INVALID_USER_BUFFER => return syscall.fail(error.SystemResources),
+ .NOT_ENOUGH_MEMORY => return syscall.fail(error.SystemResources),
+ .NOT_ENOUGH_QUOTA => return syscall.fail(error.SystemResources),
+ .NO_DATA => return syscall.fail(error.BrokenPipe),
+ .INVALID_HANDLE => return syscall.fail(error.NotOpenForWriting),
+ .LOCK_VIOLATION => return syscall.fail(error.LockViolation),
+ .ACCESS_DENIED => return syscall.fail(error.AccessDenied),
+ .WORKING_SET_QUOTA => return syscall.fail(error.SystemResources),
+ else => |err| {
+ syscall.finish();
+ return windows.unexpectedError(err);
+ },
}
}
- return bytes_written;
}
fn fileWriteStreaming(
@@ -7605,19 +8350,19 @@ fn fileWriteStreaming(
splat: usize,
) File.Writer.Error!usize {
const t: *Threaded = @ptrCast(@alignCast(userdata));
- const current_thread = Thread.getCurrent(t);
+ _ = t;
if (is_windows) {
if (header.len != 0) {
- return writeFileStreamingWindows(current_thread, file.handle, header);
+ return writeFileStreamingWindows(file.handle, header);
}
for (data[0 .. data.len - 1]) |buf| {
if (buf.len == 0) continue;
- return writeFileStreamingWindows(current_thread, file.handle, buf);
+ return writeFileStreamingWindows(file.handle, buf);
}
const pattern = data[data.len - 1];
if (pattern.len == 0 or splat == 0) return 0;
- return writeFileStreamingWindows(current_thread, file.handle, pattern);
+ return writeFileStreamingWindows(file.handle, pattern);
}
var iovecs: [max_iovecs_len]posix.iovec_const = undefined;
@@ -7655,19 +8400,19 @@ fn fileWriteStreaming(
if (native_os == .wasi and !builtin.link_libc) {
var n_written: usize = undefined;
- try current_thread.beginSyscall();
+ const syscall: Syscall = try .start();
while (true) {
switch (std.os.wasi.fd_write(file.handle, &iovecs, iovlen, &n_written)) {
.SUCCESS => {
- current_thread.endSyscall();
+ syscall.finish();
return n_written;
},
.INTR => {
- try current_thread.checkCancel();
+ try syscall.checkCancel();
continue;
},
else => |e| {
- current_thread.endSyscall();
+ syscall.finish();
switch (e) {
.INVAL => |err| return errnoBug(err),
.FAULT => |err| return errnoBug(err),
@@ -7688,20 +8433,20 @@ fn fileWriteStreaming(
}
}
- try current_thread.beginSyscall();
+ const syscall: Syscall = try .start();
while (true) {
const rc = posix.system.writev(file.handle, &iovecs, @intCast(iovlen));
switch (posix.errno(rc)) {
.SUCCESS => {
- current_thread.endSyscall();
+ syscall.finish();
return @intCast(rc);
},
.INTR => {
- try current_thread.checkCancel();
+ try syscall.checkCancel();
continue;
},
else => |e| {
- current_thread.endSyscall();
+ syscall.finish();
switch (e) {
.INVAL => |err| return errnoBug(err),
.FAULT => |err| return errnoBug(err),
@@ -7724,29 +8469,36 @@ fn fileWriteStreaming(
}
fn writeFileStreamingWindows(
- current_thread: *Thread,
handle: windows.HANDLE,
bytes: []const u8,
) File.Writer.Error!usize {
- try current_thread.checkCancel();
-
var bytes_written: windows.DWORD = undefined;
const adjusted_len = std.math.lossyCast(u32, bytes.len);
- if (windows.kernel32.WriteFile(handle, bytes.ptr, adjusted_len, &bytes_written, null) == 0) {
+ const syscall: Syscall = try .start();
+ while (true) {
+ if (windows.kernel32.WriteFile(handle, bytes.ptr, adjusted_len, &bytes_written, null) != 0) {
+ syscall.finish();
+ return bytes_written;
+ }
switch (windows.GetLastError()) {
- .INVALID_USER_BUFFER => return error.SystemResources,
- .NOT_ENOUGH_MEMORY => return error.SystemResources,
- .OPERATION_ABORTED => return error.Canceled,
- .NOT_ENOUGH_QUOTA => return error.SystemResources,
- .NO_DATA => return error.BrokenPipe,
- .INVALID_HANDLE => return error.NotOpenForWriting,
- .LOCK_VIOLATION => return error.LockViolation,
- .ACCESS_DENIED => return error.AccessDenied,
- .WORKING_SET_QUOTA => return error.SystemResources,
- else => |err| return windows.unexpectedError(err),
+ .OPERATION_ABORTED => {
+ try syscall.checkCancel();
+ continue;
+ },
+ .INVALID_USER_BUFFER => return syscall.fail(error.SystemResources),
+ .NOT_ENOUGH_MEMORY => return syscall.fail(error.SystemResources),
+ .NOT_ENOUGH_QUOTA => return syscall.fail(error.SystemResources),
+ .NO_DATA => return syscall.fail(error.BrokenPipe),
+ .INVALID_HANDLE => return syscall.fail(error.NotOpenForWriting),
+ .LOCK_VIOLATION => return syscall.fail(error.LockViolation),
+ .ACCESS_DENIED => return syscall.fail(error.AccessDenied),
+ .WORKING_SET_QUOTA => return syscall.fail(error.SystemResources),
+ else => |err| {
+ syscall.finish();
+ return windows.unexpectedError(err);
+ },
}
}
- return bytes_written;
}
fn fileWriteFileStreaming(
@@ -7807,40 +8559,39 @@ fn fileWriteFileStreaming(
const nbytes: usize = @min(file_limit, std.math.maxInt(usize));
const flags = 0;
- const current_thread = Thread.getCurrent(t);
- try current_thread.beginSyscall();
+ const syscall: Syscall = try .start();
while (true) {
switch (posix.errno(std.c.sendfile(in_fd, out_fd, offset, nbytes, hdtr, &sbytes, flags))) {
.SUCCESS => {
- current_thread.endSyscall();
+ syscall.finish();
break;
},
.INVAL, .OPNOTSUPP, .NOTSOCK, .NOSYS => {
// Give calling code chance to observe before trying
// something else.
- current_thread.endSyscall();
+ syscall.finish();
@atomicStore(UseSendfile, &t.use_sendfile, .disabled, .monotonic);
return 0;
},
.INTR, .BUSY => {
if (sbytes == 0) {
- try current_thread.checkCancel();
+ try syscall.checkCancel();
continue;
} else {
// Even if we are being canceled, there have been side
// effects, so it is better to report those side
// effects to the caller.
- current_thread.endSyscall();
+ syscall.finish();
break;
}
},
.AGAIN => {
- current_thread.endSyscall();
+ syscall.finish();
if (sbytes == 0) return error.WouldBlock;
break;
},
else => |e| {
- current_thread.endSyscall();
+ syscall.finish();
assert(error.Unexpected == switch (e) {
.NOTCONN => return error.BrokenPipe,
.IO => return error.InputOutput,
@@ -7893,40 +8644,39 @@ fn fileWriteFileStreaming(
const max_count = std.math.maxInt(i32); // Avoid EINVAL.
var len: std.c.off_t = @min(file_limit, max_count);
const flags = 0;
- const current_thread = Thread.getCurrent(t);
- try current_thread.beginSyscall();
+ const syscall: Syscall = try .start();
while (true) {
switch (posix.errno(std.c.sendfile(in_fd, out_fd, offset, &len, hdtr, flags))) {
.SUCCESS => {
- current_thread.endSyscall();
+ syscall.finish();
break;
},
.OPNOTSUPP, .NOTSOCK, .NOSYS => {
// Give calling code chance to observe before trying
// something else.
- current_thread.endSyscall();
+ syscall.finish();
@atomicStore(UseSendfile, &t.use_sendfile, .disabled, .monotonic);
return 0;
},
.INTR => {
if (len == 0) {
- try current_thread.checkCancel();
+ try syscall.checkCancel();
continue;
} else {
// Even if we are being canceled, there have been side
// effects, so it is better to report those side
// effects to the caller.
- current_thread.endSyscall();
+ syscall.finish();
break;
}
},
.AGAIN => {
- current_thread.endSyscall();
+ syscall.finish();
if (len == 0) return error.WouldBlock;
break;
},
else => |e| {
- current_thread.endSyscall();
+ syscall.finish();
assert(error.Unexpected == switch (e) {
.NOTCONN => return error.BrokenPipe,
.IO => return error.InputOutput,
@@ -7973,28 +8723,27 @@ fn fileWriteFileStreaming(
.streaming_simple, .positional_simple => break :sf,
.failure => return error.ReadFailed,
};
- const current_thread = Thread.getCurrent(t);
- try current_thread.beginSyscall();
+ const syscall: Syscall = try .start();
const n: usize = while (true) {
const rc = sendfile_sym(out_fd, in_fd, off_ptr, count);
switch (posix.errno(rc)) {
.SUCCESS => {
- current_thread.endSyscall();
+ syscall.finish();
break @intCast(rc);
},
.NOSYS, .INVAL => {
// Give calling code chance to observe before trying
// something else.
- current_thread.endSyscall();
+ syscall.finish();
@atomicStore(UseSendfile, &t.use_sendfile, .disabled, .monotonic);
return 0;
},
.INTR => {
- try current_thread.checkCancel();
+ try syscall.checkCancel();
continue;
},
else => |e| {
- current_thread.endSyscall();
+ syscall.finish();
assert(error.Unexpected == switch (e) {
.NOTCONN => return error.BrokenPipe, // `out_fd` is an unconnected socket
.AGAIN => return error.WouldBlock,
@@ -8050,30 +8799,29 @@ fn fileWriteFileStreaming(
.streaming => null,
.failure => return error.ReadFailed,
};
- const current_thread = Thread.getCurrent(t);
const n: usize = switch (native_os) {
.linux => n: {
- try current_thread.beginSyscall();
+ const syscall: Syscall = try .start();
while (true) {
const rc = linux_copy_file_range_sys.copy_file_range(in_fd, off_in_ptr, out_fd, null, @intFromEnum(limit), 0);
switch (linux_copy_file_range_sys.errno(rc)) {
.SUCCESS => {
- current_thread.endSyscall();
+ syscall.finish();
break :n @intCast(rc);
},
.INTR => {
- try current_thread.checkCancel();
+ try syscall.checkCancel();
continue;
},
.OPNOTSUPP, .INVAL, .NOSYS => {
// Give calling code chance to observe before trying
// something else.
- current_thread.endSyscall();
+ syscall.finish();
@atomicStore(UseCopyFileRange, &t.use_copy_file_range, .disabled, .monotonic);
return 0;
},
else => |e| {
- current_thread.endSyscall();
+ syscall.finish();
assert(error.Unexpected == switch (e) {
.FBIG => return error.FileTooBig,
.IO => return error.InputOutput,
@@ -8097,27 +8845,27 @@ fn fileWriteFileStreaming(
}
},
.freebsd => n: {
- try current_thread.beginSyscall();
+ const syscall: Syscall = try .start();
while (true) {
const rc = std.c.copy_file_range(in_fd, off_in_ptr, out_fd, null, @intFromEnum(limit), 0);
switch (std.c.errno(rc)) {
.SUCCESS => {
- current_thread.endSyscall();
+ syscall.finish();
break :n @intCast(rc);
},
.INTR => {
- try current_thread.checkCancel();
+ try syscall.checkCancel();
continue;
},
.OPNOTSUPP, .INVAL, .NOSYS => {
// Give calling code chance to observe before trying
// something else.
- current_thread.endSyscall();
+ syscall.finish();
@atomicStore(UseCopyFileRange, &t.use_copy_file_range, .disabled, .monotonic);
return 0;
},
else => |e| {
- current_thread.endSyscall();
+ syscall.finish();
assert(error.Unexpected == switch (e) {
.FBIG => return error.FileTooBig,
.IO => return error.InputOutput,
@@ -8226,30 +8974,29 @@ fn fileWriteFilePositional(
.failure => return error.ReadFailed,
};
var off_out: i64 = @intCast(offset);
- const current_thread = Thread.getCurrent(t);
const n: usize = switch (native_os) {
.linux => n: {
- try current_thread.beginSyscall();
+ const syscall: Syscall = try .start();
while (true) {
const rc = linux_copy_file_range_sys.copy_file_range(in_fd, off_in_ptr, out_fd, &off_out, @intFromEnum(limit), 0);
switch (linux_copy_file_range_sys.errno(rc)) {
.SUCCESS => {
- current_thread.endSyscall();
+ syscall.finish();
break :n @intCast(rc);
},
.INTR => {
- try current_thread.checkCancel();
+ try syscall.checkCancel();
continue;
},
.OPNOTSUPP, .INVAL, .NOSYS => {
// Give calling code chance to observe before trying
// something else.
- current_thread.endSyscall();
+ syscall.finish();
@atomicStore(UseCopyFileRange, &t.use_copy_file_range, .disabled, .monotonic);
return 0;
},
else => |e| {
- current_thread.endSyscall();
+ syscall.finish();
assert(error.Unexpected == switch (e) {
.FBIG => return error.FileTooBig,
.IO => return error.InputOutput,
@@ -8274,27 +9021,27 @@ fn fileWriteFilePositional(
}
},
.freebsd => n: {
- try current_thread.beginSyscall();
+ const syscall: Syscall = try .start();
while (true) {
const rc = std.c.copy_file_range(in_fd, off_in_ptr, out_fd, &off_out, @intFromEnum(limit), 0);
switch (std.c.errno(rc)) {
.SUCCESS => {
- current_thread.endSyscall();
+ syscall.finish();
break :n @intCast(rc);
},
.INTR => {
- try current_thread.checkCancel();
+ try syscall.checkCancel();
continue;
},
.OPNOTSUPP, .INVAL, .NOSYS => {
// Give calling code chance to observe before trying
// something else.
- current_thread.endSyscall();
+ syscall.finish();
@atomicStore(UseCopyFileRange, &t.use_copy_file_range, .disabled, .monotonic);
return 0;
},
else => |e| {
- current_thread.endSyscall();
+ syscall.finish();
assert(error.Unexpected == switch (e) {
.FBIG => return error.FileTooBig,
.IO => return error.InputOutput,
@@ -8334,28 +9081,27 @@ fn fileWriteFilePositional(
file_reader.interface.toss(n -| header.len);
return n;
}
- const current_thread = Thread.getCurrent(t);
- try current_thread.beginSyscall();
+ const syscall: Syscall = try .start();
while (true) {
const rc = std.c.fcopyfile(in_fd, out_fd, null, .{ .DATA = true });
switch (posix.errno(rc)) {
.SUCCESS => {
- current_thread.endSyscall();
+ syscall.finish();
break;
},
.INTR => {
- try current_thread.checkCancel();
+ try syscall.checkCancel();
continue;
},
.OPNOTSUPP => {
// Give calling code chance to observe before trying
// something else.
- current_thread.endSyscall();
+ syscall.finish();
@atomicStore(UseFcopyfile, &t.use_fcopyfile, .disabled, .monotonic);
return 0;
},
else => |e| {
- current_thread.endSyscall();
+ syscall.finish();
assert(error.Unexpected == switch (e) {
.NOMEM => return error.SystemResources,
.INVAL => |err| errnoBug(err),
@@ -8372,9 +9118,7 @@ fn fileWriteFilePositional(
return error.Unimplemented;
}
-fn nowPosix(userdata: ?*anyopaque, clock: Io.Clock) Io.Clock.Error!Io.Timestamp {
- const t: *Threaded = @ptrCast(@alignCast(userdata));
- _ = t;
+fn nowPosix(clock: Io.Clock) Io.Clock.Error!Io.Timestamp {
const clock_id: posix.clockid_t = clockToPosix(clock);
var tp: posix.timespec = undefined;
switch (posix.errno(posix.system.clock_gettime(clock_id, &tp))) {
@@ -8384,15 +9128,17 @@ fn nowPosix(userdata: ?*anyopaque, clock: Io.Clock) Io.Clock.Error!Io.Timestamp
}
}
-const now = switch (native_os) {
- .windows => nowWindows,
- .wasi => nowWasi,
- else => nowPosix,
-};
-
-fn nowWindows(userdata: ?*anyopaque, clock: Io.Clock) Io.Clock.Error!Io.Timestamp {
+fn now(userdata: ?*anyopaque, clock: Io.Clock) Io.Clock.Error!Io.Timestamp {
const t: *Threaded = @ptrCast(@alignCast(userdata));
_ = t;
+ return switch (native_os) {
+ .windows => nowWindows(clock),
+ .wasi => nowWasi(clock),
+ else => nowPosix(clock),
+ };
+}
+
+fn nowWindows(clock: Io.Clock) Io.Clock.Error!Io.Timestamp {
switch (clock) {
.real => {
// RtlGetSystemTimePrecise() has a granularity of 100 nanoseconds
@@ -8425,25 +9171,24 @@ fn nowWindows(userdata: ?*anyopaque, clock: Io.Clock) Io.Clock.Error!Io.Timestam
}
}
-fn nowWasi(userdata: ?*anyopaque, clock: Io.Clock) Io.Clock.Error!Io.Timestamp {
- const t: *Threaded = @ptrCast(@alignCast(userdata));
- _ = t;
+fn nowWasi(clock: Io.Clock) Io.Clock.Error!Io.Timestamp {
var ns: std.os.wasi.timestamp_t = undefined;
const err = std.os.wasi.clock_time_get(clockToWasi(clock), 1, &ns);
if (err != .SUCCESS) return error.Unexpected;
return .fromNanoseconds(ns);
}
-const sleep = switch (native_os) {
- .windows => sleepWindows,
- .wasi => sleepWasi,
- .linux => sleepLinux,
- else => sleepPosix,
-};
-
-fn sleepLinux(userdata: ?*anyopaque, timeout: Io.Timeout) Io.SleepError!void {
+fn sleep(userdata: ?*anyopaque, timeout: Io.Timeout) Io.SleepError!void {
const t: *Threaded = @ptrCast(@alignCast(userdata));
- const current_thread = Thread.getCurrent(t);
+ if (use_parking_sleep) return parking_sleep.sleep(timeout);
+ switch (native_os) {
+ .wasi => return sleepWasi(t, timeout),
+ .linux => return sleepLinux(timeout),
+ else => return sleepPosix(t, timeout),
+ }
+}
+
+fn sleepLinux(timeout: Io.Timeout) Io.SleepError!void {
const clock_id: posix.clockid_t = clockToPosix(switch (timeout) {
.none => .awake,
.duration => |d| d.clock,
@@ -8455,22 +9200,22 @@ 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();
+ const syscall: Syscall = try .start();
while (true) {
switch (std.os.linux.errno(std.os.linux.clock_nanosleep(clock_id, .{ .ABSTIME = switch (timeout) {
.none, .duration => false,
.deadline => true,
} }, ×pec, ×pec))) {
.SUCCESS => {
- current_thread.endSyscall();
+ syscall.finish();
return;
},
.INTR => {
- try current_thread.checkCancel();
+ try syscall.checkCancel();
continue;
},
else => |e| {
- current_thread.endSyscall();
+ syscall.finish();
switch (e) {
.INVAL => return error.UnsupportedClock,
else => |err| return posix.unexpectedErrno(err),
@@ -8480,23 +9225,7 @@ fn sleepLinux(userdata: ?*anyopaque, timeout: Io.Timeout) Io.SleepError!void {
}
}
-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 current_thread.checkCancel();
- const ms = ms: {
- const d = (try timeout.toDurationFromNow(t_io)) orelse
- break :ms std.math.maxInt(windows.DWORD);
- break :ms std.math.lossyCast(windows.DWORD, d.raw.toMilliseconds());
- };
- // TODO: alertable true with checkCancel in a loop plus deadline
- _ = windows.kernel32.SleepEx(ms, windows.FALSE);
-}
-
-fn sleepWasi(userdata: ?*anyopaque, timeout: Io.Timeout) Io.SleepError!void {
- const t: *Threaded = @ptrCast(@alignCast(userdata));
- const current_thread = Thread.getCurrent(t);
+fn sleepWasi(t: *Threaded, timeout: Io.Timeout) Io.SleepError!void {
const t_io = ioBasic(t);
const w = std.os.wasi;
@@ -8520,14 +9249,12 @@ fn sleepWasi(userdata: ?*anyopaque, timeout: Io.Timeout) Io.SleepError!void {
};
var event: w.event_t = undefined;
var nevents: usize = undefined;
- try current_thread.beginSyscall();
+ const syscall: Syscall = try .start();
_ = w.poll_oneoff(&in, &event, 1, &nevents);
- current_thread.endSyscall();
+ syscall.finish();
}
-fn sleepPosix(userdata: ?*anyopaque, timeout: Io.Timeout) Io.SleepError!void {
- const t: *Threaded = @ptrCast(@alignCast(userdata));
- const current_thread = Thread.getCurrent(t);
+fn sleepPosix(t: *Threaded, timeout: Io.Timeout) Io.SleepError!void {
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;
@@ -8539,48 +9266,85 @@ fn sleepPosix(userdata: ?*anyopaque, timeout: Io.Timeout) Io.SleepError!void {
};
break :t timestampToPosix(d.raw.toNanoseconds());
};
- try current_thread.beginSyscall();
+ const syscall: Syscall = try .start();
while (true) {
switch (posix.errno(posix.system.nanosleep(×pec, ×pec))) {
.INTR => {
- try current_thread.checkCancel();
+ try syscall.checkCancel();
continue;
},
// This prong handles success as well as unexpected errors.
- else => return current_thread.endSyscall(),
+ else => return syscall.finish(),
}
}
}
fn select(userdata: ?*anyopaque, futures: []const *Io.AnyFuture) Io.Cancelable!usize {
const t: *Threaded = @ptrCast(@alignCast(userdata));
+ _ = t;
- var event: Io.Event = .unset;
+ var num_completed: std.atomic.Value(u32) = .init(0);
- for (futures, 0..) |future, i| {
- const closure: *AsyncClosure = @ptrCast(@alignCast(future));
- if (@atomicRmw(?*Io.Event, &closure.select_condition, .Xchg, &event, .seq_cst) == AsyncClosure.done_event) {
- for (futures[0..i]) |cleanup_future| {
- const cleanup_closure: *AsyncClosure = @ptrCast(@alignCast(cleanup_future));
- if (@atomicRmw(?*Io.Event, &cleanup_closure.select_condition, .Xchg, null, .seq_cst) == AsyncClosure.done_event) {
- cleanup_closure.event.waitUncancelable(ioBasic(t)); // Ensure no reference to our stack-allocated event.
- }
- }
- return i;
+ for (futures, 0..) |any_future, i| {
+ const future: *Future = @ptrCast(@alignCast(any_future));
+ future.awaiter = &num_completed;
+ const old_status = future.status.fetchOr(
+ .{ .tag = .pending_awaited, .thread = .null },
+ .release, // release `future.awaiter`
+ );
+ switch (old_status.tag) {
+ .pending => {},
+ .pending_awaited => unreachable, // `await` raced with `select`
+ .pending_canceled => unreachable, // `cancel` raced with `select`
+ .done => {
+ future.status.store(old_status, .monotonic);
+ _ = finishSelect(&num_completed, futures[0..i]);
+ return i;
+ },
}
}
- try event.wait(ioBasic(t));
+ errdefer _ = finishSelect(&num_completed, futures);
- var result: ?usize = null;
- for (futures, 0..) |future, i| {
- const closure: *AsyncClosure = @ptrCast(@alignCast(future));
- if (@atomicRmw(?*Io.Event, &closure.select_condition, .Xchg, null, .seq_cst) == AsyncClosure.done_event) {
- closure.event.waitUncancelable(ioBasic(t)); // Ensure no reference to our stack-allocated event.
- if (result == null) result = i; // In case multiple are ready, return first.
- }
+ while (true) {
+ const n = num_completed.load(.acquire);
+ if (n > 0) break;
+ assert(n < futures.len);
+ try Thread.futexWait(&num_completed.raw, n, null);
+ }
+ return finishSelect(&num_completed, futures).?;
+}
+fn finishSelect(
+ num_completed: *std.atomic.Value(u32),
+ futures: []const *Io.AnyFuture,
+) ?usize {
+ var completed_index: ?usize = null;
+ var expect_completed: u32 = 0;
+ for (futures, 0..) |any_future, i| {
+ const future: *Future = @ptrCast(@alignCast(any_future));
+ // This operation will convert `.pending_awaited` to `.pending`, or leave `.done` untouched.
+ switch (future.status.fetchAnd(
+ .{ .tag = @enumFromInt(0b10), .thread = .all_ones },
+ .monotonic,
+ ).tag) {
+ .pending_awaited => {},
+ .pending => unreachable,
+ .pending_canceled => unreachable,
+ .done => {
+ expect_completed += 1;
+ completed_index = i;
+ },
+ }
+ }
+ // If any future has just finished, wait for it to signal `num_completed` to avoid dangling
+ // references to stack memory.
+ while (true) {
+ const n = num_completed.load(.acquire);
+ if (n == expect_completed) break;
+ assert(n < expect_completed);
+ Thread.futexWaitUncancelable(&num_completed.raw, n, null);
}
- return result.?;
+ return completed_index;
}
fn netListenIpPosix(
@@ -8590,37 +9354,37 @@ fn netListenIpPosix(
) IpAddress.ListenError!net.Server {
if (!have_networking) return error.NetworkDown;
const t: *Threaded = @ptrCast(@alignCast(userdata));
- const current_thread = Thread.getCurrent(t);
+ _ = t;
const family = posixAddressFamily(&address);
- const socket_fd = try openSocketPosix(current_thread, family, .{
+ const socket_fd = try openSocketPosix(family, .{
.mode = options.mode,
.protocol = options.protocol,
});
errdefer posix.close(socket_fd);
if (options.reuse_address) {
- try setSocketOption(current_thread, socket_fd, posix.SOL.SOCKET, posix.SO.REUSEADDR, 1);
+ try setSocketOption(socket_fd, posix.SOL.SOCKET, posix.SO.REUSEADDR, 1);
if (@hasDecl(posix.SO, "REUSEPORT"))
- try setSocketOption(current_thread, socket_fd, posix.SOL.SOCKET, posix.SO.REUSEPORT, 1);
+ try setSocketOption(socket_fd, posix.SOL.SOCKET, posix.SO.REUSEPORT, 1);
}
var storage: PosixAddress = undefined;
var addr_len = addressToPosix(&address, &storage);
- try posixBind(current_thread, socket_fd, &storage.any, addr_len);
+ try posixBind(socket_fd, &storage.any, addr_len);
- try current_thread.beginSyscall();
+ const syscall: Syscall = try .start();
while (true) {
switch (posix.errno(posix.system.listen(socket_fd, options.kernel_backlog))) {
.SUCCESS => {
- current_thread.endSyscall();
+ syscall.finish();
break;
},
.INTR => {
- try current_thread.checkCancel();
+ try syscall.checkCancel();
continue;
},
else => |e| {
- current_thread.endSyscall();
+ syscall.finish();
switch (e) {
.ADDRINUSE => return error.AddressInUse,
.BADF => |err| return errnoBug(err), // File descriptor used after closed.
@@ -8630,7 +9394,7 @@ fn netListenIpPosix(
}
}
- try posixGetSockName(current_thread, socket_fd, &storage.any, &addr_len);
+ try posixGetSockName(socket_fd, &storage.any, &addr_len);
return .{
.socket = .{
.handle = socket_fd,
@@ -8646,9 +9410,8 @@ 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, current_thread, family, .{
+ const socket_handle = try openSocketWsa(t, family, .{
.mode = options.mode,
.protocol = options.protocol,
});
@@ -8660,27 +9423,27 @@ fn netListenIpWindows(
var storage: WsaAddress = undefined;
var addr_len = addressToWsa(&address, &storage);
- try current_thread.beginSyscall();
+ var syscall: Syscall = try .start();
while (true) {
const rc = ws2_32.bind(socket_handle, &storage.any, addr_len);
if (rc != ws2_32.SOCKET_ERROR) {
- current_thread.endSyscall();
+ syscall.finish();
break;
}
switch (ws2_32.WSAGetLastError()) {
- .EINTR => {
- try current_thread.checkCancel();
- continue;
- },
.NOTINITIALISED => {
+ syscall.finish();
try initializeWsa(t);
- try current_thread.checkCancel();
+ syscall = try .start();
+ continue;
+ },
+ .EINTR, .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => {
+ try syscall.checkCancel();
continue;
},
else => |e| {
- current_thread.endSyscall();
+ syscall.finish();
switch (e) {
- .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled,
.EADDRINUSE => return error.AddressInUse,
.EADDRNOTAVAIL => return error.AddressUnavailable,
.ENOTSOCK => |err| return wsaErrorBug(err),
@@ -8694,27 +9457,27 @@ fn netListenIpWindows(
}
}
- try current_thread.beginSyscall();
+ syscall = try .start();
while (true) {
const rc = ws2_32.listen(socket_handle, options.kernel_backlog);
if (rc != ws2_32.SOCKET_ERROR) {
- current_thread.endSyscall();
+ syscall.finish();
break;
}
switch (ws2_32.WSAGetLastError()) {
- .EINTR => {
- try current_thread.checkCancel();
- continue;
- },
.NOTINITIALISED => {
+ syscall.finish();
try initializeWsa(t);
- try current_thread.checkCancel();
+ syscall = try .start();
+ continue;
+ },
+ .EINTR, .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => {
+ try syscall.checkCancel();
continue;
},
else => |e| {
- current_thread.endSyscall();
+ syscall.finish();
switch (e) {
- .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled,
.ENETDOWN => return error.NetworkDown,
.EADDRINUSE => return error.AddressInUse,
.EISCONN => |err| return wsaErrorBug(err),
@@ -8729,7 +9492,7 @@ fn netListenIpWindows(
}
}
- try wsaGetSockName(t, current_thread, socket_handle, &storage.any, &addr_len);
+ try wsaGetSockName(t, socket_handle, &storage.any, &addr_len);
return .{
.socket = .{
@@ -8757,8 +9520,8 @@ fn netListenUnixPosix(
) 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_fd = openSocketPosix(current_thread, posix.AF.UNIX, .{ .mode = .stream }) catch |err| switch (err) {
+ _ = t;
+ const socket_fd = openSocketPosix(posix.AF.UNIX, .{ .mode = .stream }) catch |err| switch (err) {
error.ProtocolUnsupportedBySystem => return error.AddressFamilyUnsupported,
error.ProtocolUnsupportedByAddressFamily => return error.AddressFamilyUnsupported,
error.SocketModeUnsupported => return error.AddressFamilyUnsupported,
@@ -8769,21 +9532,21 @@ fn netListenUnixPosix(
var storage: UnixAddress = undefined;
const addr_len = addressUnixToPosix(address, &storage);
- try posixBindUnix(current_thread, socket_fd, &storage.any, addr_len);
+ try posixBindUnix(socket_fd, &storage.any, addr_len);
- try current_thread.beginSyscall();
+ const syscall: Syscall = try .start();
while (true) {
switch (posix.errno(posix.system.listen(socket_fd, options.kernel_backlog))) {
.SUCCESS => {
- current_thread.endSyscall();
+ syscall.finish();
break;
},
.INTR => {
- try current_thread.checkCancel();
+ try syscall.checkCancel();
continue;
},
else => |e| {
- current_thread.endSyscall();
+ syscall.finish();
switch (e) {
.ADDRINUSE => return error.AddressInUse,
.BADF => |err| return errnoBug(err), // File descriptor used after closed.
@@ -8803,9 +9566,8 @@ 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, current_thread, posix.AF.UNIX, .{ .mode = .stream }) catch |err| switch (err) {
+ const socket_handle = openSocketWsa(t, posix.AF.UNIX, .{ .mode = .stream }) catch |err| switch (err) {
error.ProtocolUnsupportedByAddressFamily => return error.AddressFamilyUnsupported,
else => |e| return e,
};
@@ -8814,24 +9576,24 @@ fn netListenUnixWindows(
var storage: WsaAddress = undefined;
const addr_len = addressUnixToWsa(address, &storage);
- try current_thread.beginSyscall();
+ var syscall: Syscall = try .start();
while (true) {
const rc = ws2_32.bind(socket_handle, &storage.any, addr_len);
if (rc != ws2_32.SOCKET_ERROR) break;
switch (ws2_32.WSAGetLastError()) {
- .EINTR => {
- try current_thread.checkCancel();
- continue;
- },
.NOTINITIALISED => {
+ syscall.finish();
try initializeWsa(t);
- try current_thread.checkCancel();
+ syscall = try .start();
+ continue;
+ },
+ .EINTR, .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => {
+ try syscall.checkCancel();
continue;
},
else => |e| {
- current_thread.endSyscall();
+ syscall.finish();
switch (e) {
- .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled,
.EADDRINUSE => return error.AddressInUse,
.EADDRNOTAVAIL => return error.AddressUnavailable,
.ENOTSOCK => |err| return wsaErrorBug(err),
@@ -8846,22 +9608,23 @@ fn netListenUnixWindows(
}
while (true) {
- try current_thread.checkCancel();
+ try syscall.checkCancel();
const rc = ws2_32.listen(socket_handle, options.kernel_backlog);
if (rc != ws2_32.SOCKET_ERROR) {
- current_thread.endSyscall();
+ syscall.finish();
return socket_handle;
}
switch (ws2_32.WSAGetLastError()) {
- .EINTR => continue,
+ .EINTR, .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => continue,
.NOTINITIALISED => {
+ syscall.finish();
try initializeWsa(t);
+ syscall = try .start();
continue;
},
else => |e| {
- current_thread.endSyscall();
+ syscall.finish();
switch (e) {
- .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled,
.ENETDOWN => return error.NetworkDown,
.EADDRINUSE => return error.AddressInUse,
.EISCONN => |err| return wsaErrorBug(err),
@@ -8889,24 +9652,23 @@ fn netListenUnixUnavailable(
}
fn posixBindUnix(
- current_thread: *Thread,
fd: posix.socket_t,
addr: *const posix.sockaddr,
addr_len: posix.socklen_t,
) !void {
- try current_thread.beginSyscall();
+ const syscall: Syscall = try .start();
while (true) {
switch (posix.errno(posix.system.bind(fd, addr, addr_len))) {
.SUCCESS => {
- current_thread.endSyscall();
+ syscall.finish();
break;
},
.INTR => {
- try current_thread.checkCancel();
+ try syscall.checkCancel();
continue;
},
else => |e| {
- current_thread.endSyscall();
+ syscall.finish();
switch (e) {
.ACCES => return error.AccessDenied,
.ADDRINUSE => return error.AddressInUse,
@@ -8933,24 +9695,23 @@ fn posixBindUnix(
}
fn posixBind(
- current_thread: *Thread,
socket_fd: posix.socket_t,
addr: *const posix.sockaddr,
addr_len: posix.socklen_t,
) !void {
- try current_thread.beginSyscall();
+ const syscall: Syscall = try .start();
while (true) {
switch (posix.errno(posix.system.bind(socket_fd, addr, addr_len))) {
.SUCCESS => {
- current_thread.endSyscall();
+ syscall.finish();
break;
},
.INTR => {
- try current_thread.checkCancel();
+ try syscall.checkCancel();
continue;
},
else => |e| {
- current_thread.endSyscall();
+ syscall.finish();
switch (e) {
.ADDRINUSE => return error.AddressInUse,
.BADF => |err| return errnoBug(err), // File descriptor used after closed.
@@ -8968,24 +9729,23 @@ fn posixBind(
}
fn posixConnect(
- current_thread: *Thread,
socket_fd: posix.socket_t,
addr: *const posix.sockaddr,
addr_len: posix.socklen_t,
) !void {
- try current_thread.beginSyscall();
+ const syscall: Syscall = try .start();
while (true) {
switch (posix.errno(posix.system.connect(socket_fd, addr, addr_len))) {
.SUCCESS => {
- current_thread.endSyscall();
+ syscall.finish();
return;
},
.INTR => {
- try current_thread.checkCancel();
+ try syscall.checkCancel();
continue;
},
else => |e| {
- current_thread.endSyscall();
+ syscall.finish();
switch (e) {
.ADDRNOTAVAIL => return error.AddressUnavailable,
.AFNOSUPPORT => return error.AddressFamilyUnsupported,
@@ -9014,24 +9774,23 @@ fn posixConnect(
}
fn posixConnectUnix(
- current_thread: *Thread,
fd: posix.socket_t,
addr: *const posix.sockaddr,
addr_len: posix.socklen_t,
) !void {
- try current_thread.beginSyscall();
+ const syscall: Syscall = try .start();
while (true) {
switch (posix.errno(posix.system.connect(fd, addr, addr_len))) {
.SUCCESS => {
- current_thread.endSyscall();
+ syscall.finish();
return;
},
.INTR => {
- try current_thread.checkCancel();
+ try syscall.checkCancel();
continue;
},
else => |e| {
- current_thread.endSyscall();
+ syscall.finish();
switch (e) {
.AFNOSUPPORT => return error.AddressFamilyUnsupported,
.AGAIN => return error.WouldBlock,
@@ -9058,24 +9817,23 @@ fn posixConnectUnix(
}
fn posixGetSockName(
- current_thread: *Thread,
socket_fd: posix.fd_t,
addr: *posix.sockaddr,
addr_len: *posix.socklen_t,
) !void {
- try current_thread.beginSyscall();
+ const syscall: Syscall = try .start();
while (true) {
switch (posix.errno(posix.system.getsockname(socket_fd, addr, addr_len))) {
.SUCCESS => {
- current_thread.endSyscall();
+ syscall.finish();
break;
},
.INTR => {
- try current_thread.checkCancel();
+ try syscall.checkCancel();
continue;
},
else => |e| {
- current_thread.endSyscall();
+ syscall.finish();
switch (e) {
.BADF => |err| return errnoBug(err), // File descriptor used after closed.
.FAULT => |err| return errnoBug(err),
@@ -9091,32 +9849,31 @@ fn posixGetSockName(
fn wsaGetSockName(
t: *Threaded,
- current_thread: *Thread,
handle: ws2_32.SOCKET,
addr: *ws2_32.sockaddr,
addr_len: *i32,
) !void {
- try current_thread.beginSyscall();
+ var syscall: Syscall = try .start();
while (true) {
const rc = ws2_32.getsockname(handle, addr, addr_len);
if (rc != ws2_32.SOCKET_ERROR) {
- current_thread.endSyscall();
+ syscall.finish();
return;
}
switch (ws2_32.WSAGetLastError()) {
- .EINTR => {
- try current_thread.checkCancel();
+ .EINTR, .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => {
+ try syscall.checkCancel();
continue;
},
.NOTINITIALISED => {
+ syscall.finish();
try initializeWsa(t);
- try current_thread.checkCancel();
+ syscall = try .start();
continue;
},
else => |e| {
- current_thread.endSyscall();
+ syscall.finish();
switch (e) {
- .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled,
.ENETDOWN => return error.NetworkDown,
.EFAULT => |err| return wsaErrorBug(err),
.ENOTSOCK => |err| return wsaErrorBug(err),
@@ -9128,21 +9885,21 @@ fn wsaGetSockName(
}
}
-fn setSocketOption(current_thread: *Thread, fd: posix.fd_t, level: i32, opt_name: u32, option: u32) !void {
+fn setSocketOption(fd: posix.fd_t, level: i32, opt_name: u32, option: u32) !void {
const o: []const u8 = @ptrCast(&option);
- try current_thread.beginSyscall();
+ const syscall: Syscall = try .start();
while (true) {
switch (posix.errno(posix.system.setsockopt(fd, level, opt_name, o.ptr, @intCast(o.len)))) {
.SUCCESS => {
- current_thread.endSyscall();
+ syscall.finish();
return;
},
.INTR => {
- try current_thread.checkCancel();
+ try syscall.checkCancel();
continue;
},
else => |e| {
- current_thread.endSyscall();
+ syscall.finish();
switch (e) {
.BADF => |err| return errnoBug(err), // File descriptor used after closed.
.NOTSOCK => |err| return errnoBug(err),
@@ -9157,21 +9914,30 @@ fn setSocketOption(current_thread: *Thread, fd: posix.fd_t, level: i32, opt_name
fn setSocketOptionWsa(t: *Threaded, socket: Io.net.Socket.Handle, level: i32, opt_name: u32, option: u32) !void {
const o: []const u8 = @ptrCast(&option);
+ var syscall: Syscall = try .start();
const rc = ws2_32.setsockopt(socket, level, @bitCast(opt_name), o.ptr, @intCast(o.len));
while (true) {
- if (rc != ws2_32.SOCKET_ERROR) return;
+ if (rc != ws2_32.SOCKET_ERROR) return syscall.finish();
switch (ws2_32.WSAGetLastError()) {
- .EINTR => continue,
- .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled,
+ .EINTR, .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => {
+ try syscall.checkCancel();
+ continue;
+ },
.NOTINITIALISED => {
+ syscall.finish();
try initializeWsa(t);
+ syscall = try .start();
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),
+ .ENETDOWN => return syscall.fail(error.NetworkDown),
+ .EFAULT, .ENOTSOCK, .EINVAL => |err| {
+ syscall.finish();
+ return wsaErrorBug(err);
+ },
+ else => |err| {
+ syscall.finish();
+ return windows.unexpectedWSAError(err);
+ },
}
}
}
@@ -9184,17 +9950,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);
+ _ = t;
const family = posixAddressFamily(address);
- const socket_fd = try openSocketPosix(current_thread, family, .{
+ const socket_fd = try openSocketPosix(family, .{
.mode = options.mode,
.protocol = options.protocol,
});
errdefer posix.close(socket_fd);
var storage: PosixAddress = undefined;
var addr_len = addressToPosix(address, &storage);
- try posixConnect(current_thread, socket_fd, &storage.any, addr_len);
- try posixGetSockName(current_thread, socket_fd, &storage.any, &addr_len);
+ try posixConnect(socket_fd, &storage.any, addr_len);
+ try posixGetSockName(socket_fd, &storage.any, &addr_len);
return .{ .socket = .{
.handle = socket_fd,
.address = addressFromPosix(&storage),
@@ -9209,9 +9975,8 @@ 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, current_thread, family, .{
+ const socket_handle = try openSocketWsa(t, family, .{
.mode = options.mode,
.protocol = options.protocol,
});
@@ -9220,27 +9985,27 @@ fn netConnectIpWindows(
var storage: WsaAddress = undefined;
var addr_len = addressToWsa(address, &storage);
- try current_thread.beginSyscall();
+ var syscall: Syscall = try .start();
while (true) {
const rc = ws2_32.connect(socket_handle, &storage.any, addr_len);
if (rc != ws2_32.SOCKET_ERROR) {
- current_thread.endSyscall();
+ syscall.finish();
break;
}
switch (ws2_32.WSAGetLastError()) {
- .EINTR => {
- try current_thread.checkCancel();
+ .EINTR, .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => {
+ try syscall.checkCancel();
continue;
},
.NOTINITIALISED => {
+ syscall.finish();
try initializeWsa(t);
- try current_thread.checkCancel();
+ syscall = try .start();
continue;
},
else => |e| {
- current_thread.endSyscall();
+ syscall.finish();
switch (e) {
- .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled,
.EADDRNOTAVAIL => return error.AddressUnavailable,
.ECONNREFUSED => return error.ConnectionRefused,
.ECONNRESET => return error.ConnectionResetByPeer,
@@ -9261,7 +10026,7 @@ fn netConnectIpWindows(
}
}
- try wsaGetSockName(t, current_thread, socket_handle, &storage.any, &addr_len);
+ try wsaGetSockName(t, socket_handle, &storage.any, &addr_len);
return .{ .socket = .{
.handle = socket_handle,
@@ -9286,15 +10051,15 @@ fn netConnectUnixPosix(
) 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_fd = openSocketPosix(current_thread, posix.AF.UNIX, .{ .mode = .stream }) catch |err| switch (err) {
+ _ = t;
+ const socket_fd = openSocketPosix(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(current_thread, socket_fd, &storage.any, addr_len);
+ try posixConnectUnix(socket_fd, &storage.any, addr_len);
return socket_fd;
}
@@ -9304,34 +10069,42 @@ 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, current_thread, posix.AF.UNIX, .{ .mode = .stream });
+ const socket_handle = try openSocketWsa(t, posix.AF.UNIX, .{ .mode = .stream });
errdefer closeSocketWindows(socket_handle);
var storage: WsaAddress = undefined;
const addr_len = addressUnixToWsa(address, &storage);
+ var syscall: Syscall = try .start();
while (true) {
const rc = ws2_32.connect(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,
+ .EINTR, .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => {
+ try syscall.checkCancel();
+ continue;
+ },
.NOTINITIALISED => {
+ syscall.finish();
try initializeWsa(t);
+ syscall = try .start();
continue;
},
-
- .ECONNREFUSED => return error.FileNotFound,
- .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),
+ else => |e| {
+ syscall.finish();
+ switch (e) {
+ .ECONNREFUSED => return error.FileNotFound,
+ .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),
+ }
+ },
}
}
@@ -9354,14 +10127,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);
+ _ = t;
const family = posixAddressFamily(address);
- const socket_fd = try openSocketPosix(current_thread, family, options);
+ const socket_fd = try openSocketPosix(family, options);
errdefer posix.close(socket_fd);
var storage: PosixAddress = undefined;
var addr_len = addressToPosix(address, &storage);
- try posixBind(current_thread, socket_fd, &storage.any, addr_len);
- try posixGetSockName(current_thread, socket_fd, &storage.any, &addr_len);
+ try posixBind(socket_fd, &storage.any, addr_len);
+ try posixGetSockName(socket_fd, &storage.any, &addr_len);
return .{
.handle = socket_fd,
.address = addressFromPosix(&storage),
@@ -9375,9 +10148,8 @@ 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, current_thread, family, .{
+ const socket_handle = try openSocketWsa(t, family, .{
.mode = options.mode,
.protocol = options.protocol,
});
@@ -9386,27 +10158,27 @@ fn netBindIpWindows(
var storage: WsaAddress = undefined;
var addr_len = addressToWsa(address, &storage);
- try current_thread.beginSyscall();
+ var syscall: Syscall = try .start();
while (true) {
const rc = ws2_32.bind(socket_handle, &storage.any, addr_len);
if (rc != ws2_32.SOCKET_ERROR) {
- current_thread.endSyscall();
+ syscall.finish();
break;
}
switch (ws2_32.WSAGetLastError()) {
- .EINTR => {
- try current_thread.checkCancel();
+ .EINTR, .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => {
+ try syscall.checkCancel();
continue;
},
.NOTINITIALISED => {
+ syscall.finish();
try initializeWsa(t);
- try current_thread.checkCancel();
+ syscall = try .start();
continue;
},
else => |e| {
- current_thread.endSyscall();
+ syscall.finish();
switch (e) {
- .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled,
.EADDRINUSE => return error.AddressInUse,
.EADDRNOTAVAIL => return error.AddressUnavailable,
.ENOTSOCK => |err| return wsaErrorBug(err),
@@ -9420,7 +10192,7 @@ fn netBindIpWindows(
}
}
- try wsaGetSockName(t, current_thread, socket_handle, &storage.any, &addr_len);
+ try wsaGetSockName(t, socket_handle, &storage.any, &addr_len);
return .{
.handle = socket_handle,
@@ -9440,7 +10212,6 @@ fn netBindIpUnavailable(
}
fn openSocketPosix(
- current_thread: *Thread,
family: posix.sa_family_t,
options: IpAddress.BindOptions,
) error{
@@ -9457,7 +10228,7 @@ fn openSocketPosix(
}!posix.socket_t {
const mode = posixSocketMode(options.mode);
const protocol = posixProtocol(options.protocol);
- try current_thread.beginSyscall();
+ const syscall: Syscall = try .start();
const socket_fd = while (true) {
const flags: u32 = mode | if (socket_flags_unsupported) 0 else posix.SOCK.CLOEXEC;
const socket_rc = posix.system.socket(family, flags, protocol);
@@ -9466,25 +10237,25 @@ fn openSocketPosix(
const fd: posix.fd_t = @intCast(socket_rc);
errdefer posix.close(fd);
if (socket_flags_unsupported) while (true) {
- try current_thread.checkCancel();
+ try syscall.checkCancel();
switch (posix.errno(posix.system.fcntl(fd, posix.F.SETFD, @as(usize, posix.FD_CLOEXEC)))) {
.SUCCESS => break,
.INTR => continue,
else => |err| {
- current_thread.endSyscall();
+ syscall.finish();
return posix.unexpectedErrno(err);
},
}
};
- current_thread.endSyscall();
+ syscall.finish();
break fd;
},
.INTR => {
- try current_thread.checkCancel();
+ try syscall.checkCancel();
continue;
},
else => |e| {
- current_thread.endSyscall();
+ syscall.finish();
switch (e) {
.AFNOSUPPORT => return error.AddressFamilyUnsupported,
.INVAL => return error.ProtocolUnsupportedBySystem,
@@ -9503,7 +10274,7 @@ fn openSocketPosix(
if (options.ip6_only) {
if (posix.IPV6 == void) return error.OptionUnsupported;
- try setSocketOption(current_thread, socket_fd, posix.IPPROTO.IPV6, posix.IPV6.V6ONLY, 0);
+ try setSocketOption(socket_fd, posix.IPPROTO.IPV6, posix.IPV6.V6ONLY, 0);
}
return socket_fd;
@@ -9511,34 +10282,33 @@ fn openSocketPosix(
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();
+ var syscall: Syscall = try .start();
while (true) {
const rc = ws2_32.WSASocketW(family, @bitCast(mode), @bitCast(protocol), null, 0, flags);
if (rc != ws2_32.INVALID_SOCKET) {
- current_thread.endSyscall();
+ syscall.finish();
return rc;
}
switch (ws2_32.WSAGetLastError()) {
- .EINTR => {
- try current_thread.checkCancel();
+ .EINTR, .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => {
+ try syscall.checkCancel();
continue;
},
.NOTINITIALISED => {
+ syscall.finish();
try initializeWsa(t);
- try current_thread.checkCancel();
+ syscall = try .start();
continue;
},
else => |e| {
- current_thread.endSyscall();
+ syscall.finish();
switch (e) {
- .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled,
.EAFNOSUPPORT => return error.AddressFamilyUnsupported,
.EMFILE => return error.ProcessFdQuotaExceeded,
.ENOBUFS => return error.SystemResources,
@@ -9553,10 +10323,10 @@ fn openSocketWsa(
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);
+ _ = t;
var storage: PosixAddress = undefined;
var addr_len: posix.socklen_t = @sizeOf(PosixAddress);
- try current_thread.beginSyscall();
+ const syscall: Syscall = try .start();
const fd = while (true) {
const rc = if (have_accept4)
posix.system.accept4(listen_fd, &storage.any, &addr_len, posix.SOCK.CLOEXEC)
@@ -9567,25 +10337,25 @@ 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 current_thread.checkCancel();
+ try syscall.checkCancel();
switch (posix.errno(posix.system.fcntl(fd, posix.F.SETFD, @as(usize, posix.FD_CLOEXEC)))) {
.SUCCESS => break,
.INTR => continue,
else => |err| {
- current_thread.endSyscall();
+ syscall.finish();
return posix.unexpectedErrno(err);
},
}
};
- current_thread.endSyscall();
+ syscall.finish();
break fd;
},
.INTR => {
- try current_thread.checkCancel();
+ try syscall.checkCancel();
continue;
},
else => |e| {
- current_thread.endSyscall();
+ syscall.finish();
switch (e) {
.AGAIN => |err| return errnoBug(err),
.BADF => |err| return errnoBug(err), // File descriptor used after closed.
@@ -9614,33 +10384,32 @@ 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();
+ var syscall: Syscall = try .start();
while (true) {
const rc = ws2_32.accept(listen_handle, &storage.any, &addr_len);
if (rc != ws2_32.INVALID_SOCKET) {
- current_thread.endSyscall();
+ syscall.finish();
return .{ .socket = .{
.handle = rc,
.address = addressFromWsa(&storage),
} };
}
switch (ws2_32.WSAGetLastError()) {
- .EINTR => {
- try current_thread.checkCancel();
+ .EINTR, .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => {
+ try syscall.checkCancel();
continue;
},
.NOTINITIALISED => {
+ syscall.finish();
try initializeWsa(t);
- try current_thread.checkCancel();
+ syscall = try .start();
continue;
},
else => |e| {
- current_thread.endSyscall();
+ syscall.finish();
switch (e) {
- .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled,
.ECONNRESET => return error.ConnectionAborted,
.EFAULT => |err| return wsaErrorBug(err),
.ENOTSOCK => |err| return wsaErrorBug(err),
@@ -9665,7 +10434,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);
+ _ = t;
var iovecs_buffer: [max_iovecs_len]posix.iovec = undefined;
var i: usize = 0;
@@ -9680,20 +10449,20 @@ fn netReadPosix(userdata: ?*anyopaque, fd: net.Socket.Handle, data: [][]u8) net.
assert(dest[0].len > 0);
if (native_os == .wasi and !builtin.link_libc) {
- try current_thread.beginSyscall();
+ const syscall: Syscall = try .start();
while (true) {
var n: usize = undefined;
switch (std.os.wasi.fd_read(fd, dest.ptr, dest.len, &n)) {
.SUCCESS => {
- current_thread.endSyscall();
+ syscall.finish();
return n;
},
.INTR => {
- try current_thread.checkCancel();
+ try syscall.checkCancel();
continue;
},
else => |e| {
- current_thread.endSyscall();
+ syscall.finish();
switch (e) {
.INVAL => |err| return errnoBug(err),
.FAULT => |err| return errnoBug(err),
@@ -9712,20 +10481,20 @@ fn netReadPosix(userdata: ?*anyopaque, fd: net.Socket.Handle, data: [][]u8) net.
}
}
- try current_thread.beginSyscall();
+ const syscall: Syscall = try .start();
while (true) {
const rc = posix.system.readv(fd, dest.ptr, @intCast(dest.len));
switch (posix.errno(rc)) {
.SUCCESS => {
- current_thread.endSyscall();
+ syscall.finish();
return @intCast(rc);
},
.INTR => {
- try current_thread.checkCancel();
+ try syscall.checkCancel();
continue;
},
else => |e| {
- current_thread.endSyscall();
+ syscall.finish();
switch (e) {
.INVAL => |err| return errnoBug(err),
.FAULT => |err| return errnoBug(err),
@@ -9748,7 +10517,6 @@ 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;
@@ -9775,48 +10543,41 @@ fn netReadWindows(userdata: ?*anyopaque, handle: net.Socket.Handle, data: [][]u8
break :b bufs;
};
+ var syscall: Syscall = try .start();
while (true) {
- try current_thread.checkCancel();
-
var flags: u32 = 0;
- var overlapped: windows.OVERLAPPED = std.mem.zeroes(windows.OVERLAPPED);
var n: u32 = undefined;
- const rc = ws2_32.WSARecv(handle, bufs.ptr, @intCast(bufs.len), &n, &flags, &overlapped, null);
- if (rc != ws2_32.SOCKET_ERROR) return n;
- const wsa_error: ws2_32.WinsockError = switch (ws2_32.WSAGetLastError()) {
- .IO_PENDING => e: {
- var result_flags: u32 = undefined;
- const overlapped_rc = ws2_32.WSAGetOverlappedResult(
- handle,
- &overlapped,
- &n,
- windows.TRUE,
- &result_flags,
- );
- if (overlapped_rc == windows.FALSE) {
- break :e ws2_32.WSAGetLastError();
- } else {
- return n;
- }
+ const rc = ws2_32.WSARecv(handle, bufs.ptr, @intCast(bufs.len), &n, &flags, null, null);
+ if (rc != ws2_32.SOCKET_ERROR) {
+ syscall.finish();
+ return n;
+ }
+ switch (ws2_32.WSAGetLastError()) {
+ .EINTR, .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => {
+ try syscall.checkCancel();
+ continue;
},
- else => |err| err,
- };
- switch (wsa_error) {
- .EINTR => continue,
- .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled,
.NOTINITIALISED => {
+ syscall.finish();
try initializeWsa(t);
+ syscall = try .start();
continue;
},
- .ECONNRESET => return error.ConnectionResetByPeer,
+ .ECONNRESET => return syscall.fail(error.ConnectionResetByPeer),
+ .ENETDOWN => return syscall.fail(error.NetworkDown),
+ .ENETRESET => return syscall.fail(error.ConnectionResetByPeer),
+ .ENOTCONN => return syscall.fail(error.SocketUnconnected),
.EFAULT => unreachable, // a pointer is not completely contained in user address space.
- .EINVAL => |err| return wsaErrorBug(err),
- .EMSGSIZE => |err| return wsaErrorBug(err),
- .ENETDOWN => return error.NetworkDown,
- .ENETRESET => return error.ConnectionResetByPeer,
- .ENOTCONN => return error.SocketUnconnected,
- else => |err| return windows.unexpectedWSAError(err),
+
+ else => |err| {
+ syscall.finish();
+ switch (err) {
+ .EINVAL => return wsaErrorBug(err),
+ .EMSGSIZE => return wsaErrorBug(err),
+ else => return windows.unexpectedWSAError(err),
+ }
+ },
}
}
}
@@ -9836,7 +10597,6 @@ 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) |
@@ -9849,10 +10609,10 @@ fn netSendPosix(
var i: usize = 0;
while (messages.len - i != 0) {
if (have_sendmmsg) {
- i += netSendMany(current_thread, handle, messages[i..], posix_flags) catch |err| return .{ err, i };
+ i += netSendMany(handle, messages[i..], posix_flags) catch |err| return .{ err, i };
continue;
}
- netSendOne(t, current_thread, handle, &messages[i], posix_flags) catch |err| return .{ err, i };
+ netSendOne(t, handle, &messages[i], posix_flags) catch |err| return .{ err, i };
i += 1;
}
return .{ null, i };
@@ -9888,7 +10648,6 @@ fn netSendUnavailable(
fn netSendOne(
t: *Threaded,
- current_thread: *Thread,
handle: net.Socket.Handle,
message: *net.OutgoingMessage,
flags: u32,
@@ -9905,29 +10664,29 @@ fn netSendOne(
.controllen = @intCast(message.control.len),
.flags = 0,
};
- try current_thread.beginSyscall();
+ var syscall: Syscall = try .start();
while (true) {
const rc = posix.system.sendmsg(handle, &msg, flags);
if (is_windows) {
if (rc != ws2_32.SOCKET_ERROR) {
- current_thread.endSyscall();
+ syscall.finish();
message.data_len = @intCast(rc);
return;
}
switch (ws2_32.WSAGetLastError()) {
- .EINTR => {
- try current_thread.checkCancel();
+ .EINTR, .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => {
+ try syscall.checkCancel();
continue;
},
.NOTINITIALISED => {
+ syscall.finish();
try initializeWsa(t);
- try current_thread.checkCancel();
+ syscall = try .start();
continue;
},
else => |e| {
- current_thread.endSyscall();
+ syscall.finish();
switch (e) {
- .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled,
.EACCES => return error.AccessDenied,
.EADDRNOTAVAIL => return error.AddressUnavailable,
.ECONNRESET => return error.ConnectionResetByPeer,
@@ -9951,16 +10710,16 @@ fn netSendOne(
}
switch (posix.errno(rc)) {
.SUCCESS => {
- current_thread.endSyscall();
+ syscall.finish();
message.data_len = @intCast(rc);
return;
},
.INTR => {
- try current_thread.checkCancel();
+ try syscall.checkCancel();
continue;
},
else => |e| {
- current_thread.endSyscall();
+ syscall.finish();
switch (e) {
.ACCES => return error.AccessDenied,
.ALREADY => return error.FastOpenAlreadyInProgress,
@@ -9989,7 +10748,6 @@ fn netSendOne(
}
fn netSendMany(
- current_thread: *Thread,
handle: net.Socket.Handle,
messages: []net.OutgoingMessage,
flags: u32,
@@ -10019,12 +10777,12 @@ fn netSendMany(
};
}
- try current_thread.beginSyscall();
+ const syscall: Syscall = try .start();
while (true) {
const rc = posix.system.sendmmsg(handle, clamped_msgs.ptr, @intCast(clamped_msgs.len), flags);
switch (posix.errno(rc)) {
.SUCCESS => {
- current_thread.endSyscall();
+ syscall.finish();
const n: usize = @intCast(rc);
for (clamped_messages[0..n], clamped_msgs[0..n]) |*message, *msg| {
message.data_len = msg.len;
@@ -10032,11 +10790,11 @@ fn netSendMany(
return n;
},
.INTR => {
- try current_thread.checkCancel();
+ try syscall.checkCancel();
continue;
},
else => |e| {
- current_thread.endSyscall();
+ syscall.finish();
switch (e) {
.AGAIN => |err| return errnoBug(err),
.ALREADY => return error.FastOpenAlreadyInProgress,
@@ -10074,7 +10832,6 @@ 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:
@@ -10120,9 +10877,12 @@ 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();
+ const recv_rc = rc: {
+ const syscall = Syscall.start() catch |err| return .{ err, message_i };
+ const rc = posix.system.recvmsg(handle, &msg, posix_flags);
+ syscall.finish();
+ break :rc rc;
+ };
switch (posix.errno(recv_rc)) {
.SUCCESS => {
const data = remaining_data_buffer[0..@intCast(recv_rc)];
@@ -10152,9 +10912,9 @@ 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 syscall = Syscall.start() catch |err| return .{ err, message_i };
const poll_rc = posix.system.poll(&poll_fds, poll_fds.len, timeout_ms);
- current_thread.endSyscall();
+ syscall.finish();
switch (posix.errno(poll_rc)) {
.SUCCESS => {
@@ -10240,7 +11000,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);
+ _ = t;
var iovecs: [max_iovecs_len]posix.iovec_const = undefined;
var msg: posix.msghdr_const = .{
@@ -10282,20 +11042,20 @@ fn netWritePosix(
};
const flags = posix.MSG.NOSIGNAL;
- try current_thread.beginSyscall();
+ const syscall: Syscall = try .start();
while (true) {
const rc = posix.system.sendmsg(fd, &msg, flags);
switch (posix.errno(rc)) {
.SUCCESS => {
- current_thread.endSyscall();
+ syscall.finish();
return @intCast(rc);
},
.INTR => {
- try current_thread.checkCancel();
+ try syscall.checkCancel();
continue;
},
else => |e| {
- current_thread.endSyscall();
+ syscall.finish();
switch (e) {
.ACCES => |err| return errnoBug(err),
.AGAIN => |err| return errnoBug(err),
@@ -10332,7 +11092,6 @@ 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;
@@ -10365,49 +11124,44 @@ fn netWriteWindows(
},
};
+ var syscall: Syscall = try .start();
while (true) {
- try current_thread.checkCancel();
-
var n: u32 = undefined;
- var overlapped: windows.OVERLAPPED = std.mem.zeroes(windows.OVERLAPPED);
- const rc = ws2_32.WSASend(handle, &iovecs, len, &n, 0, &overlapped, null);
- if (rc != ws2_32.SOCKET_ERROR) return n;
- const wsa_error: ws2_32.WinsockError = switch (ws2_32.WSAGetLastError()) {
- .IO_PENDING => e: {
- var result_flags: u32 = undefined;
- const overlapped_rc = ws2_32.WSAGetOverlappedResult(
- handle,
- &overlapped,
- &n,
- windows.TRUE,
- &result_flags,
- );
- if (overlapped_rc == windows.FALSE) {
- break :e ws2_32.WSAGetLastError();
- } else {
- return n;
- }
+ const rc = ws2_32.WSASend(handle, &iovecs, len, &n, 0, null, null);
+ if (rc != ws2_32.SOCKET_ERROR) {
+ syscall.finish();
+ return n;
+ }
+ switch (ws2_32.WSAGetLastError()) {
+ .IO_PENDING => unreachable, // not overlapped
+ .EINTR, .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => {
+ try syscall.checkCancel();
+ continue;
},
- else => |err| err,
- };
- switch (wsa_error) {
- .EINTR, .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => continue,
.NOTINITIALISED => {
+ syscall.finish();
try initializeWsa(t);
+ syscall = try .start();
continue;
},
- .ECONNABORTED => return error.ConnectionResetByPeer,
- .ECONNRESET => return error.ConnectionResetByPeer,
- .EINVAL => return error.SocketUnconnected,
- .ENETDOWN => return error.NetworkDown,
- .ENETRESET => return error.ConnectionResetByPeer,
- .ENOBUFS => return error.SystemResources,
- .ENOTCONN => return error.SocketUnconnected,
- .ENOTSOCK => |err| return wsaErrorBug(err),
- .EOPNOTSUPP => |err| return wsaErrorBug(err),
- .ESHUTDOWN => |err| return wsaErrorBug(err),
- else => |err| return windows.unexpectedWSAError(err),
+ .ECONNABORTED => return syscall.fail(error.ConnectionResetByPeer),
+ .ECONNRESET => return syscall.fail(error.ConnectionResetByPeer),
+ .EINVAL => return syscall.fail(error.SocketUnconnected),
+ .ENETDOWN => return syscall.fail(error.NetworkDown),
+ .ENETRESET => return syscall.fail(error.ConnectionResetByPeer),
+ .ENOBUFS => return syscall.fail(error.SystemResources),
+ .ENOTCONN => return syscall.fail(error.SocketUnconnected),
+
+ else => |err| {
+ syscall.finish();
+ switch (err) {
+ .ENOTSOCK => return wsaErrorBug(err),
+ .EOPNOTSUPP => return wsaErrorBug(err),
+ .ESHUTDOWN => return wsaErrorBug(err),
+ else => return windows.unexpectedWSAError(err),
+ }
+ },
}
}
}
@@ -10476,7 +11230,7 @@ fn netCloseUnavailable(userdata: ?*anyopaque, handles: []const net.Socket.Handle
fn netShutdownPosix(userdata: ?*anyopaque, handle: net.Socket.Handle, how: net.ShutdownHow) net.ShutdownError!void {
if (!have_networking) return error.NetworkDown;
const t: *Threaded = @ptrCast(@alignCast(userdata));
- const current_thread = Thread.getCurrent(t);
+ _ = t;
const posix_how: i32 = switch (how) {
.recv => posix.SHUT.RD,
@@ -10484,19 +11238,16 @@ fn netShutdownPosix(userdata: ?*anyopaque, handle: net.Socket.Handle, how: net.S
.both => posix.SHUT.RDWR,
};
- try current_thread.beginSyscall();
+ const syscall: Syscall = try .start();
while (true) {
switch (posix.errno(posix.system.shutdown(handle, posix_how))) {
- .SUCCESS => {
- current_thread.endSyscall();
- return;
- },
+ .SUCCESS => return syscall.finish(),
.INTR => {
- try current_thread.checkCancel();
+ try syscall.checkCancel();
continue;
},
else => |e| {
- current_thread.endSyscall();
+ syscall.finish();
switch (e) {
.BADF, .NOTSOCK, .INVAL => |err| return errnoBug(err),
.NOTCONN => return error.SocketUnconnected,
@@ -10511,7 +11262,6 @@ fn netShutdownPosix(userdata: ?*anyopaque, handle: net.Socket.Handle, how: net.S
fn netShutdownWindows(userdata: ?*anyopaque, handle: net.Socket.Handle, how: net.ShutdownHow) net.ShutdownError!void {
if (!have_networking) return error.NetworkDown;
const t: *Threaded = @ptrCast(@alignCast(userdata));
- const current_thread = Thread.getCurrent(t);
const wsa_how: i32 = switch (how) {
.recv => ws2_32.SD_RECEIVE,
@@ -10519,27 +11269,27 @@ fn netShutdownWindows(userdata: ?*anyopaque, handle: net.Socket.Handle, how: net
.both => ws2_32.SD_BOTH,
};
- try current_thread.beginSyscall();
+ var syscall: Syscall = try .start();
while (true) {
const rc = ws2_32.shutdown(handle, wsa_how);
if (rc != ws2_32.SOCKET_ERROR) {
- current_thread.endSyscall();
+ syscall.finish();
return;
}
switch (ws2_32.WSAGetLastError()) {
- .EINTR => {
- try current_thread.checkCancel();
+ .EINTR, .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => {
+ try syscall.checkCancel();
continue;
},
.NOTINITIALISED => {
+ syscall.finish();
try initializeWsa(t);
- try current_thread.checkCancel();
+ syscall = try .start();
continue;
},
else => |e| {
- current_thread.endSyscall();
+ syscall.finish();
switch (e) {
- .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled,
.ECONNABORTED => return error.ConnectionAborted,
.ECONNRESET => return error.ConnectionResetByPeer,
.ENETDOWN => return error.NetworkDown,
@@ -10562,10 +11312,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);
+ _ = t;
if (native_os == .linux) {
- const sock_fd = openSocketPosix(current_thread, posix.AF.UNIX, .{ .mode = .dgram }) catch |err| switch (err) {
+ const sock_fd = openSocketPosix(posix.AF.UNIX, .{ .mode = .dgram }) catch |err| switch (err) {
error.ProcessFdQuotaExceeded => return error.SystemResources,
error.SystemFdQuotaExceeded => return error.SystemResources,
error.AddressFamilyUnsupported => return error.Unexpected,
@@ -10582,19 +11332,19 @@ fn netInterfaceNameResolve(
.ifru = undefined,
};
- try current_thread.beginSyscall();
+ const syscall: Syscall = try .start();
while (true) {
switch (posix.errno(posix.system.ioctl(sock_fd, posix.SIOCGIFINDEX, @intFromPtr(&ifr)))) {
.SUCCESS => {
- current_thread.endSyscall();
+ syscall.finish();
return .{ .index = @bitCast(ifr.ifru.ivalue) };
},
.INTR => {
- try current_thread.checkCancel();
+ try syscall.checkCancel();
continue;
},
else => |e| {
- current_thread.endSyscall();
+ syscall.finish();
switch (e) {
.INVAL => |err| return errnoBug(err), // Bad parameters.
.NOTTY => |err| return errnoBug(err),
@@ -10611,12 +11361,12 @@ fn netInterfaceNameResolve(
}
if (native_os == .windows) {
- try current_thread.checkCancel();
+ try Thread.checkCancel();
@panic("TODO implement netInterfaceNameResolve for Windows");
}
if (builtin.link_libc) {
- try current_thread.checkCancel();
+ try Thread.checkCancel();
const index = std.c.if_nametoindex(&name.bytes);
if (index == 0) return error.InterfaceNotFound;
return .{ .index = @bitCast(index) };
@@ -10636,8 +11386,8 @@ fn netInterfaceNameResolveUnavailable(
fn netInterfaceName(userdata: ?*anyopaque, interface: net.Interface) net.Interface.NameError!net.Interface.Name {
const t: *Threaded = @ptrCast(@alignCast(userdata));
- const current_thread = Thread.getCurrent(t);
- try current_thread.checkCancel();
+ _ = t;
+ try Thread.checkCancel();
if (native_os == .linux) {
_ = interface;
@@ -10696,7 +11446,6 @@ fn netLookupFallible(
) (net.HostName.LookupError || Io.QueueClosedError)!void {
if (!have_networking) return error.NetworkDown;
- const current_thread: *Thread = .getCurrent(t);
const t_io = io(t);
const name = host_name.bytes;
assert(name.len <= HostName.max_len);
@@ -10733,18 +11482,17 @@ fn netLookupFallible(
.provider = null,
.next = null,
};
- const cancel_handle: ?*windows.HANDLE = null;
var res: *ws2_32.ADDRINFOEXW = undefined;
const timeout: ?*ws2_32.timeval = null;
while (true) {
- 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));
+ // TODO: hook this up to cancelation with `Thread.Status.cancelation.blocked_windows_dns`.
+ // See matching TODO in `Thread.cancelAwaitable`.
+ try Thread.checkCancel();
+ // 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, null));
switch (rc) {
@as(ws2_32.WinsockError, @enumFromInt(0)) => break,
- .EINTR => continue,
- .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled,
+ .EINTR, .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => continue,
.NOTINITIALISED => {
try initializeWsa(t);
continue;
@@ -10884,25 +11632,25 @@ fn netLookupFallible(
.next = null,
};
var res: ?*posix.addrinfo = null;
- try current_thread.beginSyscall();
+ const syscall: Syscall = try .start();
while (true) {
switch (posix.system.getaddrinfo(name_c.ptr, port_c.ptr, &hints, &res)) {
@as(posix.system.EAI, @enumFromInt(0)) => {
- current_thread.endSyscall();
+ syscall.finish();
break;
},
.SYSTEM => switch (posix.errno(-1)) {
.INTR => {
- try current_thread.checkCancel();
+ try syscall.checkCancel();
continue;
},
else => |e| {
- current_thread.endSyscall();
+ syscall.finish();
return posix.unexpectedErrno(e);
},
},
else => |e| {
- current_thread.endSyscall();
+ syscall.finish();
switch (e) {
.ADDRFAMILY => return error.AddressFamilyUnsupported,
.AGAIN => return error.NameServerFailure,
@@ -10977,7 +11725,7 @@ fn unlockStderr(userdata: ?*anyopaque) void {
const t: *Threaded = @ptrCast(@alignCast(userdata));
t.stderr_writer.interface.flush() catch |err| switch (err) {
error.WriteFailed => switch (t.stderr_writer.err.?) {
- error.Canceled => recancel(t),
+ error.Canceled => recancelInner(),
else => {},
},
};
@@ -10989,62 +11737,66 @@ fn unlockStderr(userdata: ?*anyopaque) void {
fn processSetCurrentDir(userdata: ?*anyopaque, dir: Dir) std.process.SetCurrentDirError!void {
if (native_os == .wasi) return error.OperationUnsupported;
const t: *Threaded = @ptrCast(@alignCast(userdata));
- const current_thread = Thread.getCurrent(t);
+ _ = t;
if (is_windows) {
- try current_thread.checkCancel();
var dir_path_buffer: [windows.PATH_MAX_WIDE]u16 = undefined;
// TODO move GetFinalPathNameByHandle logic into std.Io.Threaded and add cancel checks
+ try Thread.checkCancel();
const dir_path = try windows.GetFinalPathNameByHandle(dir.handle, .{}, &dir_path_buffer);
const path_len_bytes = std.math.cast(u16, dir_path.len * 2) orelse return error.NameTooLong;
- try current_thread.checkCancel();
var nt_name: windows.UNICODE_STRING = .{
.Length = path_len_bytes,
.MaximumLength = path_len_bytes,
.Buffer = @constCast(dir_path.ptr),
};
- switch (windows.ntdll.RtlSetCurrentDirectory_U(&nt_name)) {
- .SUCCESS => return,
- .OBJECT_NAME_INVALID => return error.BadPathName,
- .OBJECT_NAME_NOT_FOUND => return error.FileNotFound,
- .OBJECT_PATH_NOT_FOUND => return error.FileNotFound,
- .NO_MEDIA_IN_DEVICE => return error.NoDevice,
- .INVALID_PARAMETER => |err| return windows.statusBug(err),
- .ACCESS_DENIED => return error.AccessDenied,
- .OBJECT_PATH_SYNTAX_BAD => |err| return windows.statusBug(err),
- .NOT_A_DIRECTORY => return error.NotDir,
- else => |status| return windows.unexpectedStatus(status),
- }
+ const syscall: Syscall = try .start();
+ while (true) switch (windows.ntdll.RtlSetCurrentDirectory_U(&nt_name)) {
+ .SUCCESS => return syscall.finish(),
+ .OBJECT_NAME_INVALID => return syscall.fail(error.BadPathName),
+ .OBJECT_NAME_NOT_FOUND => return syscall.fail(error.FileNotFound),
+ .OBJECT_PATH_NOT_FOUND => return syscall.fail(error.FileNotFound),
+ .NO_MEDIA_IN_DEVICE => return syscall.fail(error.NoDevice),
+ .INVALID_PARAMETER => |err| return syscall.ntstatusBug(err),
+ .ACCESS_DENIED => return syscall.fail(error.AccessDenied),
+ .OBJECT_PATH_SYNTAX_BAD => |err| return syscall.ntstatusBug(err),
+ .NOT_A_DIRECTORY => return syscall.fail(error.NotDir),
+ .CANCELLED => {
+ try syscall.checkCancel();
+ continue;
+ },
+ else => |status| return syscall.unexpectedNtstatus(status),
+ };
}
if (dir.handle == posix.AT.FDCWD) return;
- try current_thread.beginSyscall();
+ const syscall: Syscall = try .start();
while (true) {
switch (posix.errno(posix.system.fchdir(dir.handle))) {
- .SUCCESS => return current_thread.endSyscall(),
+ .SUCCESS => return syscall.finish(),
.INTR => {
- try current_thread.checkCancel();
+ try syscall.checkCancel();
continue;
},
.ACCES => {
- current_thread.endSyscall();
+ syscall.finish();
return error.AccessDenied;
},
.BADF => |err| {
- current_thread.endSyscall();
+ syscall.finish();
return errnoBug(err);
},
.NOTDIR => {
- current_thread.endSyscall();
+ syscall.finish();
return error.NotDir;
},
.IO => {
- current_thread.endSyscall();
+ syscall.finish();
return error.FileSystem;
},
else => |err| {
- current_thread.endSyscall();
+ syscall.finish();
return posix.unexpectedErrno(err);
},
}
@@ -11825,391 +12577,6 @@ fn initializeWsa(t: *Threaded) error{ NetworkDown, Canceled }!void {
fn doNothingSignalHandler(_: posix.SIG) callconv(.c) void {}
-const pthreads_futex = struct {
- const c = std.c;
- const atomic = std.atomic;
-
- const Event = struct {
- cond: c.pthread_cond_t,
- mutex: c.pthread_mutex_t,
- state: enum { empty, waiting, notified },
-
- fn init(self: *Event) void {
- // Use static init instead of pthread_cond/mutex_init() since this is generally faster.
- self.cond = .{};
- self.mutex = .{};
- self.state = .empty;
- }
-
- fn deinit(self: *Event) void {
- // Some platforms reportedly give EINVAL for statically initialized pthread types.
- const rc = c.pthread_cond_destroy(&self.cond);
- assert(rc == .SUCCESS or rc == .INVAL);
-
- const rm = c.pthread_mutex_destroy(&self.mutex);
- assert(rm == .SUCCESS or rm == .INVAL);
-
- self.* = undefined;
- }
-
- fn wait(self: *Event, timeout: ?u64) error{Timeout}!void {
- assert(c.pthread_mutex_lock(&self.mutex) == .SUCCESS);
- defer assert(c.pthread_mutex_unlock(&self.mutex) == .SUCCESS);
-
- // Early return if the event was already set.
- if (self.state == .notified) {
- return;
- }
-
- // Compute the absolute timeout if one was specified.
- // POSIX requires that REALTIME is used by default for the pthread timedwait functions.
- // This can be changed with pthread_condattr_setclock, but it's an extension and may not be available everywhere.
- var ts: c.timespec = undefined;
- if (timeout) |timeout_ns| {
- ts = std.posix.clock_gettime(c.CLOCK.REALTIME) catch return error.Timeout;
- ts.sec +|= @as(@TypeOf(ts.sec), @intCast(timeout_ns / std.time.ns_per_s));
- ts.nsec += @as(@TypeOf(ts.nsec), @intCast(timeout_ns % std.time.ns_per_s));
-
- if (ts.nsec >= std.time.ns_per_s) {
- ts.sec +|= 1;
- ts.nsec -= std.time.ns_per_s;
- }
- }
-
- // Start waiting on the event - there can be only one thread waiting.
- assert(self.state == .empty);
- self.state = .waiting;
-
- while (true) {
- // Block using either pthread_cond_wait or pthread_cond_timewait if there's an absolute timeout.
- const rc = blk: {
- if (timeout == null) break :blk c.pthread_cond_wait(&self.cond, &self.mutex);
- break :blk c.pthread_cond_timedwait(&self.cond, &self.mutex, &ts);
- };
-
- // After waking up, check if the event was set.
- if (self.state == .notified) {
- return;
- }
-
- assert(self.state == .waiting);
- switch (rc) {
- .SUCCESS => {},
- .TIMEDOUT => {
- // If timed out, reset the event to avoid the set() thread doing an unnecessary signal().
- self.state = .empty;
- return error.Timeout;
- },
- .INVAL => recoverableOsBugDetected(), // cond, mutex, and potentially ts should all be valid
- .PERM => recoverableOsBugDetected(), // mutex is locked when cond_*wait() functions are called
- else => recoverableOsBugDetected(),
- }
- }
- }
-
- fn set(self: *Event) void {
- assert(c.pthread_mutex_lock(&self.mutex) == .SUCCESS);
- defer assert(c.pthread_mutex_unlock(&self.mutex) == .SUCCESS);
-
- // Make sure that multiple calls to set() were not done on the same Event.
- const old_state = self.state;
- assert(old_state != .notified);
-
- // Mark the event as set and wake up the waiting thread if there was one.
- // This must be done while the mutex as the wait() thread could deallocate
- // the condition variable once it observes the new state, potentially causing a UAF if done unlocked.
- self.state = .notified;
- if (old_state == .waiting) {
- assert(c.pthread_cond_signal(&self.cond) == .SUCCESS);
- }
- }
- };
-
- const Treap = std.Treap(usize, std.math.order);
- const Waiter = struct {
- node: Treap.Node,
- prev: ?*Waiter,
- next: ?*Waiter,
- tail: ?*Waiter,
- is_queued: bool,
- event: Event,
- };
-
- // An unordered set of Waiters
- const WaitList = struct {
- top: ?*Waiter = null,
- len: usize = 0,
-
- fn push(self: *WaitList, waiter: *Waiter) void {
- waiter.next = self.top;
- self.top = waiter;
- self.len += 1;
- }
-
- fn pop(self: *WaitList) ?*Waiter {
- const waiter = self.top orelse return null;
- self.top = waiter.next;
- self.len -= 1;
- return waiter;
- }
- };
-
- const WaitQueue = struct {
- fn insert(treap: *Treap, address: usize, waiter: *Waiter) void {
- // prepare the waiter to be inserted.
- waiter.next = null;
- waiter.is_queued = true;
-
- // Find the wait queue entry associated with the address.
- // If there isn't a wait queue on the address, this waiter creates the queue.
- var entry = treap.getEntryFor(address);
- const entry_node = entry.node orelse {
- waiter.prev = null;
- waiter.tail = waiter;
- entry.set(&waiter.node);
- return;
- };
-
- // There's a wait queue on the address; get the queue head and tail.
- const head: *Waiter = @fieldParentPtr("node", entry_node);
- const tail = head.tail orelse unreachable;
-
- // Push the waiter to the tail by replacing it and linking to the previous tail.
- head.tail = waiter;
- tail.next = waiter;
- waiter.prev = tail;
- }
-
- fn remove(treap: *Treap, address: usize, max_waiters: usize) WaitList {
- // Find the wait queue associated with this address and get the head/tail if any.
- var entry = treap.getEntryFor(address);
- var queue_head: ?*Waiter = if (entry.node) |node| @fieldParentPtr("node", node) else null;
- const queue_tail = if (queue_head) |head| head.tail else null;
-
- // Once we're done updating the head, fix it's tail pointer and update the treap's queue head as well.
- defer entry.set(blk: {
- const new_head = queue_head orelse break :blk null;
- new_head.tail = queue_tail;
- break :blk &new_head.node;
- });
-
- var removed = WaitList{};
- while (removed.len < max_waiters) {
- // dequeue and collect waiters from their wait queue.
- const waiter = queue_head orelse break;
- queue_head = waiter.next;
- removed.push(waiter);
-
- // When dequeueing, we must mark is_queued as false.
- // This ensures that a waiter which calls tryRemove() returns false.
- assert(waiter.is_queued);
- waiter.is_queued = false;
- }
-
- return removed;
- }
-
- fn tryRemove(treap: *Treap, address: usize, waiter: *Waiter) bool {
- if (!waiter.is_queued) {
- return false;
- }
-
- queue_remove: {
- // Find the wait queue associated with the address.
- var entry = blk: {
- // A waiter without a previous link means it's the queue head that's in the treap so we can avoid lookup.
- if (waiter.prev == null) {
- assert(waiter.node.key == address);
- break :blk treap.getEntryForExisting(&waiter.node);
- }
- break :blk treap.getEntryFor(address);
- };
-
- // The queue head and tail must exist if we're removing a queued waiter.
- const head: *Waiter = @fieldParentPtr("node", entry.node orelse unreachable);
- const tail = head.tail orelse unreachable;
-
- // A waiter with a previous link is never the head of the queue.
- if (waiter.prev) |prev| {
- assert(waiter != head);
- prev.next = waiter.next;
-
- // A waiter with both a previous and next link is in the middle.
- // We only need to update the surrounding waiter's links to remove it.
- if (waiter.next) |next| {
- assert(waiter != tail);
- next.prev = waiter.prev;
- break :queue_remove;
- }
-
- // A waiter with a previous but no next link means it's the tail of the queue.
- // In that case, we need to update the head's tail reference.
- assert(waiter == tail);
- head.tail = waiter.prev;
- break :queue_remove;
- }
-
- // A waiter with no previous link means it's the queue head of queue.
- // We must replace (or remove) the head waiter reference in the treap.
- assert(waiter == head);
- entry.set(blk: {
- const new_head = waiter.next orelse break :blk null;
- new_head.tail = head.tail;
- break :blk &new_head.node;
- });
- }
-
- // Mark the waiter as successfully removed.
- waiter.is_queued = false;
- return true;
- }
- };
-
- const Bucket = struct {
- mutex: c.pthread_mutex_t align(atomic.cache_line) = .{},
- pending: atomic.Value(usize) = atomic.Value(usize).init(0),
- treap: Treap = .{},
-
- // Global array of buckets that addresses map to.
- // Bucket array size is pretty much arbitrary here, but it must be a power of two for fibonacci hashing.
- var buckets = [_]Bucket{.{}} ** @bitSizeOf(usize);
-
- // https://github.com/Amanieu/parking_lot/blob/1cf12744d097233316afa6c8b7d37389e4211756/core/src/parking_lot.rs#L343-L353
- fn from(address: usize) *Bucket {
- // The upper `@bitSizeOf(usize)` bits of the fibonacci golden ratio.
- // Hashing this via (h * k) >> (64 - b) where k=golden-ration and b=bitsize-of-array
- // evenly lays out h=hash values over the bit range even when the hash has poor entropy (identity-hash for pointers).
- const max_multiplier_bits = @bitSizeOf(usize);
- const fibonacci_multiplier = 0x9E3779B97F4A7C15 >> (64 - max_multiplier_bits);
-
- const max_bucket_bits = @ctz(buckets.len);
- comptime assert(std.math.isPowerOfTwo(buckets.len));
-
- const index = (address *% fibonacci_multiplier) >> (max_multiplier_bits - max_bucket_bits);
- return &buckets[index];
- }
- };
-
- const Address = struct {
- fn from(ptr: *const u32) usize {
- // Get the alignment of the pointer.
- const alignment = @alignOf(atomic.Value(u32));
- comptime assert(std.math.isPowerOfTwo(alignment));
-
- // Make sure the pointer is aligned,
- // then cut off the zero bits from the alignment to get the unique address.
- const addr = @intFromPtr(ptr);
- assert(addr & (alignment - 1) == 0);
- return addr >> @ctz(@as(usize, alignment));
- }
- };
-
- fn wait(ptr: *const u32, expect: u32, timeout: ?u64) error{Timeout}!void {
- const address = Address.from(ptr);
- const bucket = Bucket.from(address);
-
- // Announce that there's a waiter in the bucket before checking the ptr/expect condition.
- // If the announcement is reordered after the ptr check, the waiter could deadlock:
- //
- // - T1: checks ptr == expect which is true
- // - T2: updates ptr to != expect
- // - T2: does Futex.wake(), sees no pending waiters, exits
- // - T1: bumps pending waiters (was reordered after the ptr == expect check)
- // - T1: goes to sleep and misses both the ptr change and T2's wake up
- //
- // acquire barrier to ensure the announcement happens before the ptr check below.
- var pending = bucket.pending.fetchAdd(1, .acquire);
- assert(pending < std.math.maxInt(usize));
-
- // If the wait gets canceled, remove the pending count we previously added.
- // This is done outside the mutex lock to keep the critical section short in case of contention.
- var canceled = false;
- defer if (canceled) {
- pending = bucket.pending.fetchSub(1, .monotonic);
- assert(pending > 0);
- };
-
- var waiter: Waiter = undefined;
- {
- assert(c.pthread_mutex_lock(&bucket.mutex) == .SUCCESS);
- defer assert(c.pthread_mutex_unlock(&bucket.mutex) == .SUCCESS);
-
- canceled = @atomicLoad(u32, ptr, .monotonic) != expect;
- if (canceled) {
- return;
- }
-
- waiter.event.init();
- WaitQueue.insert(&bucket.treap, address, &waiter);
- }
-
- defer {
- assert(!waiter.is_queued);
- waiter.event.deinit();
- }
-
- waiter.event.wait(timeout) catch {
- // If we fail to cancel after a timeout, it means a wake() thread
- // dequeued us and will wake us up. We must wait until the event is
- // set as that's a signal that the wake() thread won't access the
- // waiter memory anymore. If we return early without waiting, the
- // waiter on the stack would be invalidated and the wake() thread
- // risks a UAF.
- defer if (!canceled) waiter.event.wait(null) catch unreachable;
-
- assert(c.pthread_mutex_lock(&bucket.mutex) == .SUCCESS);
- defer assert(c.pthread_mutex_unlock(&bucket.mutex) == .SUCCESS);
-
- canceled = WaitQueue.tryRemove(&bucket.treap, address, &waiter);
- if (canceled) {
- return error.Timeout;
- }
- };
- }
-
- fn wake(ptr: *const u32, max_waiters: u32) void {
- const address = Address.from(ptr);
- const bucket = Bucket.from(address);
-
- // Quick check if there's even anything to wake up.
- // The change to the ptr's value must happen before we check for pending waiters.
- // If not, the wake() thread could miss a sleeping waiter and have it deadlock:
- //
- // - T2: p = has pending waiters (reordered before the ptr update)
- // - T1: bump pending waiters
- // - T1: if ptr == expected: sleep()
- // - T2: update ptr != expected
- // - T2: p is false from earlier so doesn't wake (T1 missed ptr update and T2 missed T1 sleeping)
- //
- // What we really want here is a Release load, but that doesn't exist under the C11 memory model.
- // We could instead do `bucket.pending.fetchAdd(0, Release) == 0` which achieves effectively the same thing,
- // LLVM lowers the fetchAdd(0, .release) into an mfence+load which avoids gaining ownership of the cache-line.
- if (bucket.pending.fetchAdd(0, .release) == 0) {
- return;
- }
-
- // Keep a list of all the waiters notified and wake then up outside the mutex critical section.
- var notified = WaitList{};
- defer if (notified.len > 0) {
- const pending = bucket.pending.fetchSub(notified.len, .monotonic);
- assert(pending >= notified.len);
-
- while (notified.pop()) |waiter| {
- assert(!waiter.is_queued);
- waiter.event.set();
- }
- };
-
- assert(c.pthread_mutex_lock(&bucket.mutex) == .SUCCESS);
- defer assert(c.pthread_mutex_unlock(&bucket.mutex) == .SUCCESS);
-
- // Another pending check again to avoid the WaitQueue lookup if not necessary.
- if (bucket.pending.load(.monotonic) > 0) {
- notified = WaitQueue.remove(&bucket.treap, address, max_waiters);
- }
- }
-};
-
fn scanEnviron(t: *Threaded) void {
t.mutex.lock();
defer t.mutex.unlock();
@@ -12328,3 +12695,459 @@ fn scanEnviron(t: *Threaded) void {
test {
_ = @import("Threaded/test.zig");
}
+
+const use_parking_futex = switch (builtin.target.os.tag) {
+ .windows => true, // RtlWaitOnAddress is a userland implementation anyway
+ .netbsd => true, // NetBSD has `futex(2)`, but it's historically been quite buggy. TODO: evaluate whether it's okay to use now.
+ .illumos => true, // Illumos has no futex mechanism
+ else => false,
+};
+const use_parking_sleep = switch (builtin.target.os.tag) {
+ // On Windows, we can implement sleep either with `NtDelayExecution` (which is how `SleepEx` in
+ // kernel32 works) or `NtWaitForAlertByThreadId` (thread parking). We're already using the
+ // latter for futex, so we may as well use it for sleeping too, to maximise code reuse. I'm
+ // also more confident that it will always correctly handle the cancelation race (so "unpark"
+ // before "park" causes "park" to return immediately): it *seems* like alertable sleeps paired
+ // with `NtAlertThread` do actually do this too, but there could be some caveat (e.g. it might
+ // fail under some specific condition), whereas `NtWaitForAlertByThreadId` must reliably trigger
+ // this behavior because `RtlWaitOnAddress` relies on it.
+ .windows => true,
+
+ // These targets have `_lwp_park`, which is superior to POSIX nanosleep because it has a better
+ // cancelation mechanism.
+ .netbsd,
+ .illumos,
+ => true,
+
+ else => false,
+};
+
+const parking_futex = struct {
+ comptime {
+ assert(use_parking_futex);
+ }
+
+ const Bucket = struct {
+ /// Used as a fast check for `wake` to avoid having to acquire `mutex` to discover there are no
+ /// waiters. It is important for `wait` to increment this *before* checking the futex value to
+ /// avoid a race.
+ num_waiters: std.atomic.Value(u32),
+ /// Protects `waiters`.
+ mutex: std.Thread.Mutex,
+ waiters: std.DoublyLinkedList,
+
+ /// Prevent false sharing between buckets.
+ _: void align(std.atomic.cache_line) = {},
+
+ const init: Bucket = .{ .num_waiters = .init(0), .mutex = .{}, .waiters = .{} };
+ };
+
+ const Waiter = struct {
+ node: std.DoublyLinkedList.Node,
+ address: usize,
+ tid: std.Thread.Id,
+ /// `thread_status.cancelation` is `.parked` while the thread is waiting. The single thread
+ /// which atomically updates it (to `.none` or `.canceling`) is responsible for:
+ ///
+ /// * Removing the `Waiter` from `Bucket.waiters`
+ /// * Decrementing `Bucket.num_waiters`
+ /// * Unparking the thread (*after* the above, so that the `Waiter` does not go out of scope
+ /// while it is still in the `Bucket`).
+ thread_status: *std.atomic.Value(Thread.Status),
+ };
+
+ fn bucketForAddress(address: usize) *Bucket {
+ const global = struct {
+ /// Length must be a power of two. The longer this array, the less likely contention is
+ /// between different futexes. This length seems like it'll provide a reasonable balance
+ /// between contention and memory usage: assuming a 128-byte `Bucket` (due to cache line
+ /// alignment), this uses 32 KiB of memory.
+ var buckets: [256]Bucket = @splat(.init);
+ };
+
+ // Here we use Fibonacci hashing: the golden ratio can be used to evenly redistribute input
+ // values across a range, giving a poor, but extremely quick to compute, hash.
+
+ // This literal is the rounded value of '2^64 / phi' (where 'phi' is the golden ratio). The
+ // shift then converts it to '2^b / phi', where 'b' is the pointer bit width.
+ const fibonacci_multiplier = 0x9E3779B97F4A7C15 >> (64 - @bitSizeOf(usize));
+ const hashed = address *% fibonacci_multiplier;
+
+ comptime assert(std.math.isPowerOfTwo(global.buckets.len));
+ // The high bits of `hashed` have better entropy than the low bits.
+ const index = hashed >> (@bitSizeOf(usize) - @ctz(global.buckets.len));
+
+ return &global.buckets[index];
+ }
+
+ fn wait(ptr: *const u32, expect: u32, uncancelable: bool, timeout: Io.Timeout) Io.Cancelable!void {
+ const bucket = bucketForAddress(@intFromPtr(ptr));
+
+ // Put the threadlocal access outside of the critical section.
+ const opt_thread = Thread.current;
+ const self_tid = if (opt_thread) |thread| thread.id else std.Thread.getCurrentId();
+
+ var waiter: Waiter = .{
+ .node = undefined, // populated by list append
+ .address = @intFromPtr(ptr),
+ .tid = self_tid,
+ .thread_status = undefined, // populated in critical section
+ };
+
+ var status_buf: std.atomic.Value(Thread.Status) = undefined;
+
+ {
+ bucket.mutex.lock();
+ defer bucket.mutex.unlock();
+
+ _ = bucket.num_waiters.fetchAdd(1, .acquire);
+
+ if (@atomicLoad(u32, ptr, .monotonic) != expect) {
+ assert(bucket.num_waiters.fetchSub(1, .monotonic) > 0);
+ return;
+ }
+
+ // This is in the critical section to avoid marking the thread as parked until we're
+ // certain that we're actually going to park.
+ waiter.thread_status = status: {
+ cancelable: {
+ if (uncancelable) break :cancelable;
+ const thread = opt_thread orelse break :cancelable;
+ switch (thread.cancel_protection) {
+ .blocked => break :cancelable,
+ .unblocked => {},
+ }
+ thread.futex_waiter = &waiter;
+ const old_status = thread.status.fetchOr(
+ .{ .cancelation = @enumFromInt(0b001), .awaitable = .null },
+ .release, // release `thread.futex_waiter`
+ );
+ switch (old_status.cancelation) {
+ .none => {}, // status is now `.parked`
+ .canceling => {
+ // status is now `.canceled`
+ assert(bucket.num_waiters.fetchSub(1, .monotonic) > 0);
+ return error.Canceled;
+ },
+ .canceled => break :cancelable, // status is still `.canceled`
+ .parked => unreachable,
+ .blocked => unreachable,
+ .blocked_windows_dns => unreachable,
+ .blocked_canceling => unreachable,
+ }
+ // We could now be unparked for a cancelation at any time!
+ break :status &thread.status;
+ }
+ // This is an uncancelable wait, so just use `status_buf`. Note that the value of
+ // `status_buf.awaitable` is irrelevant because this is only visible to futex code,
+ // while only cancelation cares about `awaitable`.
+ status_buf.raw = .{ .cancelation = .parked, .awaitable = .null };
+ break :status &status_buf;
+ };
+
+ bucket.waiters.append(&waiter.node);
+ }
+
+ if (park(timeout, ptr)) {
+ // We were unparked by either `wake` or cancelation, so our current status is either
+ // `.none` or `.canceling`. In either case, they've already removed `waiter` from
+ // `bucket`, so we have nothing more to do!
+ } else |err| switch (err) {
+ error.Timeout => {
+ // We're not out of the woods yet: an unpark could race with the timeout.
+ const old_status = waiter.thread_status.fetchAnd(
+ .{ .cancelation = @enumFromInt(0b110), .awaitable = .all_ones },
+ .monotonic,
+ );
+ switch (old_status.cancelation) {
+ .parked => {
+ // No race. It is our responsibility to remove `waiter` from `bucket`.
+ // New status is `.none`.
+ bucket.mutex.lock();
+ defer bucket.mutex.unlock();
+ bucket.waiters.remove(&waiter.node);
+ assert(bucket.num_waiters.fetchSub(1, .monotonic) > 0);
+ },
+ .none, .canceling => {
+ // Race condition: the timeout was reached, then `wake` or a canceler tried
+ // to unpark us. Whoever did that will remove us from `bucket`. Wait for
+ // that (and drop the unpark request in doing so).
+ // New status is `.none` or `.canceling` respectively.
+ park(.none, ptr) catch |e| switch (e) {
+ error.Timeout => unreachable,
+ };
+ },
+ .canceled => unreachable,
+ .blocked => unreachable,
+ .blocked_windows_dns => unreachable,
+ .blocked_canceling => unreachable,
+ }
+ },
+ }
+ }
+
+ fn wake(ptr: *const u32, max_waiters: u32) void {
+ if (max_waiters == 0) return;
+
+ const bucket = bucketForAddress(@intFromPtr(ptr));
+
+ // To ensure the store to `ptr` is ordered before this check, we effectively want a `.release`
+ // load, but that doesn't exist in the C11 memory model, so emulate it with a non-mutating rmw.
+ if (bucket.num_waiters.fetchAdd(0, .release) == 0) {
+ @branchHint(.likely);
+ return; // no waiters
+ }
+
+ // Waiters removed from the linked list under the mutex so we can unpark their threads outside
+ // of the critical section. This forms a singly-linked list of waiters using `Waiter.node.next`.
+ var waking_head: ?*std.DoublyLinkedList.Node = null;
+ {
+ bucket.mutex.lock();
+ defer bucket.mutex.unlock();
+
+ var num_removed: u32 = 0;
+ var it = bucket.waiters.first;
+ while (num_removed < max_waiters) {
+ const waiter: *Waiter = @fieldParentPtr("node", it orelse break);
+ it = waiter.node.next;
+ if (waiter.address != @intFromPtr(ptr)) continue;
+ const old_status = waiter.thread_status.fetchAnd(
+ .{ .cancelation = @enumFromInt(0b110), .awaitable = .all_ones },
+ .monotonic,
+ );
+ switch (old_status.cancelation) {
+ .parked => {}, // state updated to `.none`
+ .none => unreachable, // if another `wake` call is unparking this thread, it should have removed it from the list
+ .canceling => continue, // race with a canceler who hasn't called `removeCanceledWaiter` yet
+ .canceled => unreachable,
+ .blocked => unreachable,
+ .blocked_windows_dns => unreachable,
+ .blocked_canceling => unreachable,
+ }
+ // We're waking this waiter. Remove them from the bucket and add them to our local list.
+ bucket.waiters.remove(&waiter.node);
+ waiter.node.next = waking_head;
+ waking_head = &waiter.node;
+ num_removed += 1;
+ // Signal to `waiter` that they're about to be unparked, in case we're racing with their
+ // timeout. See corresponding logic in `wake`.
+ waiter.address = 0;
+ }
+
+ _ = bucket.num_waiters.fetchSub(num_removed, .monotonic);
+ }
+
+ var unpark_buf: [128]UnparkTid = undefined;
+ var unpark_len: usize = 0;
+
+ // Finally, unpark the threads.
+ while (waking_head) |node| {
+ waking_head = node.next;
+ const waiter: *Waiter = @fieldParentPtr("node", node);
+ unpark_buf[unpark_len] = waiter.tid;
+ unpark_len += 1;
+ if (unpark_len == unpark_buf.len) {
+ unpark(&unpark_buf, ptr);
+ unpark_len = 0;
+ }
+ }
+ if (unpark_len > 0) {
+ unpark(unpark_buf[0..unpark_len], ptr);
+ }
+ }
+
+ fn removeCanceledWaiter(waiter: *Waiter) void {
+ const bucket = bucketForAddress(waiter.address);
+ bucket.mutex.lock();
+ defer bucket.mutex.unlock();
+ bucket.waiters.remove(&waiter.node);
+ assert(bucket.num_waiters.fetchSub(1, .monotonic) > 0);
+ }
+};
+const parking_sleep = struct {
+ comptime {
+ assert(use_parking_sleep);
+ }
+ fn sleep(timeout: Io.Timeout) Io.Cancelable!void {
+ const opt_thread = Thread.current;
+ cancelable: {
+ const thread = opt_thread orelse break :cancelable;
+ switch (thread.cancel_protection) {
+ .blocked => break :cancelable,
+ .unblocked => {},
+ }
+ thread.futex_waiter = null;
+ {
+ const old_status = thread.status.fetchOr(
+ .{ .cancelation = @enumFromInt(0b001), .awaitable = .null },
+ .release, // release `thread.futex_waiter`
+ );
+ switch (old_status.cancelation) {
+ .none => {}, // status is now `.parked`
+ .canceling => return error.Canceled, // status is now `.canceled`
+ .canceled => break :cancelable, // status is still `.canceled`
+ .parked => unreachable,
+ .blocked => unreachable,
+ .blocked_windows_dns => unreachable,
+ .blocked_canceling => unreachable,
+ }
+ }
+ if (park(timeout, null)) {
+ // The only reason this could possibly happen is cancelation.
+ const old_status = thread.status.load(.monotonic);
+ assert(old_status.cancelation == .canceling);
+ thread.status.store(
+ .{ .cancelation = .canceled, .awaitable = old_status.awaitable },
+ .monotonic,
+ );
+ return error.Canceled;
+ } else |err| switch (err) {
+ error.Timeout => {
+ // We're not out of the woods yet: an unpark could race with the timeout.
+ const old_status = thread.status.fetchAnd(
+ .{ .cancelation = @enumFromInt(0b110), .awaitable = .all_ones },
+ .monotonic,
+ );
+ switch (old_status.cancelation) {
+ .parked => return, // No race; new status is `.none`
+ .canceling => {
+ // Race condition: the timeout was reached, then someone tried to unpark
+ // us for a cancelation. Whoever did that will have called `unpark`, so
+ // drop that unpark request by waiting for it.
+ // Status is still `.canceling`.
+ park(.none, null) catch |e| switch (e) {
+ error.Timeout => unreachable,
+ };
+ return;
+ },
+ .none => unreachable,
+ .canceled => unreachable,
+ .blocked => unreachable,
+ .blocked_windows_dns => unreachable,
+ .blocked_canceling => unreachable,
+ }
+ },
+ }
+ }
+ // Uncancelable sleep; we expect not to be manually unparked.
+ if (park(timeout, null)) {
+ unreachable; // unexpected unpark
+ } else |err| switch (err) {
+ error.Timeout => return,
+ }
+ }
+};
+
+/// `addr_hint` has no semantic effect, but may allow the OS to optimize this operation.
+fn park(timeout: Io.Timeout, addr_hint: ?*const anyopaque) error{Timeout}!void {
+ comptime assert(use_parking_futex or use_parking_sleep);
+ switch (builtin.target.os.tag) {
+ .windows => {
+ var timeout_buf: windows.LARGE_INTEGER = undefined;
+ const raw_timeout: ?*windows.LARGE_INTEGER = timeout: switch (timeout) {
+ .none => null,
+ .deadline => |timestamp| continue :timeout .{ .duration = .{
+ .clock = timestamp.clock,
+ .raw = (nowWindows(timestamp.clock) catch unreachable).durationTo(timestamp.raw),
+ } },
+ .duration => |duration| {
+ _ = duration.clock; // Windows only supports monotonic
+ timeout_buf = @intCast(@divTrunc(-duration.raw.nanoseconds, 100));
+ break :timeout &timeout_buf;
+ },
+ };
+ // `RtlWaitOnAddress` passes the futex address in as the first argument to this call,
+ // but it's unclear what that actually does, especially since `NtAlertThreadByThreadId`
+ // does *not* accept the address so the kernel can't really be using it as a hint. An
+ // old Microsoft blog post discusses a more traditional futex-like mechanism in the
+ // kernel which definitely isn't how `RtlWaitOnAddress` works today:
+ //
+ // https://devblogs.microsoft.com/oldnewthing/20160826-00/?p=94185
+ //
+ // ...so it's possible this argument is simply a remnant which no longer does anything
+ // (perhaps the implementation changed during development but someone forgot to remove
+ // this parameter). However, to err on the side of caution, let's match the behavior of
+ // `RtlWaitOnAddress` and pass the pointer, in case the kernel ever does something
+ // stupid such as trying to dereference it.
+ switch (windows.ntdll.NtWaitForAlertByThreadId(addr_hint, raw_timeout)) {
+ .ALERTED => return,
+ .TIMEOUT => return error.Timeout,
+ else => unreachable,
+ }
+ },
+ .netbsd => {
+ var ts_buf: posix.timespec = undefined;
+ const ts: ?*posix.timespec, const abstime: bool, const clock_real: bool = switch (timeout) {
+ .none => .{ null, false, false },
+ .deadline => |timestamp| timeout: {
+ ts_buf = timestampToPosix(timestamp.raw.nanoseconds);
+ break :timeout .{ &ts_buf, true, timestamp.clock == .real };
+ },
+ .duration => |duration| timeout: {
+ ts_buf = timestampToPosix(duration.raw.nanoseconds);
+ break :timeout .{ &ts_buf, false, duration.clock == .real };
+ },
+ };
+ switch (posix.errno(std.c._lwp_park(
+ if (clock_real) .REALTIME else .MONOTONIC,
+ .{ .ABSTIME = abstime },
+ ts,
+ 0,
+ addr_hint,
+ null,
+ ))) {
+ .SUCCESS, .ALREADY, .INTR => return,
+ .TIMEDOUT => return error.Timeout,
+ .INVAL => unreachable,
+ .SRCH => unreachable,
+ else => unreachable,
+ }
+ },
+ .illumos => @panic("TODO: illumos lwp_park"),
+ else => comptime unreachable,
+ }
+}
+
+const UnparkTid = switch (builtin.target.os.tag) {
+ // `NtAlertMultipleThreadByThreadId` is weird and wants 64-bit thread handles?
+ .windows => usize,
+ else => std.Thread.Id,
+};
+/// `addr_hint` has no semantic effect, but may allow the OS to optimize this operation.
+fn unpark(tids: []const UnparkTid, addr_hint: ?*const anyopaque) void {
+ comptime assert(use_parking_futex or use_parking_sleep);
+ switch (builtin.target.os.tag) {
+ .windows => {
+ // TODO: this condition is currently disabled because mingw-w64 does not contain this
+ // symbol. Once it's added, enable this check to use the new bulk API where possible.
+ if (false and (builtin.os.version_range.windows.isAtLeast(.win11_dt) orelse false)) {
+ _ = windows.ntdll.NtAlertMultipleThreadByThreadId(tids.ptr, @intCast(tids.len), null, null);
+ } else {
+ for (tids) |tid| {
+ _ = windows.ntdll.NtAlertThreadByThreadId(@intCast(tid));
+ }
+ }
+ },
+ .netbsd => {
+ switch (posix.errno(std.c._lwp_unpark_all(@ptrCast(tids.ptr), tids.len, addr_hint))) {
+ .SUCCESS => return,
+ // For errors, fall through to a loop over `tids`, though this is only expected to
+ // be possible for ENOMEM (and even that is questionable).
+ .SRCH => recoverableOsBugDetected(),
+ .FAULT => recoverableOsBugDetected(),
+ .INVAL => recoverableOsBugDetected(),
+ .NOMEM => {},
+ else => recoverableOsBugDetected(),
+ }
+ for (tids) |tid| {
+ switch (posix.errno(std.c._lwp_unpark(@bitCast(tid), addr_hint))) {
+ .SUCCESS => {},
+ .SRCH => recoverableOsBugDetected(),
+ else => recoverableOsBugDetected(),
+ }
+ }
+ },
+ .illumos => @panic("TODO: illumos lwp_unpark"),
+ else => comptime unreachable,
+ }
+}
diff --git a/lib/std/Io/Threaded/test.zig b/lib/std/Io/Threaded/test.zig
@@ -124,7 +124,7 @@ test "Group.async context alignment" {
var group: std.Io.Group = .init;
var result: ByteArray512 = undefined;
group.async(io, concatByteArraysResultPtr, .{ a, b, &result });
- group.awaitUncancelable(io);
+ try group.await(io);
try std.testing.expectEqualSlices(u8, &expected.x, &result.x);
}
@@ -141,3 +141,48 @@ test "async with array return type" {
const result = future.await(io);
try std.testing.expectEqualSlices(u8, &@as([32]u8, @splat(5)), &result);
}
+
+test "cancel blocked read from pipe" {
+ const global = struct {
+ fn readFromPipe(io: Io, pipe: Io.File) !void {
+ var buf: [1]u8 = undefined;
+ if (pipe.readStreaming(io, &.{&buf})) |_| {
+ return error.UnexpectedData;
+ } else |err| switch (err) {
+ error.Canceled => return,
+ else => |e| return e,
+ }
+ }
+ };
+
+ var threaded: std.Io.Threaded = .init(std.testing.allocator, .{});
+ defer threaded.deinit();
+ const io = threaded.io();
+
+ var read_end: Io.File = undefined;
+ var write_end: Io.File = undefined;
+ switch (builtin.target.os.tag) {
+ .wasi => return error.SkipZigTest,
+ .windows => try std.os.windows.CreatePipe(&read_end.handle, &write_end.handle, &.{
+ .nLength = @sizeOf(std.os.windows.SECURITY_ATTRIBUTES),
+ .lpSecurityDescriptor = null,
+ .bInheritHandle = std.os.windows.FALSE,
+ }),
+ else => {
+ const pipe = try std.posix.pipe();
+ read_end = .{ .handle = pipe[0] };
+ write_end = .{ .handle = pipe[1] };
+ },
+ }
+ defer {
+ read_end.close(io);
+ write_end.close(io);
+ }
+
+ var future = io.concurrent(global.readFromPipe, .{ io, read_end }) catch |err| switch (err) {
+ error.ConcurrencyUnavailable => return error.SkipZigTest,
+ };
+ defer _ = future.cancel(io) catch {};
+ try io.sleep(.fromMilliseconds(10), .awake);
+ try future.cancel(io);
+}
diff --git a/lib/std/Io/net/HostName.zig b/lib/std/Io/net/HostName.zig
@@ -233,11 +233,12 @@ pub fn connect(
if (result) |stream| {
return stream;
} else |err| switch (err) {
+ error.Canceled => unreachable,
+
error.SystemResources,
error.OptionUnsupported,
error.ProcessFdQuotaExceeded,
error.SystemFdQuotaExceeded,
- error.Canceled,
=> |e| return e,
error.WouldBlock => return error.Unexpected,
@@ -259,6 +260,8 @@ pub fn connect(
/// Asynchronously establishes a connection to all IP addresses associated with
/// a host name, adding them to a results queue upon completion.
///
+/// `error.Canceled` will never be added to the queue, but other errors may be.
+///
/// Closes `results` before return, even on error.
///
/// Asserts `results` is not closed until this call returns.
@@ -299,22 +302,15 @@ fn enqueueConnection(
io: Io,
queue: *Io.Queue(IpAddress.ConnectError!Stream),
options: IpAddress.ConnectOptions,
-) void {
- enqueueConnectionFallible(address, io, queue, options) catch |err| switch (err) {
- error.Canceled => {},
- };
-}
-fn enqueueConnectionFallible(
- address: IpAddress,
- io: Io,
- queue: *Io.Queue(IpAddress.ConnectError!Stream),
- options: IpAddress.ConnectOptions,
) Io.Cancelable!void {
- const result = address.connect(io, options);
+ const result = address.connect(io, options) catch |err| switch (err) {
+ error.Canceled => |e| return e,
+ else => |e| e, // other errors go in the result queue
+ };
errdefer if (result) |s| s.close(io) else |_| {};
queue.putOne(io, result) catch |err| switch (err) {
- error.Closed => unreachable, // `queue` must not be closed
error.Canceled => |e| return e,
+ error.Closed => unreachable, // `queue` must not be closed
};
}
diff --git a/lib/std/Io/test.zig b/lib/std/Io/test.zig
@@ -194,7 +194,7 @@ test "Group" {
group.async(io, count, .{ 1, 10, &results[0] });
group.async(io, count, .{ 20, 30, &results[1] });
- group.awaitUncancelable(io);
+ try group.await(io);
try testing.expectEqualSlices(usize, &.{ 45, 245 }, &results);
}
@@ -207,49 +207,53 @@ fn count(a: usize, b: usize, result: *usize) void {
result.* = sum;
}
-test "Group cancelation" {
- const io = testing.io;
+test "Group.cancel" {
+ const global = struct {
+ fn sleep(io: Io, result: *usize) Io.Cancelable!void {
+ defer result.* = 1;
+ io.sleep(.fromSeconds(100_000), .awake) catch |err| switch (err) {
+ error.Canceled => |e| return e,
+ else => {},
+ };
+ }
- var group: Io.Group = .init;
- var results: [4]usize = .{ 0, 0, 0, 0 };
+ fn sleepRecancel(io: Io, result: *usize) void {
+ io.sleep(.fromSeconds(100_000), .awake) catch |err| switch (err) {
+ error.Canceled => io.recancel(),
+ else => {},
+ };
+ result.* = 1;
+ }
- // TODO when robust cancelation is available, make the sleep timeouts much
- // longer so that it causes the unit test to be failed if not canceled.
- // https://codeberg.org/ziglang/zig/issues/30049
- group.async(io, sleep, .{ io, &results[0] });
- group.async(io, sleep, .{ io, &results[1] });
- group.async(io, sleepUncancelable, .{ io, &results[2] });
- group.async(io, sleepRecancel, .{ io, &results[3] });
+ fn sleepUncancelable(io: Io, result: *usize) void {
+ const old_prot = io.swapCancelProtection(.blocked);
+ defer _ = io.swapCancelProtection(old_prot);
+ // Short sleep interval, because this one won't be canceled (that's the point!).
+ io.sleep(.fromMilliseconds(50), .awake) catch {};
+ result.* = 1;
+ }
+ };
- group.cancel(io);
+ const io = testing.io;
- try testing.expectEqualSlices(usize, &.{ 1, 1, 1, 1 }, &results);
-}
+ var group: Io.Group = .init;
+ var results: [5]usize = @splat(0);
-fn sleep(io: Io, result: *usize) error{Canceled}!void {
- defer result.* = 1;
- io.sleep(.fromMilliseconds(1), .awake) catch |err| switch (err) {
- error.Canceled => |e| return e,
- else => {},
+ group.concurrent(io, global.sleep, .{ io, &results[0] }) catch |err| switch (err) {
+ error.ConcurrencyUnavailable => return error.SkipZigTest,
};
-}
+ try group.concurrent(io, global.sleep, .{ io, &results[1] });
+ try group.concurrent(io, global.sleepRecancel, .{ io, &results[2] });
+ try group.concurrent(io, global.sleepUncancelable, .{ io, &results[3] });
+ // Because this one doesn't block until canceled, it is safe to run asynchronously.
+ group.async(io, global.sleepUncancelable, .{ io, &results[4] });
-fn sleepUncancelable(io: Io, result: *usize) void {
- const old_prot = io.swapCancelProtection(.blocked);
- defer _ = io.swapCancelProtection(old_prot);
- io.sleep(.fromMilliseconds(1), .awake) catch {};
- result.* = 1;
-}
+ group.cancel(io);
-fn sleepRecancel(io: Io, result: *usize) void {
- io.sleep(.fromMilliseconds(1), .awake) catch |err| switch (err) {
- error.Canceled => io.recancel(),
- else => {},
- };
- result.* = 1;
+ try testing.expectEqualSlices(usize, &.{ 1, 1, 1, 1, 1 }, &results);
}
-test "Group concurrent" {
+test "Group.concurrent" {
const io = testing.io;
var group: Io.Group = .init;
@@ -488,3 +492,75 @@ test "swapCancelProtection" {
// Because it reached the `set`, it should be too late for `sleepThenSet` to see `error.Canceled`.
try set_future.cancel(io);
}
+
+test "cancel futex wait" {
+ const global = struct {
+ fn blockUntilCanceled(io: Io) void {
+ while (true) io.futexWait(u32, &0, 0) catch |err| switch (err) {
+ error.Canceled => return,
+ };
+ }
+ };
+
+ const io = std.testing.io;
+
+ var future = io.concurrent(global.blockUntilCanceled, .{io}) catch |err| switch (err) {
+ error.ConcurrencyUnavailable => return error.SkipZigTest,
+ };
+ defer future.cancel(io);
+
+ // Give the task some time to start so that we cancel while it is blocked.
+ try io.sleep(.fromMilliseconds(20), .awake);
+}
+
+test "cancel sleep" {
+ const global = struct {
+ fn blockUntilCanceled(io: Io) void {
+ while (true) io.sleep(.fromSeconds(100_000), .awake) catch |err| switch (err) {
+ error.Canceled => return,
+ error.UnsupportedClock => @panic("unsupported clock"),
+ error.Unexpected => @panic("unexpected"),
+ };
+ }
+ };
+
+ const io = std.testing.io;
+
+ var future = io.concurrent(global.blockUntilCanceled, .{io}) catch |err| switch (err) {
+ error.ConcurrencyUnavailable => return error.SkipZigTest,
+ };
+ defer future.cancel(io);
+
+ // Give the task some time to start so that we cancel while it is blocked.
+ try io.sleep(.fromMilliseconds(20), .awake);
+}
+
+test "tasks spawned in group after Group.cancel are canceled" {
+ const global = struct {
+ fn waitThenSpawn(io: Io, group: *Io.Group) void {
+ _ = io.swapCancelProtection(.blocked);
+ group.concurrent(io, blockUntilCanceled, .{io}) catch {};
+ io.sleep(.fromMilliseconds(10), .awake) catch unreachable;
+ group.concurrent(io, blockUntilCanceled, .{io}) catch {};
+ group.async(io, blockUntilCanceled, .{io});
+ }
+ fn blockUntilCanceled(io: Io) Io.Cancelable!void {
+ while (true) io.sleep(.fromSeconds(100_000), .awake) catch |err| switch (err) {
+ error.Canceled => |e| return e,
+ error.UnsupportedClock => @panic("unsupported clock"),
+ error.Unexpected => @panic("unexpected"),
+ };
+ }
+ };
+
+ const io = std.testing.io;
+
+ var group: Io.Group = .init;
+ defer group.cancel(io);
+
+ group.concurrent(io, global.blockUntilCanceled, .{io}) catch |err| switch (err) {
+ error.ConcurrencyUnavailable => return error.SkipZigTest,
+ };
+ try io.sleep(.fromMilliseconds(10), .awake); // let that first sleep start up
+ try group.concurrent(io, global.waitThenSpawn, .{ io, &group });
+}
diff --git a/lib/std/c.zig b/lib/std/c.zig
@@ -11399,6 +11399,9 @@ pub const vm_region_flavor_t = darwin.vm_region_flavor_t;
pub const _ksiginfo = netbsd._ksiginfo;
pub const _lwp_self = netbsd._lwp_self;
+pub const _lwp_park = netbsd._lwp_park;
+pub const _lwp_unpark = netbsd._lwp_unpark;
+pub const _lwp_unpark_all = netbsd._lwp_unpark_all;
pub const lwpid_t = netbsd.lwpid_t;
pub const lwp_gettid = dragonfly.lwp_gettid;
diff --git a/lib/std/c/netbsd.zig b/lib/std/c/netbsd.zig
@@ -1,17 +1,35 @@
const std = @import("../std.zig");
const clock_t = std.c.clock_t;
+const clockid_t = std.c.clockid_t;
const pid_t = std.c.pid_t;
const pthread_t = std.c.pthread_t;
const sigval_t = std.c.sigval_t;
const uid_t = std.c.uid_t;
+const timespec = std.c.timespec;
pub extern "c" fn ptrace(request: c_int, pid: pid_t, addr: ?*anyopaque, data: c_int) c_int;
pub const lwpid_t = i32;
-pub extern "c" fn _lwp_self() lwpid_t;
pub extern "c" fn pthread_setname_np(thread: pthread_t, name: [*:0]const u8, arg: ?*anyopaque) c_int;
+pub extern "c" fn _lwp_self() lwpid_t;
+
+pub extern "c" fn _lwp_park(
+ clock_id: clockid_t,
+ flags: packed struct(u32) {
+ ABSTIME: bool = false,
+ unused: u31 = 0,
+ },
+ ts: ?*timespec,
+ unpark: lwpid_t,
+ hint: ?*const anyopaque,
+ unpark_hint: ?*const anyopaque,
+) c_int;
+
+pub extern "c" fn _lwp_unpark(lwp: lwpid_t, hint: ?*const anyopaque) c_int;
+pub extern "c" fn _lwp_unpark_all(targets: [*]const lwpid_t, ntargets: usize, hint: ?*const anyopaque) c_int;
+
pub const TCIFLUSH = 1;
pub const TCOFLUSH = 2;
pub const TCIOFLUSH = 3;
diff --git a/lib/std/debug/SelfInfo/Windows.zig b/lib/std/debug/SelfInfo/Windows.zig
@@ -315,8 +315,7 @@ const Module = struct {
);
if (len == 0) return error.MissingDebugInfo;
const name_w = name_buffer[0 .. len + 4 :0];
- // TODO eliminate the reference to Io.Threaded.global_single_threaded here
- const coff_file = Io.Threaded.global_single_threaded.dirOpenFileWtf16(null, name_w, .{}) catch |err| switch (err) {
+ const coff_file = Io.Threaded.dirOpenFileWtf16(null, name_w, .{}) catch |err| switch (err) {
error.Canceled => |e| return e,
error.Unexpected => |e| return e,
error.FileNotFound => return error.MissingDebugInfo,
diff --git a/lib/std/http/test.zig b/lib/std/http/test.zig
@@ -1139,13 +1139,26 @@ fn createTestServer(io: Io, S: type) !*TestServer {
}
const address = try net.IpAddress.parse("127.0.0.1", 0);
- const test_server = try std.testing.allocator.create(TestServer);
+
+ const gpa = std.testing.allocator;
+
+ const test_server = try gpa.create(TestServer);
+ errdefer gpa.destroy(test_server);
+
+ var net_server = try address.listen(io, .{ .reuse_address = true });
+ errdefer net_server.deinit(io);
+
+ // populate `test_server` first so `S.run` can use it
test_server.* = .{
.io = io,
- .net_server = try address.listen(io, .{ .reuse_address = true }),
+ .net_server = net_server,
.shutting_down = false,
- .server_thread = try std.Thread.spawn(.{}, S.run, .{test_server}),
+ .server_thread = undefined, // set below
};
+
+ test_server.server_thread = try .spawn(.{}, S.run, .{test_server});
+ errdefer comptime unreachable;
+
return test_server;
}
diff --git a/lib/std/os/windows.zig b/lib/std/os/windows.zig
@@ -2253,7 +2253,7 @@ pub fn GetProcessHeap() ?*HEAP {
pub const OBJECT_ATTRIBUTES = extern struct {
Length: ULONG,
RootDirectory: ?HANDLE,
- ObjectName: *UNICODE_STRING,
+ ObjectName: ?*UNICODE_STRING,
Attributes: ATTRIBUTES,
SecurityDescriptor: ?*anyopaque,
SecurityQualityOfService: ?*anyopaque,
@@ -2306,6 +2306,7 @@ pub const OpenError = error{
NetworkNotFound,
AntivirusInterference,
BadPathName,
+ OperationCanceled,
};
pub const OpenFileOptions = struct {
@@ -2405,6 +2406,7 @@ pub fn OpenFile(sub_path_w: []const u16, options: OpenFileOptions) OpenError!HAN
continue;
},
.VIRUS_INFECTED, .VIRUS_DELETED => return error.AntivirusInterference,
+ .CANCELLED => return error.OperationCanceled,
else => return unexpectedStatus(rc),
}
}
@@ -2985,6 +2987,7 @@ pub const ReadLinkError = error{
AntivirusInterference,
UnsupportedReparsePointType,
NotLink,
+ OperationCanceled,
};
/// `sub_path_w` will never be accessed after `out_buffer` has been written to, so it
@@ -3015,6 +3018,7 @@ pub fn ReadLink(dir: ?HANDLE, sub_path_w: []const u16, out_buffer: []u16) ReadLi
const rc = DeviceIoControl(result_handle, FSCTL.GET_REPARSE_POINT, .{ .out = reparse_buf[0..] });
switch (rc) {
.SUCCESS => {},
+ .CANCELLED => return error.OperationCanceled,
.NOT_A_REPARSE_POINT => return error.NotLink,
else => return unexpectedStatus(rc),
}
@@ -3339,71 +3343,6 @@ pub fn GetStdHandle(handle_id: DWORD) GetStdHandleError!HANDLE {
return handle;
}
-pub const SetFilePointerError = error{
- Unseekable,
- Unexpected,
-};
-
-/// The SetFilePointerEx function with the `dwMoveMethod` parameter set to `FILE_BEGIN`.
-pub fn SetFilePointerEx_BEGIN(handle: HANDLE, offset: u64) SetFilePointerError!void {
- // "The starting point is zero or the beginning of the file. If [FILE_BEGIN]
- // is specified, then the liDistanceToMove parameter is interpreted as an unsigned value."
- // https://docs.microsoft.com/en-us/windows/desktop/api/fileapi/nf-fileapi-setfilepointerex
- const ipos = @as(LARGE_INTEGER, @bitCast(offset));
- if (kernel32.SetFilePointerEx(handle, ipos, null, FILE_BEGIN) == 0) {
- switch (GetLastError()) {
- .INVALID_FUNCTION => return error.Unseekable,
- .NEGATIVE_SEEK => return error.Unseekable,
- .INVALID_PARAMETER => unreachable,
- .INVALID_HANDLE => unreachable,
- else => |err| return unexpectedError(err),
- }
- }
-}
-
-/// The SetFilePointerEx function with the `dwMoveMethod` parameter set to `FILE_CURRENT`.
-pub fn SetFilePointerEx_CURRENT(handle: HANDLE, offset: i64) SetFilePointerError!void {
- if (kernel32.SetFilePointerEx(handle, offset, null, FILE_CURRENT) == 0) {
- switch (GetLastError()) {
- .INVALID_FUNCTION => return error.Unseekable,
- .NEGATIVE_SEEK => return error.Unseekable,
- .INVALID_PARAMETER => unreachable,
- .INVALID_HANDLE => unreachable,
- else => |err| return unexpectedError(err),
- }
- }
-}
-
-/// The SetFilePointerEx function with the `dwMoveMethod` parameter set to `FILE_END`.
-pub fn SetFilePointerEx_END(handle: HANDLE, offset: i64) SetFilePointerError!void {
- if (kernel32.SetFilePointerEx(handle, offset, null, FILE_END) == 0) {
- switch (GetLastError()) {
- .INVALID_FUNCTION => return error.Unseekable,
- .NEGATIVE_SEEK => return error.Unseekable,
- .INVALID_PARAMETER => unreachable,
- .INVALID_HANDLE => unreachable,
- else => |err| return unexpectedError(err),
- }
- }
-}
-
-/// The SetFilePointerEx function with parameters to get the current offset.
-pub fn SetFilePointerEx_CURRENT_get(handle: HANDLE) SetFilePointerError!u64 {
- var result: LARGE_INTEGER = undefined;
- if (kernel32.SetFilePointerEx(handle, 0, &result, FILE_CURRENT) == 0) {
- switch (GetLastError()) {
- .INVALID_FUNCTION => return error.Unseekable,
- .NEGATIVE_SEEK => return error.Unseekable,
- .INVALID_PARAMETER => unreachable,
- .INVALID_HANDLE => unreachable,
- else => |err| return unexpectedError(err),
- }
- }
- // Based on the docs for FILE_BEGIN, it seems that the returned signed integer
- // should be interpreted as an unsigned integer.
- return @as(u64, @bitCast(result));
-}
-
pub const QueryObjectNameError = error{
AccessDenied,
InvalidHandle,
@@ -3562,6 +3501,7 @@ pub fn GetFinalPathNameByHandle(
error.NetworkNotFound => return error.Unexpected,
error.AntivirusInterference => return error.Unexpected,
error.BadPathName => return error.Unexpected,
+ error.OperationCanceled => @panic("TODO: better integrate cancelation"),
else => |e| return e,
};
defer CloseHandle(mgmt_handle);
diff --git a/lib/std/os/windows/ntdll.zig b/lib/std/os/windows/ntdll.zig
@@ -554,3 +554,30 @@ pub extern "ntdll" fn RtlWakeConditionVariable(
pub extern "ntdll" fn RtlWakeAllConditionVariable(
ConditionVariable: *CONDITION_VARIABLE,
) callconv(.winapi) void;
+
+pub extern "ntdll" fn NtWaitForAlertByThreadId(
+ Address: ?*const anyopaque,
+ Timeout: ?*const LARGE_INTEGER,
+) callconv(.winapi) NTSTATUS;
+pub extern "ntdll" fn NtAlertThreadByThreadId(
+ ThreadId: DWORD,
+) callconv(.winapi) NTSTATUS;
+pub extern "ntdll" fn NtAlertMultipleThreadByThreadId(
+ ThreadIds: [*]const ULONG_PTR,
+ ThreadCount: ULONG,
+ Unknown1: ?*const anyopaque,
+ Unknown2: ?*const anyopaque,
+) callconv(.winapi) NTSTATUS;
+
+pub extern "ntdll" fn NtOpenThread(
+ ThreadHandle: *HANDLE,
+ DesiredAccess: ACCESS_MASK,
+ ObjectAttributes: *const OBJECT_ATTRIBUTES,
+ ClientId: *const windows.CLIENT_ID,
+) callconv(.winapi) NTSTATUS;
+
+pub extern "ntdll" fn NtCancelSynchronousIoFile(
+ ThreadHandle: HANDLE,
+ RequestToCancel: ?*IO_STATUS_BLOCK,
+ IoStatusBlock: *IO_STATUS_BLOCK,
+) callconv(.winapi) NTSTATUS;
diff --git a/lib/std/posix.zig b/lib/std/posix.zig
@@ -1124,6 +1124,7 @@ pub fn mkdirW(dir_path_w: []const u16, mode: mode_t) MakeDirError!void {
error.NoDevice => return error.Unexpected,
error.WouldBlock => return error.Unexpected,
error.AntivirusInterference => return error.Unexpected,
+ error.OperationCanceled => return error.Unexpected,
else => |e| return e,
};
windows.CloseHandle(sub_dir_handle);
diff --git a/lib/std/process/Child.zig b/lib/std/process/Child.zig
@@ -778,6 +778,7 @@ fn spawnWindows(self: *Child, io: Io) SpawnError!void {
error.WouldBlock => return error.Unexpected, // not possible for "NUL"
error.NetworkNotFound => return error.Unexpected, // not possible for "NUL"
error.AntivirusInterference => return error.Unexpected, // not possible for "NUL"
+ error.OperationCanceled => return error.Unexpected, // we're not canceling the operation
else => |e| return e,
}
else
@@ -1129,8 +1130,7 @@ fn windowsCreateProcessPathExt(
defer dir_buf.shrinkRetainingCapacity(dir_path_len);
const dir_path_z = dir_buf.items[0 .. dir_buf.items.len - 1 :0];
const prefixed_path = try windows.wToPrefixedFileW(null, dir_path_z);
- // TODO eliminate this reference
- break :dir Io.Threaded.global_single_threaded.dirOpenDirWindows(.cwd(), prefixed_path.span(), .{
+ break :dir Io.Threaded.dirOpenDirWindows(.cwd(), prefixed_path.span(), .{
.iterate = true,
}) catch return error.FileNotFound;
};
diff --git a/src/codegen/wasm/CodeGen.zig b/src/codegen/wasm/CodeGen.zig
@@ -6973,9 +6973,20 @@ fn airShlSat(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void {
return cg.fail("TODO: Saturating shifting left for integers with bitsize '{d}'", .{int_info.bits});
}
- const lhs = try cg.resolveInst(bin_op.lhs);
- const rhs = try cg.resolveInst(bin_op.rhs);
const wasm_bits = toWasmBits(int_info.bits).?;
+
+ const lhs = try cg.resolveInst(bin_op.lhs);
+ const rhs = rhs: {
+ const rhs = try cg.resolveInst(bin_op.rhs);
+ const rhs_ty = cg.typeOf(bin_op.rhs);
+ // The type of `rhs` is the log2 int of the type of `lhs`, but WASM wants the lhs and rhs types to match.
+ if (toWasmBits(@intCast(rhs_ty.bitSize(zcu))).? == wasm_bits) {
+ break :rhs rhs; // the WASM types match, so no cast necessary
+ }
+ const casted = try cg.intcast(rhs, rhs_ty, ty);
+ break :rhs try casted.toLocal(cg, ty);
+ };
+
const result = try cg.allocLocal(ty);
if (wasm_bits == int_info.bits) {
diff --git a/tools/incr-check.zig b/tools/incr-check.zig
@@ -6,6 +6,27 @@ const Cache = std.Build.Cache;
const usage = "usage: incr-check <zig binary path> <input file> [--zig-lib-dir lib] [--debug-log foo] [--preserve-tmp] [--zig-cc-binary /path/to/zig]";
+pub const std_options: std.Options = .{
+ .logFn = logImpl,
+};
+var log_cur_update: ?struct { *const Case.Target, *const Case.Update } = null;
+fn logImpl(
+ comptime level: std.log.Level,
+ comptime scope: @EnumLiteral(),
+ comptime format: []const u8,
+ args: anytype,
+) void {
+ const target, const update = log_cur_update orelse {
+ return std.log.defaultLog(level, scope, format, args);
+ };
+ std.log.defaultLog(
+ level,
+ scope,
+ "[{s}-{t} '{s}'] " ++ format,
+ .{ target.query, target.backend, update.name } ++ args,
+ );
+}
+
pub fn main() !void {
const fatal = std.process.fatal;
@@ -225,6 +246,9 @@ pub fn main() !void {
std.log.scoped(.status).info("update: '{s}'", .{update.name});
}
+ log_cur_update = .{ &target, &update };
+ defer log_cur_update = null;
+
eval.write(update);
try eval.requestUpdate();
try eval.check(&poller, update, update_node);
@@ -295,9 +319,9 @@ const Eval = struct {
if (stderr.bufferedLen() > 0) {
const stderr_data = try poller.toOwnedSlice(.stderr);
if (eval.allow_stderr) {
- std.log.info("error_bundle included stderr:\n{s}", .{stderr_data});
+ std.log.info("error_bundle stderr:\n{s}", .{stderr_data});
} else {
- eval.fatal("error_bundle included unexpected stderr:\n{s}", .{stderr_data});
+ eval.fatal("error_bundle unexpected stderr:\n{s}", .{stderr_data});
}
}
if (result_error_bundle.errorMessageCount() != 0) {
@@ -312,9 +336,9 @@ const Eval = struct {
if (stderr.bufferedLen() > 0) {
const stderr_data = try poller.toOwnedSlice(.stderr);
if (eval.allow_stderr) {
- std.log.info("emit_digest included stderr:\n{s}", .{stderr_data});
+ std.log.info("emit_digest stderr:\n{s}", .{stderr_data});
} else {
- eval.fatal("emit_digest included unexpected stderr:\n{s}", .{stderr_data});
+ eval.fatal("emit_digest unexpected stderr:\n{s}", .{stderr_data});
}
}
@@ -344,14 +368,14 @@ const Eval = struct {
if (stderr.bufferedLen() > 0) {
if (eval.allow_stderr) {
- std.log.info("update '{s}' included stderr:\n{s}", .{ update.name, stderr.buffered() });
+ std.log.info("stderr:\n{s}", .{stderr.buffered()});
} else {
- eval.fatal("update '{s}' failed:\n{s}", .{ update.name, stderr.buffered() });
+ eval.fatal("unexpected stderr:\n{s}", .{stderr.buffered()});
}
}
waitChild(eval.child, eval);
- eval.fatal("update '{s}': compiler failed to send error_bundle or emit_bin_path", .{update.name});
+ eval.fatal("compiler failed to send error_bundle or emit_bin_path", .{});
}
fn checkErrorOutcome(eval: *Eval, update: Case.Update, error_bundle: std.zig.ErrorBundle) !void {
@@ -361,7 +385,7 @@ const Eval = struct {
.compile_errors => |ce| ce,
.stdout, .exit_code => {
try error_bundle.renderToStderr(io, .{}, .auto);
- eval.fatal("update '{s}': unexpected compile errors", .{update.name});
+ eval.fatal("unexpected compile errors", .{});
},
};
@@ -370,30 +394,29 @@ const Eval = struct {
for (error_bundle.getMessages()) |err_idx| {
if (expected_idx == expected.errors.len) {
try error_bundle.renderToStderr(io, .{}, .auto);
- eval.fatal("update '{s}': more errors than expected", .{update.name});
+ eval.fatal("more errors than expected", .{});
}
- try eval.checkOneError(update, error_bundle, expected.errors[expected_idx], false, err_idx);
+ try eval.checkOneError(error_bundle, expected.errors[expected_idx], false, err_idx);
expected_idx += 1;
for (error_bundle.getNotes(err_idx)) |note_idx| {
if (expected_idx == expected.errors.len) {
try error_bundle.renderToStderr(io, .{}, .auto);
- eval.fatal("update '{s}': more error notes than expected", .{update.name});
+ eval.fatal("more error notes than expected", .{});
}
- try eval.checkOneError(update, error_bundle, expected.errors[expected_idx], true, note_idx);
+ try eval.checkOneError(error_bundle, expected.errors[expected_idx], true, note_idx);
expected_idx += 1;
}
}
if (!std.mem.eql(u8, error_bundle.getCompileLogOutput(), expected.compile_log_output)) {
try error_bundle.renderToStderr(io, .{}, .auto);
- eval.fatal("update '{s}': unexpected compile log output", .{update.name});
+ eval.fatal("unexpected compile log output", .{});
}
}
fn checkOneError(
eval: *Eval,
- update: Case.Update,
eb: std.zig.ErrorBundle,
expected: Case.ExpectedError,
is_note: bool,
@@ -423,7 +446,7 @@ const Eval = struct {
!std.mem.eql(u8, expected.msg, msg))
{
eb.renderToStderr(io, .{}, .auto) catch {};
- eval.fatal("update '{s}': compile error did not match expected error", .{update.name});
+ eval.fatal("compile error did not match expected error", .{});
}
}
@@ -444,7 +467,7 @@ const Eval = struct {
.cbe => bin: {
const rand_int = std.crypto.random.int(u64);
const out_bin_name = "./out_" ++ std.fmt.hex(rand_int);
- try eval.buildCOutput(update, emitted_path, out_bin_name, prog_node);
+ try eval.buildCOutput(emitted_path, out_bin_name, prog_node);
break :bin out_bin_name;
},
};
@@ -521,8 +544,7 @@ const Eval = struct {
if (is_foreign) {
// Chances are the foreign executor isn't available. Skip this evaluation.
if (eval.allow_stderr) {
- std.log.warn("update '{s}': skipping execution of '{s}' via executor for foreign target '{s}': {t}", .{
- update.name,
+ std.log.warn("skipping execution of '{s}' via executor for foreign target '{s}': {t}", .{
binary_path,
try eval.target.resolved.zigTriple(eval.arena),
err,
@@ -530,16 +552,14 @@ const Eval = struct {
}
return;
}
- eval.fatal("update '{s}': failed to run the generated executable '{s}': {t}", .{
- update.name, binary_path, err,
- });
+ eval.fatal("failed to run the generated executable '{s}': {t}", .{ binary_path, err });
};
// Some executors (looking at you, Wine) like throwing some stderr in, just for fun.
// Therefore, we'll ignore stderr when using a foreign executor.
if (!is_foreign and result.stderr.len != 0) {
- std.log.err("update '{s}': generated executable '{s}' had unexpected stderr:\n{s}", .{
- update.name, binary_path, result.stderr,
+ std.log.err("generated executable '{s}' had unexpected stderr:\n{s}", .{
+ binary_path, result.stderr,
});
}
@@ -548,18 +568,14 @@ const Eval = struct {
.unknown, .compile_errors => unreachable,
.stdout => |expected_stdout| {
if (code != 0) {
- eval.fatal("update '{s}': generated executable '{s}' failed with code {d}", .{
- update.name, binary_path, code,
- });
+ eval.fatal("generated executable '{s}' failed with code {d}", .{ binary_path, code });
}
try std.testing.expectEqualStrings(expected_stdout, result.stdout);
},
.exit_code => |expected_code| try std.testing.expectEqual(expected_code, result.term.Exited),
},
.Signal, .Stopped, .Unknown => {
- eval.fatal("update '{s}': generated executable '{s}' terminated unexpectedly", .{
- update.name, binary_path,
- });
+ eval.fatal("generated executable '{s}' terminated unexpectedly", .{binary_path});
},
}
@@ -597,7 +613,7 @@ const Eval = struct {
}
}
- fn buildCOutput(eval: *Eval, update: Case.Update, c_path: []const u8, out_path: []const u8, prog_node: std.Progress.Node) !void {
+ fn buildCOutput(eval: *Eval, c_path: []const u8, out_path: []const u8, prog_node: std.Progress.Node) !void {
std.debug.assert(eval.cc_child_args.items.len > 0);
const child_prog_node = prog_node.start("build cbe output", 0);
@@ -612,28 +628,20 @@ const Eval = struct {
.cwd = eval.tmp_dir_path,
.progress_node = child_prog_node,
}) catch |err| {
- eval.fatal("update '{s}': failed to spawn zig cc for '{s}': {t}", .{ update.name, c_path, err });
+ eval.fatal("failed to spawn zig cc for '{s}': {t}", .{ c_path, err });
};
switch (result.term) {
.Exited => |code| if (code != 0) {
if (result.stderr.len != 0) {
- std.log.err("update '{s}': zig cc stderr:\n{s}", .{
- update.name, result.stderr,
- });
+ std.log.err("zig cc stderr:\n{s}", .{result.stderr});
}
- eval.fatal("update '{s}': zig cc for '{s}' failed with code {d}", .{
- update.name, c_path, code,
- });
+ eval.fatal("zig cc for '{s}' failed with code {d}", .{ c_path, code });
},
.Signal, .Stopped, .Unknown => {
if (result.stderr.len != 0) {
- std.log.err("update '{s}': zig cc stderr:\n{s}", .{
- update.name, result.stderr,
- });
+ std.log.err("zig cc stderr:\n{s}", .{result.stderr});
}
- eval.fatal("update '{s}': zig cc for '{s}' terminated unexpectedly", .{
- update.name, c_path,
- });
+ eval.fatal("zig cc for '{s}' terminated unexpectedly", .{c_path});
},
}
}