mirror of
https://codeberg.org/ziglang/zig.git
synced 2026-03-08 04:04:44 +01:00
Merge pull request 'std.Io: remove select function' (#31223) from remove-select into master
Reviewed-on: https://codeberg.org/ziglang/zig/pulls/31223
This commit is contained in:
commit
c8f54a2f07
7 changed files with 209 additions and 483 deletions
|
|
@ -144,10 +144,6 @@ pub const VTable = struct {
|
|||
swapCancelProtection: *const fn (?*anyopaque, new: CancelProtection) CancelProtection,
|
||||
checkCancel: *const fn (?*anyopaque) Cancelable!void,
|
||||
|
||||
/// Blocks until one of the futures from the list has a result ready, such
|
||||
/// that awaiting it will not block. Returns that index.
|
||||
select: *const fn (?*anyopaque, futures: []const *AnyFuture) Cancelable!usize,
|
||||
|
||||
futexWait: *const fn (?*anyopaque, ptr: *const u32, expected: u32, Timeout) Cancelable!void,
|
||||
futexWaitUncancelable: *const fn (?*anyopaque, ptr: *const u32, expected: u32) void,
|
||||
futexWake: *const fn (?*anyopaque, ptr: *const u32, max_waiters: u32) void,
|
||||
|
|
@ -2120,40 +2116,6 @@ pub fn sleep(io: Io, duration: Duration, clock: Clock) Cancelable!void {
|
|||
} });
|
||||
}
|
||||
|
||||
/// Given a struct with each field a `*Future`, returns a union with the same
|
||||
/// fields, each field type the future's result.
|
||||
pub fn SelectUnion(S: type) type {
|
||||
const struct_fields = @typeInfo(S).@"struct".fields;
|
||||
var names: [struct_fields.len][]const u8 = undefined;
|
||||
var types: [struct_fields.len]type = undefined;
|
||||
for (struct_fields, &names, &types) |struct_field, *union_field_name, *UnionFieldType| {
|
||||
const FieldFuture = @typeInfo(struct_field.type).pointer.child;
|
||||
union_field_name.* = struct_field.name;
|
||||
UnionFieldType.* = @FieldType(FieldFuture, "result");
|
||||
}
|
||||
return @Union(.auto, std.meta.FieldEnum(S), &names, &types, &@splat(.{}));
|
||||
}
|
||||
|
||||
/// `s` is a struct with every field a `*Future(T)`, where `T` can be any type,
|
||||
/// and can be different for each field.
|
||||
pub fn select(io: Io, s: anytype) Cancelable!SelectUnion(@TypeOf(s)) {
|
||||
const U = SelectUnion(@TypeOf(s));
|
||||
const S = @TypeOf(s);
|
||||
const fields = @typeInfo(S).@"struct".fields;
|
||||
var futures: [fields.len]*AnyFuture = undefined;
|
||||
inline for (fields, &futures) |field, *any_future| {
|
||||
const future = @field(s, field.name);
|
||||
any_future.* = future.any_future orelse return @unionInit(U, field.name, future.result);
|
||||
}
|
||||
switch (try io.vtable.select(io.userdata, &futures)) {
|
||||
inline 0...(fields.len - 1) => |selected_index| {
|
||||
const field_name = fields[selected_index].name;
|
||||
return @unionInit(U, field_name, @field(s, field_name).await(io));
|
||||
},
|
||||
else => unreachable,
|
||||
}
|
||||
}
|
||||
|
||||
pub const LockedStderr = struct {
|
||||
file_writer: *File.Writer,
|
||||
terminal_mode: Terminal.Mode,
|
||||
|
|
|
|||
|
|
@ -100,15 +100,11 @@ const Fiber = struct {
|
|||
required_align: void align(4),
|
||||
evented: *Evented,
|
||||
context: Io.fiber.Context,
|
||||
await_count: i32,
|
||||
link: union {
|
||||
awaiter: ?*Fiber,
|
||||
group: struct { prev: ?*Fiber, next: ?*Fiber },
|
||||
},
|
||||
status: union(enum) {
|
||||
queue_next: ?*Fiber,
|
||||
awaiting_group: Group,
|
||||
},
|
||||
awaiting_group: Group,
|
||||
cancel_status: CancelStatus,
|
||||
cancel_protection: CancelProtection,
|
||||
|
||||
|
|
@ -123,7 +119,6 @@ const Fiber = struct {
|
|||
const Awaiting = enum(@Int(.unsigned, @bitSizeOf(usize) - shift)) {
|
||||
nothing = 0,
|
||||
group = 1,
|
||||
select = 2,
|
||||
_,
|
||||
|
||||
const shift = 1;
|
||||
|
|
@ -216,7 +211,6 @@ const Fiber = struct {
|
|||
}
|
||||
|
||||
fn destroy(fiber: *Fiber, ev: *Evented) void {
|
||||
assert(fiber.status.queue_next == null);
|
||||
ev.allocator().free(fiber.allocatedSlice());
|
||||
}
|
||||
|
||||
|
|
@ -272,14 +266,11 @@ const Fiber = struct {
|
|||
.group => {
|
||||
// The awaiter received a cancelation request while awaiting a group,
|
||||
// so propagate the cancelation to the group.
|
||||
if (fiber.status.awaiting_group.cancel(ev, null)) {
|
||||
fiber.status = .{ .queue_next = null };
|
||||
if (fiber.awaiting_group.cancel(ev, null)) {
|
||||
fiber.awaiting_group = undefined;
|
||||
ev.queue.async(fiber, &Fiber.@"resume");
|
||||
}
|
||||
},
|
||||
.select => if (@atomicRmw(i32, &fiber.await_count, .Add, 1, .monotonic) == -1) {
|
||||
ev.queue.async(fiber, &Fiber.@"resume");
|
||||
},
|
||||
_ => |awaiting| awaiting.toCancelable().async(),
|
||||
}
|
||||
}
|
||||
|
|
@ -370,8 +361,6 @@ pub fn io(ev: *Evented) Io {
|
|||
.swapCancelProtection = swapCancelProtection,
|
||||
.checkCancel = checkCancel,
|
||||
|
||||
.select = select,
|
||||
|
||||
.futexWait = futexWait,
|
||||
.futexWaitUncancelable = futexWaitUncancelable,
|
||||
.futexWake = futexWake,
|
||||
|
|
@ -522,9 +511,8 @@ pub fn init(ev: *Evented, backing_allocator: Allocator, options: InitOptions) !v
|
|||
.required_align = {},
|
||||
.evented = ev,
|
||||
.context = undefined,
|
||||
.await_count = 0,
|
||||
.link = .{ .awaiter = null },
|
||||
.status = .{ .queue_next = null },
|
||||
.awaiting_group = undefined,
|
||||
.cancel_status = .unrequested,
|
||||
.cancel_protection = .unblocked,
|
||||
},
|
||||
|
|
@ -642,7 +630,7 @@ const SwitchMessage = struct {
|
|||
|
||||
const PendingTask = union(enum) {
|
||||
nothing,
|
||||
await: u31,
|
||||
await: *Fiber,
|
||||
activate: c.dispatch.object_t,
|
||||
@"resume": c.dispatch.object_t,
|
||||
group_await: Group,
|
||||
|
|
@ -661,10 +649,10 @@ const SwitchMessage = struct {
|
|||
thread.current_context = message.contexts.new;
|
||||
switch (message.pending_task) {
|
||||
.nothing => {},
|
||||
.await => |count| {
|
||||
const fiber: *Fiber = @alignCast(@fieldParentPtr("context", message.contexts.old));
|
||||
if (@atomicRmw(i32, &fiber.await_count, .Sub, count, .monotonic) > 0)
|
||||
ev.queue.async(fiber, &Fiber.@"resume");
|
||||
.await => |awaiting| {
|
||||
const awaiter: *Fiber = @alignCast(@fieldParentPtr("context", message.contexts.old));
|
||||
if (@atomicRmw(?*Fiber, &awaiting.link.awaiter, .Xchg, awaiter, .acq_rel) ==
|
||||
Fiber.finished) ev.queue.async(awaiter, &Fiber.@"resume");
|
||||
},
|
||||
.activate => |object| object.activate(),
|
||||
.@"resume" => |object| object.@"resume"(),
|
||||
|
|
@ -998,7 +986,7 @@ fn crashHandler(userdata: ?*anyopaque) void {
|
|||
}
|
||||
|
||||
const AsyncClosure = struct {
|
||||
ev: *Evented,
|
||||
evented: *Evented,
|
||||
fiber: *Fiber,
|
||||
start: *const fn (context: *const anyopaque, result: *anyopaque) void,
|
||||
result_align: Alignment,
|
||||
|
|
@ -1035,13 +1023,13 @@ const AsyncClosure = struct {
|
|||
closure: *AsyncClosure,
|
||||
message: *const SwitchMessage,
|
||||
) callconv(.withStackAlign(.c, @alignOf(AsyncClosure))) noreturn {
|
||||
message.handle(closure.ev);
|
||||
const ev = closure.evented;
|
||||
const fiber = closure.fiber;
|
||||
message.handle(ev);
|
||||
closure.start(closure.contextPointer(), fiber.resultBytes(closure.result_align));
|
||||
if (@atomicRmw(?*Fiber, &fiber.link.awaiter, .Xchg, Fiber.finished, .acq_rel)) |awaiter|
|
||||
if (@atomicRmw(i32, &awaiter.await_count, .Add, 1, .monotonic) == -1)
|
||||
closure.ev.queue.async(awaiter, &Fiber.@"resume");
|
||||
closure.ev.yield(.nothing);
|
||||
ev.queue.async(awaiter, &Fiber.@"resume");
|
||||
ev.yield(.nothing);
|
||||
unreachable; // switched to dead fiber
|
||||
}
|
||||
};
|
||||
|
|
@ -1096,14 +1084,13 @@ fn concurrent(
|
|||
},
|
||||
else => |arch| @compileError("unimplemented architecture: " ++ @tagName(arch)),
|
||||
},
|
||||
.await_count = 0,
|
||||
.link = .{ .awaiter = null },
|
||||
.status = .{ .queue_next = null },
|
||||
.awaiting_group = undefined,
|
||||
.cancel_status = .unrequested,
|
||||
.cancel_protection = .unblocked,
|
||||
};
|
||||
closure.* = .{
|
||||
.ev = ev,
|
||||
.evented = ev,
|
||||
.fiber = fiber,
|
||||
.start = start,
|
||||
.result_align = result_alignment,
|
||||
|
|
@ -1121,18 +1108,11 @@ fn await(
|
|||
result_alignment: Alignment,
|
||||
) void {
|
||||
const ev: *Evented = @ptrCast(@alignCast(userdata));
|
||||
const fiber = Thread.current().currentFiber();
|
||||
const future_fiber: *Fiber = @ptrCast(@alignCast(future));
|
||||
if (@atomicRmw(?*Fiber, &future_fiber.link.awaiter, .Xchg, fiber, .acq_rel)) |awaiter| {
|
||||
assert(awaiter == Fiber.finished);
|
||||
} else while (true) {
|
||||
ev.yield(.{ .await = 1 });
|
||||
const awaiter = @atomicLoad(?*Fiber, &future_fiber.link.awaiter, .acquire);
|
||||
if (awaiter == Fiber.finished) break;
|
||||
assert(awaiter == fiber); // spurious wakeup
|
||||
}
|
||||
@memcpy(result, future_fiber.resultBytes(result_alignment));
|
||||
future_fiber.destroy(ev);
|
||||
const awaiting: *Fiber = @ptrCast(@alignCast(future));
|
||||
if (@atomicLoad(?*Fiber, &awaiting.link.awaiter, .acquire) != Fiber.finished)
|
||||
ev.yield(.{ .await = awaiting });
|
||||
@memcpy(result, awaiting.resultBytes(result_alignment));
|
||||
awaiting.destroy(ev);
|
||||
}
|
||||
|
||||
fn cancel(
|
||||
|
|
@ -1267,8 +1247,8 @@ const Group = struct {
|
|||
.awaiter_delayed = false,
|
||||
.fibers = .null,
|
||||
}, .release);
|
||||
assert(awaiter.status.awaiting_group.ptr == group.ptr);
|
||||
awaiter.status = .{ .queue_next = null };
|
||||
assert(awaiter.awaiting_group.ptr == group.ptr);
|
||||
awaiter.awaiting_group = undefined;
|
||||
return awaiter;
|
||||
}
|
||||
// Race with `Fiber.requestCancel`
|
||||
|
|
@ -1336,8 +1316,7 @@ const Group = struct {
|
|||
|
||||
/// Assumes the mutex is held.
|
||||
fn registerAwaiter(group: Group, awaiter: *Fiber) bool {
|
||||
assert(awaiter.status.queue_next == null);
|
||||
awaiter.status = .{ .awaiting_group = group };
|
||||
awaiter.awaiting_group = group;
|
||||
assert(@atomicRmw(
|
||||
Awaiter,
|
||||
group.awaiterPtr(),
|
||||
|
|
@ -1349,7 +1328,7 @@ const Group = struct {
|
|||
}
|
||||
|
||||
const AsyncClosure = struct {
|
||||
ev: *Evented,
|
||||
evented: *Evented,
|
||||
group: Group,
|
||||
fiber: *Fiber,
|
||||
start: *const fn (context: *const anyopaque) Io.Cancelable!void,
|
||||
|
|
@ -1388,19 +1367,15 @@ const Group = struct {
|
|||
closure: *Group.AsyncClosure,
|
||||
message: *const SwitchMessage,
|
||||
) callconv(.withStackAlign(.c, @alignOf(Group.AsyncClosure))) noreturn {
|
||||
message.handle(closure.ev);
|
||||
assert(closure.fiber.status.queue_next == null);
|
||||
const result = closure.start(closure.contextPointer());
|
||||
const ev = closure.ev;
|
||||
const group = closure.group;
|
||||
const ev = closure.evented;
|
||||
const fiber = closure.fiber;
|
||||
const cancel_acknowledged = fiber.cancel_protection.acknowledged;
|
||||
if (result) {
|
||||
assert(!cancel_acknowledged); // group task acknowledged cancelation but did not return `error.Canceled`
|
||||
message.handle(ev);
|
||||
if (closure.start(closure.contextPointer())) {
|
||||
assert(!fiber.cancel_protection.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
|
||||
error.Canceled => assert(fiber.cancel_protection.acknowledged), // group task returned `error.Canceled` but was never canceled
|
||||
}
|
||||
if (group.removeFiber(ev, fiber)) |awaiter| ev.queue.async(awaiter, &Fiber.@"resume");
|
||||
if (closure.group.removeFiber(ev, fiber)) |awaiter| ev.queue.async(awaiter, &Fiber.@"resume");
|
||||
ev.yield(.destroy);
|
||||
unreachable; // switched to dead fiber
|
||||
}
|
||||
|
|
@ -1470,14 +1445,13 @@ fn groupConcurrent(
|
|||
},
|
||||
else => |arch| @compileError("unimplemented architecture: " ++ @tagName(arch)),
|
||||
},
|
||||
.await_count = 0,
|
||||
.link = .{ .group = .{ .prev = null, .next = null } },
|
||||
.status = .{ .queue_next = null },
|
||||
.awaiting_group = undefined,
|
||||
.cancel_status = .unrequested,
|
||||
.cancel_protection = .unblocked,
|
||||
};
|
||||
closure.* = .{
|
||||
.ev = ev,
|
||||
.evented = ev,
|
||||
.group = group,
|
||||
.fiber = fiber,
|
||||
.start = start,
|
||||
|
|
@ -1689,50 +1663,6 @@ fn futexForAddress(ev: *Evented, address: usize) *Futex {
|
|||
return &ev.futexes[hashed >> @clz(ev.futexes.len - 1)];
|
||||
}
|
||||
|
||||
fn select(userdata: ?*anyopaque, futures: []const *Io.AnyFuture) Io.Cancelable!usize {
|
||||
const ev: *Evented = @ptrCast(@alignCast(userdata));
|
||||
const fiber = Thread.current().currentFiber();
|
||||
var await_count: u31, var result = for (futures, 0..) |future, future_index| {
|
||||
const future_fiber: *Fiber = @ptrCast(@alignCast(future));
|
||||
if (@atomicRmw(
|
||||
?*Fiber,
|
||||
&future_fiber.link.awaiter,
|
||||
.Xchg,
|
||||
fiber,
|
||||
.acq_rel,
|
||||
)) |awaiter| {
|
||||
assert(awaiter == Fiber.finished);
|
||||
break .{ @intCast(future_index), future_index };
|
||||
}
|
||||
} else result: {
|
||||
const await_count: u31 = @intCast(futures.len);
|
||||
ev.yield(.{ .await = 1 });
|
||||
break :result .{ await_count - 1, futures.len };
|
||||
};
|
||||
for (futures[0..result], 0..) |future, future_index| {
|
||||
const future_fiber: *Fiber = @ptrCast(@alignCast(future));
|
||||
const awaiter = @atomicRmw(?*Fiber, &future_fiber.link.awaiter, .Xchg, null, .monotonic);
|
||||
if (awaiter == Fiber.finished) {
|
||||
@atomicStore(?*Fiber, &future_fiber.link.awaiter, Fiber.finished, .monotonic);
|
||||
result = @min(future_index, result);
|
||||
} else {
|
||||
assert(awaiter == fiber);
|
||||
await_count -= 1;
|
||||
}
|
||||
}
|
||||
// Equivalent to `ev.yield(null, .{ .await = await_count });`,
|
||||
// but avoiding a context switch in the common case.
|
||||
switch (std.math.order(
|
||||
@atomicRmw(i32, &fiber.await_count, .Sub, await_count, .monotonic),
|
||||
await_count,
|
||||
)) {
|
||||
.lt => ev.yield(.{ .await = 0 }),
|
||||
.eq => {},
|
||||
.gt => unreachable,
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
fn futexWait(
|
||||
userdata: ?*anyopaque,
|
||||
ptr: *const u32,
|
||||
|
|
|
|||
|
|
@ -491,7 +491,6 @@ const SwitchMessage = struct {
|
|||
reschedule,
|
||||
recycle: *Fiber,
|
||||
register_awaiter: *?*Fiber,
|
||||
register_select: []const *Io.AnyFuture,
|
||||
exit,
|
||||
};
|
||||
|
||||
|
|
@ -514,19 +513,6 @@ const SwitchMessage = struct {
|
|||
if (@atomicRmw(?*Fiber, awaiter, .Xchg, prev_fiber, .acq_rel) == Fiber.finished)
|
||||
k.schedule(thread, .{ .head = prev_fiber, .tail = prev_fiber });
|
||||
},
|
||||
.register_select => |futures| {
|
||||
const prev_fiber: *Fiber = @alignCast(@fieldParentPtr("context", message.contexts.old));
|
||||
assert(prev_fiber.queue_next == null);
|
||||
for (futures) |any_future| {
|
||||
const future_fiber: *Fiber = @ptrCast(@alignCast(any_future));
|
||||
if (@atomicRmw(?*Fiber, &future_fiber.awaiter, .Xchg, prev_fiber, .acq_rel) == Fiber.finished) {
|
||||
const closure: *AsyncClosure = .fromFiber(future_fiber);
|
||||
if (!@atomicRmw(bool, &closure.already_awaited, .Xchg, true, .seq_cst)) {
|
||||
k.schedule(thread, .{ .head = prev_fiber, .tail = prev_fiber });
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
.exit => for (k.threads.allocated[0..@atomicLoad(u32, &k.threads.active, .acquire)]) |*each_thread| {
|
||||
const changes = [_]posix.Kevent{
|
||||
.{
|
||||
|
|
@ -628,7 +614,6 @@ pub fn io(k: *Kqueue) Io {
|
|||
.concurrent = concurrent,
|
||||
.await = await,
|
||||
.cancel = cancel,
|
||||
.select = select,
|
||||
|
||||
.groupAsync = groupAsync,
|
||||
.groupConcurrent = groupConcurrent,
|
||||
|
|
@ -824,13 +809,6 @@ fn groupCancel(userdata: ?*anyopaque, group: *Io.Group, token: *anyopaque) void
|
|||
@panic("TODO");
|
||||
}
|
||||
|
||||
fn select(userdata: ?*anyopaque, futures: []const *Io.AnyFuture) Io.Cancelable!usize {
|
||||
const k: *Kqueue = @ptrCast(@alignCast(userdata));
|
||||
_ = k;
|
||||
_ = futures;
|
||||
@panic("TODO");
|
||||
}
|
||||
|
||||
fn dirCreateDir(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, permissions: Dir.Permissions) Dir.CreateDirError!void {
|
||||
const k: *Kqueue = @ptrCast(@alignCast(userdata));
|
||||
_ = k;
|
||||
|
|
|
|||
|
|
@ -1772,7 +1772,6 @@ pub fn io(t: *Threaded) Io {
|
|||
.concurrent = concurrent,
|
||||
.await = await,
|
||||
.cancel = cancel,
|
||||
.select = select,
|
||||
|
||||
.groupAsync = groupAsync,
|
||||
.groupConcurrent = groupConcurrent,
|
||||
|
|
@ -1938,7 +1937,6 @@ pub fn ioBasic(t: *Threaded) Io {
|
|||
.concurrent = concurrent,
|
||||
.await = await,
|
||||
.cancel = cancel,
|
||||
.select = select,
|
||||
|
||||
.groupAsync = groupAsync,
|
||||
.groupConcurrent = groupConcurrent,
|
||||
|
|
@ -11727,74 +11725,6 @@ fn sleepNanosleep(t: *Threaded, timeout: Io.Timeout) Io.Cancelable!void {
|
|||
}
|
||||
}
|
||||
|
||||
fn select(userdata: ?*anyopaque, futures: []const *Io.AnyFuture) Io.Cancelable!usize {
|
||||
const t: *Threaded = @ptrCast(@alignCast(userdata));
|
||||
_ = t;
|
||||
|
||||
var num_completed: std.atomic.Value(u32) = .init(0);
|
||||
|
||||
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;
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
errdefer _ = finishSelect(&num_completed, futures);
|
||||
|
||||
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 completed_index;
|
||||
}
|
||||
|
||||
fn netListenIpPosix(
|
||||
userdata: ?*anyopaque,
|
||||
address: IpAddress,
|
||||
|
|
|
|||
|
|
@ -150,7 +150,6 @@ const Thread = struct {
|
|||
const Fiber = struct {
|
||||
required_align: void align(4),
|
||||
context: Io.fiber.Context,
|
||||
await_count: i32,
|
||||
link: union {
|
||||
awaiter: ?*Fiber,
|
||||
group: struct { prev: ?*Fiber, next: ?*Fiber },
|
||||
|
|
@ -175,7 +174,6 @@ const Fiber = struct {
|
|||
const Awaiting = enum(u31) {
|
||||
nothing = std.math.maxInt(u31),
|
||||
group = std.math.maxInt(u31) - 1,
|
||||
select = std.math.maxInt(u31) - 2,
|
||||
/// An io_uring fd.
|
||||
_,
|
||||
|
||||
|
|
@ -186,14 +184,14 @@ const Fiber = struct {
|
|||
fn fromIoUringFd(fd: fd_t) Awaiting {
|
||||
const awaiting: Awaiting = @enumFromInt(fd);
|
||||
switch (awaiting) {
|
||||
.nothing, .group, .select => unreachable,
|
||||
.nothing, .group => unreachable,
|
||||
_ => return awaiting,
|
||||
}
|
||||
}
|
||||
|
||||
fn toIoUringFd(awaiting: Awaiting) fd_t {
|
||||
switch (awaiting) {
|
||||
.nothing, .group, .select => unreachable,
|
||||
.nothing, .group => unreachable,
|
||||
_ => return @intFromEnum(awaiting),
|
||||
}
|
||||
}
|
||||
|
|
@ -376,9 +374,6 @@ const Fiber = struct {
|
|||
_ = ev.schedule(.current(), .{ .head = fiber, .tail = fiber });
|
||||
}
|
||||
},
|
||||
.select => if (@atomicRmw(i32, &fiber.await_count, .Add, 1, .monotonic) == -1) {
|
||||
_ = ev.schedule(.current(), .{ .head = fiber, .tail = fiber });
|
||||
},
|
||||
_ => |awaiting| {
|
||||
const awaiting_io_uring_fd = awaiting.toIoUringFd();
|
||||
const thread: *Thread = .current();
|
||||
|
|
@ -684,8 +679,6 @@ pub fn io(ev: *Evented) Io {
|
|||
.swapCancelProtection = swapCancelProtection,
|
||||
.checkCancel = checkCancel,
|
||||
|
||||
.select = select,
|
||||
|
||||
.futexWait = futexWait,
|
||||
.futexWaitUncancelable = futexWaitUncancelable,
|
||||
.futexWake = futexWake,
|
||||
|
|
@ -862,7 +855,6 @@ pub fn init(ev: *Evented, backing_allocator: Allocator, options: InitOptions) !v
|
|||
main_fiber.* = .{
|
||||
.required_align = {},
|
||||
.context = undefined,
|
||||
.await_count = 0,
|
||||
.link = .{ .awaiter = null },
|
||||
.status = .{ .queue_next = null },
|
||||
.cancel_status = .unrequested,
|
||||
|
|
@ -1266,7 +1258,7 @@ const SwitchMessage = struct {
|
|||
const PendingTask = union(enum) {
|
||||
nothing,
|
||||
reschedule,
|
||||
await: u31,
|
||||
await: *Fiber,
|
||||
group_await: Group,
|
||||
group_cancel: Group,
|
||||
batch_await: *Io.Batch,
|
||||
|
|
@ -1290,10 +1282,11 @@ const SwitchMessage = struct {
|
|||
assert(fiber.status.queue_next == null);
|
||||
_ = ev.schedule(thread, .{ .head = fiber, .tail = fiber });
|
||||
},
|
||||
.await => |count| {
|
||||
const fiber: *Fiber = @alignCast(@fieldParentPtr("context", message.contexts.old));
|
||||
if (@atomicRmw(i32, &fiber.await_count, .Sub, count, .monotonic) > 0)
|
||||
_ = ev.schedule(thread, .{ .head = fiber, .tail = fiber });
|
||||
.await => |awaiting| {
|
||||
const awaiter: *Fiber = @alignCast(@fieldParentPtr("context", message.contexts.old));
|
||||
assert(awaiter.status.queue_next == null);
|
||||
if (@atomicRmw(?*Fiber, &awaiting.link.awaiter, .Xchg, awaiter, .acq_rel) ==
|
||||
Fiber.finished) _ = ev.schedule(thread, .{ .head = awaiter, .tail = awaiter });
|
||||
},
|
||||
.group_await => |group| {
|
||||
const fiber: *Fiber = @alignCast(@fieldParentPtr("context", message.contexts.old));
|
||||
|
|
@ -1367,7 +1360,7 @@ fn crashHandler(userdata: ?*anyopaque) void {
|
|||
}
|
||||
|
||||
const AsyncClosure = struct {
|
||||
ev: *Evented,
|
||||
evented: *Evented,
|
||||
fiber: *Fiber,
|
||||
start: *const fn (context: *const anyopaque, result: *anyopaque) void,
|
||||
result_align: Alignment,
|
||||
|
|
@ -1404,16 +1397,11 @@ const AsyncClosure = struct {
|
|||
closure: *AsyncClosure,
|
||||
message: *const SwitchMessage,
|
||||
) callconv(.withStackAlign(.c, @alignOf(AsyncClosure))) noreturn {
|
||||
message.handle(closure.ev);
|
||||
const ev = closure.evented;
|
||||
const fiber = closure.fiber;
|
||||
message.handle(ev);
|
||||
closure.start(closure.contextPointer(), fiber.resultBytes(closure.result_align));
|
||||
closure.ev.yield(
|
||||
if (@atomicRmw(?*Fiber, &fiber.link.awaiter, .Xchg, Fiber.finished, .acq_rel)) |awaiter|
|
||||
if (@atomicRmw(i32, &awaiter.await_count, .Add, 1, .monotonic) == -1) awaiter else null
|
||||
else
|
||||
null,
|
||||
.nothing,
|
||||
);
|
||||
ev.yield(@atomicRmw(?*Fiber, &fiber.link.awaiter, .Xchg, Fiber.finished, .acq_rel), .nothing);
|
||||
unreachable; // switched to dead fiber
|
||||
}
|
||||
};
|
||||
|
|
@ -1467,7 +1455,6 @@ fn concurrent(
|
|||
},
|
||||
else => |arch| @compileError("unimplemented architecture: " ++ @tagName(arch)),
|
||||
},
|
||||
.await_count = 0,
|
||||
.link = .{ .awaiter = null },
|
||||
.status = .{ .queue_next = null },
|
||||
.cancel_status = .unrequested,
|
||||
|
|
@ -1485,7 +1472,7 @@ fn concurrent(
|
|||
},
|
||||
};
|
||||
closure.* = .{
|
||||
.ev = ev,
|
||||
.evented = ev,
|
||||
.fiber = fiber,
|
||||
.start = start,
|
||||
.result_align = result_alignment,
|
||||
|
|
@ -1504,18 +1491,11 @@ fn await(
|
|||
result_alignment: Alignment,
|
||||
) void {
|
||||
const ev: *Evented = @ptrCast(@alignCast(userdata));
|
||||
const fiber = Thread.current().currentFiber();
|
||||
const future_fiber: *Fiber = @ptrCast(@alignCast(future));
|
||||
if (@atomicRmw(?*Fiber, &future_fiber.link.awaiter, .Xchg, fiber, .acq_rel)) |awaiter| {
|
||||
assert(awaiter == Fiber.finished);
|
||||
} else while (true) {
|
||||
ev.yield(null, .{ .await = 1 });
|
||||
const awaiter = @atomicLoad(?*Fiber, &future_fiber.link.awaiter, .acquire);
|
||||
if (awaiter == Fiber.finished) break;
|
||||
assert(awaiter == fiber); // spurious wakeup
|
||||
}
|
||||
@memcpy(result, future_fiber.resultBytes(result_alignment));
|
||||
future_fiber.destroy();
|
||||
const awaiting: *Fiber = @ptrCast(@alignCast(future));
|
||||
if (@atomicLoad(?*Fiber, &awaiting.link.awaiter, .acquire) != Fiber.finished)
|
||||
ev.yield(null, .{ .await = awaiting });
|
||||
@memcpy(result, awaiting.resultBytes(result_alignment));
|
||||
awaiting.destroy();
|
||||
}
|
||||
|
||||
fn cancel(
|
||||
|
|
@ -1732,7 +1712,7 @@ const Group = struct {
|
|||
}
|
||||
|
||||
const AsyncClosure = struct {
|
||||
ev: *Evented,
|
||||
evented: *Evented,
|
||||
group: Group,
|
||||
fiber: *Fiber,
|
||||
start: *const fn (context: *const anyopaque) Io.Cancelable!void,
|
||||
|
|
@ -1771,19 +1751,16 @@ const Group = struct {
|
|||
closure: *Group.AsyncClosure,
|
||||
message: *const SwitchMessage,
|
||||
) callconv(.withStackAlign(.c, @alignOf(Group.AsyncClosure))) noreturn {
|
||||
message.handle(closure.ev);
|
||||
assert(closure.fiber.status.queue_next == null);
|
||||
const result = closure.start(closure.contextPointer());
|
||||
const ev = closure.ev;
|
||||
const group = closure.group;
|
||||
const ev = closure.evented;
|
||||
const fiber = closure.fiber;
|
||||
const cancel_acknowledged = fiber.cancel_protection.acknowledged;
|
||||
if (result) {
|
||||
assert(!cancel_acknowledged); // group task acknowledged cancelation but did not return `error.Canceled`
|
||||
message.handle(ev);
|
||||
assert(fiber.status.queue_next == null);
|
||||
if (closure.start(closure.contextPointer())) {
|
||||
assert(!fiber.cancel_protection.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
|
||||
error.Canceled => assert(fiber.cancel_protection.acknowledged), // group task returned `error.Canceled` but was never canceled
|
||||
}
|
||||
ev.yield(group.removeFiber(ev, fiber), .destroy);
|
||||
ev.yield(closure.group.removeFiber(ev, fiber), .destroy);
|
||||
unreachable; // switched to dead fiber
|
||||
}
|
||||
};
|
||||
|
|
@ -1851,7 +1828,6 @@ fn groupConcurrent(
|
|||
},
|
||||
else => |arch| @compileError("unimplemented architecture: " ++ @tagName(arch)),
|
||||
},
|
||||
.await_count = 0,
|
||||
.link = .{ .group = .{ .prev = null, .next = null } },
|
||||
.status = .{ .queue_next = null },
|
||||
.cancel_status = .unrequested,
|
||||
|
|
@ -1869,7 +1845,7 @@ fn groupConcurrent(
|
|||
},
|
||||
};
|
||||
closure.* = .{
|
||||
.ev = ev,
|
||||
.evented = ev,
|
||||
.group = group,
|
||||
.fiber = fiber,
|
||||
.start = start,
|
||||
|
|
@ -1928,57 +1904,6 @@ fn checkCancel(userdata: ?*anyopaque) Io.Cancelable!void {
|
|||
}
|
||||
}
|
||||
|
||||
fn select(userdata: ?*anyopaque, futures: []const *Io.AnyFuture) Io.Cancelable!usize {
|
||||
const ev: *Evented = @ptrCast(@alignCast(userdata));
|
||||
var cancel_region: CancelRegion = .init();
|
||||
defer cancel_region.deinit();
|
||||
var await_count: u31, var result = for (futures, 0..) |future, future_index| {
|
||||
const future_fiber: *Fiber = @ptrCast(@alignCast(future));
|
||||
if (@atomicRmw(
|
||||
?*Fiber,
|
||||
&future_fiber.link.awaiter,
|
||||
.Xchg,
|
||||
cancel_region.fiber,
|
||||
.acq_rel,
|
||||
)) |awaiter| {
|
||||
assert(awaiter == Fiber.finished);
|
||||
break .{ @intCast(future_index), future_index };
|
||||
}
|
||||
} else result: {
|
||||
const await_count: u31 = @intCast(futures.len);
|
||||
cancel_region.await(.select) catch |err| switch (err) {
|
||||
error.Canceled => |e| break :result .{ await_count + 1, e },
|
||||
};
|
||||
ev.yield(null, .{ .await = 1 });
|
||||
cancel_region.await(.nothing) catch |err| switch (err) {
|
||||
error.Canceled => |e| break :result .{ await_count, e },
|
||||
};
|
||||
break :result .{ await_count - 1, futures.len };
|
||||
};
|
||||
for (futures[0 .. result catch futures.len], 0..) |future, future_index| {
|
||||
const future_fiber: *Fiber = @ptrCast(@alignCast(future));
|
||||
const awaiter = @atomicRmw(?*Fiber, &future_fiber.link.awaiter, .Xchg, null, .monotonic);
|
||||
if (awaiter == Fiber.finished) {
|
||||
@atomicStore(?*Fiber, &future_fiber.link.awaiter, Fiber.finished, .monotonic);
|
||||
result = if (result) |finished_index| @min(future_index, finished_index) else |e| e;
|
||||
} else {
|
||||
assert(awaiter == cancel_region.fiber);
|
||||
await_count -= 1;
|
||||
}
|
||||
}
|
||||
// Equivalent to `ev.yield(null, .{ .await = await_count });`,
|
||||
// but avoiding a context switch in the common case.
|
||||
switch (std.math.order(
|
||||
@atomicRmw(i32, &cancel_region.fiber.await_count, .Sub, await_count, .monotonic),
|
||||
await_count,
|
||||
)) {
|
||||
.lt => ev.yield(null, .{ .await = 0 }),
|
||||
.eq => {},
|
||||
.gt => unreachable,
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
fn futexWait(
|
||||
userdata: ?*anyopaque,
|
||||
ptr: *const u32,
|
||||
|
|
@ -2230,7 +2155,7 @@ fn deviceIoControl(
|
|||
const rc = linux.ioctl(o.file.handle, @bitCast(o.code), @intFromPtr(o.arg));
|
||||
switch (linux.errno(rc)) {
|
||||
.SUCCESS => return @bitCast(@as(u32, @truncate(rc))),
|
||||
.INTR => continue,
|
||||
.INTR => {},
|
||||
else => |err| return -@as(i32, @intFromEnum(err)),
|
||||
}
|
||||
}
|
||||
|
|
@ -2628,7 +2553,7 @@ fn dirCreateDir(
|
|||
ev.yield(null, .nothing);
|
||||
switch (cancel_region.errno()) {
|
||||
.SUCCESS => return,
|
||||
.INTR, .CANCELED => continue,
|
||||
.INTR, .CANCELED => {},
|
||||
.ACCES => return error.AccessDenied,
|
||||
.BADF => |err| return errnoBug(err), // File descriptor used after closed.
|
||||
.PERM => return error.PermissionDenied,
|
||||
|
|
@ -2711,7 +2636,7 @@ fn filePathKind(ev: *Evented, dir: Dir, sub_path: []const u8) !File.Kind {
|
|||
if (!statx_buf.mask.TYPE) return error.Unexpected;
|
||||
return statxKind(statx_buf.mode);
|
||||
},
|
||||
.INTR, .CANCELED => continue,
|
||||
.INTR, .CANCELED => {},
|
||||
.ACCES => |err| return errnoBug(err),
|
||||
.BADF => |err| return errnoBug(err), // File descriptor used after closed.
|
||||
.FAULT => |err| return errnoBug(err),
|
||||
|
|
@ -2824,7 +2749,7 @@ fn dirAccess(
|
|||
try sync.cancel_region.await(.nothing);
|
||||
switch (linux.errno(linux.faccessat(dir.handle, sub_path_posix, mode, flags))) {
|
||||
.SUCCESS => return,
|
||||
.INTR => continue,
|
||||
.INTR => {},
|
||||
.ACCES => return error.AccessDenied,
|
||||
.PERM => return error.PermissionDenied,
|
||||
.ROFS => return error.ReadOnlyFileSystem,
|
||||
|
|
@ -2863,7 +2788,7 @@ fn dirCreateFile(
|
|||
.EXCL = flags.exclusive,
|
||||
.CLOEXEC = true,
|
||||
}, flags.permissions.toMode());
|
||||
errdefer ev.close(fd);
|
||||
errdefer ev.closeAsync(fd);
|
||||
|
||||
switch (flags.lock) {
|
||||
.none => {},
|
||||
|
|
@ -3051,7 +2976,7 @@ fn dirOpenFile(
|
|||
.CLOEXEC = true,
|
||||
.PATH = flags.path_only,
|
||||
}, 0);
|
||||
errdefer ev.close(fd);
|
||||
errdefer ev.closeAsync(fd);
|
||||
|
||||
if (!flags.allow_directory) {
|
||||
const is_dir = is_dir: {
|
||||
|
|
@ -3105,7 +3030,7 @@ fn dirRead(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) Dir.Read
|
|||
const rc = linux.getdents64(dr.dir.handle, dr.buffer.ptr, dr.buffer.len);
|
||||
switch (linux.errno(rc)) {
|
||||
.SUCCESS => break rc,
|
||||
.INTR => continue,
|
||||
.INTR => {},
|
||||
.BADF => |err| return errnoBug(err), // Dir is invalid or was opened without iteration ability.
|
||||
.FAULT => |err| return errnoBug(err),
|
||||
.NOTDIR => |err| return errnoBug(err),
|
||||
|
|
@ -3198,7 +3123,7 @@ fn dirRealPathFile(
|
|||
error.FileLocksUnsupported => return errnoBug(.OPNOTSUPP), // Not asking for locks.
|
||||
else => |e| return e,
|
||||
};
|
||||
defer ev.close(fd);
|
||||
defer ev.closeAsync(fd);
|
||||
return ev.realPath(try maybe_sync.enterSync(ev), fd, out_buffer);
|
||||
}
|
||||
|
||||
|
|
@ -3231,7 +3156,7 @@ fn dirDeleteFile(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8) Dir.Dele
|
|||
ev.yield(null, .nothing);
|
||||
switch (cancel_region.errno()) {
|
||||
.SUCCESS => return,
|
||||
.INTR, .CANCELED => continue,
|
||||
.INTR, .CANCELED => {},
|
||||
.PERM => return error.PermissionDenied,
|
||||
.ACCES => return error.AccessDenied,
|
||||
.BUSY => return error.FileBusy,
|
||||
|
|
@ -3283,7 +3208,7 @@ fn dirDeleteDir(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8) Dir.Delet
|
|||
ev.yield(null, .nothing);
|
||||
switch (cancel_region.errno()) {
|
||||
.SUCCESS => return,
|
||||
.INTR, .CANCELED => continue,
|
||||
.INTR, .CANCELED => {},
|
||||
.ACCES => return error.AccessDenied,
|
||||
.PERM => return error.PermissionDenied,
|
||||
.BUSY => return error.FileBusy,
|
||||
|
|
@ -3399,7 +3324,7 @@ fn dirSymLink(
|
|||
ev.yield(null, .nothing);
|
||||
switch (cancel_region.errno()) {
|
||||
.SUCCESS => return,
|
||||
.INTR, .CANCELED => continue,
|
||||
.INTR, .CANCELED => {},
|
||||
.FAULT => |err| return errnoBug(err),
|
||||
.INVAL => |err| return errnoBug(err),
|
||||
.ACCES => return error.AccessDenied,
|
||||
|
|
@ -3438,7 +3363,7 @@ fn dirReadLink(
|
|||
const rc = linux.readlinkat(dir.handle, sub_path_posix, buffer.ptr, buffer.len);
|
||||
switch (linux.errno(rc)) {
|
||||
.SUCCESS => return @bitCast(rc),
|
||||
.INTR => continue,
|
||||
.INTR => {},
|
||||
.ACCES => return error.AccessDenied,
|
||||
.FAULT => |err| return errnoBug(err),
|
||||
.INVAL => return error.NotLink,
|
||||
|
|
@ -3628,7 +3553,7 @@ fn fileLength(userdata: ?*anyopaque, file: File) File.LengthError!u64 {
|
|||
if (!statx_buf.mask.SIZE) return error.Unexpected;
|
||||
return statx_buf.size;
|
||||
},
|
||||
.INTR, .CANCELED => continue,
|
||||
.INTR, .CANCELED => {},
|
||||
.ACCES => |err| return errnoBug(err),
|
||||
.BADF => |err| return errnoBug(err), // File descriptor used after closed.
|
||||
.FAULT => |err| return errnoBug(err),
|
||||
|
|
@ -3811,7 +3736,7 @@ fn fileSync(userdata: ?*anyopaque, file: File) File.SyncError!void {
|
|||
ev.yield(null, .nothing);
|
||||
switch (cancel_region.errno()) {
|
||||
.SUCCESS => return,
|
||||
.INTR, .CANCELED => continue,
|
||||
.INTR, .CANCELED => {},
|
||||
.BADF => |err| return errnoBug(err),
|
||||
.INVAL => |err| return errnoBug(err),
|
||||
.ROFS => |err| return errnoBug(err),
|
||||
|
|
@ -3833,7 +3758,7 @@ fn fileIsTty(userdata: ?*anyopaque, file: File) Io.Cancelable!bool {
|
|||
const rc = linux.ioctl(file.handle, linux.T.IOCGWINSZ, @intFromPtr(&wsz));
|
||||
switch (linux.errno(rc)) {
|
||||
.SUCCESS => return true,
|
||||
.INTR => continue,
|
||||
.INTR => {},
|
||||
else => return false,
|
||||
}
|
||||
}
|
||||
|
|
@ -3869,7 +3794,7 @@ fn fileSetLength(userdata: ?*anyopaque, file: File, length: u64) File.SetLengthE
|
|||
ev.yield(null, .nothing);
|
||||
switch (cancel_region.errno()) {
|
||||
.SUCCESS => return,
|
||||
.INTR, .CANCELED => continue,
|
||||
.INTR, .CANCELED => {},
|
||||
.FBIG => return error.FileTooBig,
|
||||
.IO => return error.InputOutput,
|
||||
.PERM => return error.PermissionDenied,
|
||||
|
|
@ -4051,7 +3976,7 @@ fn fileMemoryMapCreate(
|
|||
const rc = linux.mmap(null, options.len, prot, flags, file.handle, casted_offset);
|
||||
switch (linux.errno(rc)) {
|
||||
.SUCCESS => break @as([*]align(page_align) u8, @ptrFromInt(rc))[0..options.len],
|
||||
.INTR => continue,
|
||||
.INTR => {},
|
||||
.ACCES => return error.AccessDenied,
|
||||
.AGAIN => return error.LockedMemoryLimitExceeded,
|
||||
.MFILE => return error.ProcessFdQuotaExceeded,
|
||||
|
|
@ -4111,7 +4036,7 @@ fn fileMemoryMapSetLength(
|
|||
const rc = linux.mremap(old_memory.ptr, old_memory.len, new_len, flags, addr_hint);
|
||||
switch (linux.errno(rc)) {
|
||||
.SUCCESS => break @as([*]align(page_align) u8, @ptrFromInt(rc))[0..new_len],
|
||||
.INTR => continue,
|
||||
.INTR => {},
|
||||
.AGAIN => return error.LockedMemoryLimitExceeded,
|
||||
.NOMEM => return error.OutOfMemory,
|
||||
.INVAL => |err| return errnoBug(err),
|
||||
|
|
@ -4220,7 +4145,7 @@ fn processCurrentPath(userdata: ?*anyopaque, buffer: []u8) process.CurrentPathEr
|
|||
try sync.cancel_region.await(.nothing);
|
||||
switch (linux.errno(linux.getcwd(buffer.ptr, buffer.len))) {
|
||||
.SUCCESS => return std.mem.findScalar(u8, buffer, 0).?,
|
||||
.INTR => continue,
|
||||
.INTR => {},
|
||||
.NOENT => return error.CurrentDirUnlinked,
|
||||
.RANGE => return error.NameTooLong,
|
||||
.FAULT => |err| return errnoBug(err),
|
||||
|
|
@ -4235,7 +4160,7 @@ fn processSetCurrentDir(userdata: ?*anyopaque, dir: Dir) process.SetCurrentDirEr
|
|||
if (dir.handle == linux.AT.FDCWD) return;
|
||||
var sync: CancelRegion.Sync = try .init(ev);
|
||||
defer sync.deinit(ev);
|
||||
return ev.fchdir(&sync, dir.handle);
|
||||
return fchdir(&sync, dir.handle);
|
||||
}
|
||||
|
||||
fn processSetCurrentPath(userdata: ?*anyopaque, dir_path: []const u8) ChdirError!void {
|
||||
|
|
@ -4272,7 +4197,7 @@ fn processReplace(userdata: ?*anyopaque, options: process.ReplaceOptions) proces
|
|||
|
||||
var sync: CancelRegion.Sync = try .init(ev);
|
||||
defer sync.deinit(ev);
|
||||
return ev.execv(&sync, options.expand_arg0, argv_buf.ptr[0].?, argv_buf.ptr, env_block, PATH);
|
||||
return execv(&sync, options.expand_arg0, argv_buf.ptr[0].?, argv_buf.ptr, env_block, PATH);
|
||||
}
|
||||
|
||||
fn processReplacePath(
|
||||
|
|
@ -4292,7 +4217,7 @@ fn processSpawn(userdata: ?*anyopaque, options: process.SpawnOptions) process.Sp
|
|||
const spawned = try ev.spawn(options);
|
||||
var cancel_region: CancelRegion = .initBlocked();
|
||||
defer cancel_region.deinit();
|
||||
defer ev.close(spawned.err_fd);
|
||||
defer ev.closeAsync(spawned.err_fd);
|
||||
|
||||
// Wait for the child to report any errors in or before `execvpe`.
|
||||
var child_err: ForkBailError = undefined;
|
||||
|
|
@ -4434,8 +4359,9 @@ fn spawn(ev: *Evented, options: process.SpawnOptions) process.SpawnError!Spawned
|
|||
|
||||
if (pid_result == 0) {
|
||||
defer comptime unreachable; // We are the child.
|
||||
// Note that the parent uring is no longer accessible, so we must no longer reference `ev`.
|
||||
var sync: CancelRegion.Sync = .{ .cancel_region = .initBlocked() };
|
||||
const err = ev.setUpChild(&sync, .{
|
||||
const err = setUpChild(&sync, .{
|
||||
.stdin_pipe = stdin_pipe[0],
|
||||
.stdout_pipe = stdout_pipe[1],
|
||||
.stderr_pipe = stderr_pipe[1],
|
||||
|
|
@ -4446,7 +4372,7 @@ fn spawn(ev: *Evented, options: process.SpawnOptions) process.SpawnError!Spawned
|
|||
.PATH = PATH,
|
||||
.spawn = options,
|
||||
});
|
||||
ev.writeAll(&sync.cancel_region, err_pipe[1], @ptrCast(&err)) catch {};
|
||||
writeAllSync(&sync, err_pipe[1], @ptrCast(&err)) catch {};
|
||||
const exit = if (builtin.single_threaded) linux.exit else linux.exit_group;
|
||||
exit(1);
|
||||
}
|
||||
|
|
@ -4454,13 +4380,13 @@ fn spawn(ev: *Evented, options: process.SpawnOptions) process.SpawnError!Spawned
|
|||
const pid: pid_t = @intCast(pid_result); // We are the parent.
|
||||
errdefer comptime unreachable; // The child is forked; we must not error from now on
|
||||
|
||||
ev.close(err_pipe[1]); // make sure only the child holds the write end open
|
||||
ev.closeAsync(err_pipe[1]); // make sure only the child holds the write end open
|
||||
|
||||
if (options.stdin == .pipe) ev.close(stdin_pipe[0]);
|
||||
if (options.stdout == .pipe) ev.close(stdout_pipe[1]);
|
||||
if (options.stderr == .pipe) ev.close(stderr_pipe[1]);
|
||||
if (options.stdin == .pipe) ev.closeAsync(stdin_pipe[0]);
|
||||
if (options.stdout == .pipe) ev.closeAsync(stdout_pipe[1]);
|
||||
if (options.stderr == .pipe) ev.closeAsync(stderr_pipe[1]);
|
||||
|
||||
if (prog_pipe[1] != -1) ev.close(prog_pipe[1]);
|
||||
if (prog_pipe[1] != -1) ev.closeAsync(prog_pipe[1]);
|
||||
|
||||
options.progress_node.setIpcFile(ev, .{ .handle = prog_pipe[0], .flags = .{ .nonblocking = true } });
|
||||
|
||||
|
|
@ -4497,14 +4423,14 @@ pub fn pipe2(flags: linux.O) PipeError![2]fd_t {
|
|||
}
|
||||
}
|
||||
fn destroyPipe(ev: *Evented, pipe: [2]fd_t) void {
|
||||
if (pipe[0] != -1) ev.close(pipe[0]);
|
||||
if (pipe[0] != pipe[1]) ev.close(pipe[1]);
|
||||
if (pipe[0] != -1) ev.closeAsync(pipe[0]);
|
||||
if (pipe[0] != pipe[1]) ev.closeAsync(pipe[1]);
|
||||
}
|
||||
|
||||
/// Errors that can occur between fork() and execv()
|
||||
const ForkBailError = process.SetCurrentDirError || ChdirError ||
|
||||
process.SpawnError || process.ReplaceError;
|
||||
fn setUpChild(ev: *Evented, sync: *CancelRegion.Sync, options: struct {
|
||||
fn setUpChild(sync: *CancelRegion.Sync, options: struct {
|
||||
stdin_pipe: fd_t,
|
||||
stdout_pipe: fd_t,
|
||||
stderr_pipe: fd_t,
|
||||
|
|
@ -4515,21 +4441,21 @@ fn setUpChild(ev: *Evented, sync: *CancelRegion.Sync, options: struct {
|
|||
PATH: []const u8,
|
||||
spawn: process.SpawnOptions,
|
||||
}) ForkBailError {
|
||||
try ev.setUpChildIo(
|
||||
try setUpChildIo(
|
||||
sync,
|
||||
options.spawn.stdin,
|
||||
options.stdin_pipe,
|
||||
linux.STDIN_FILENO,
|
||||
options.dev_null_fd,
|
||||
);
|
||||
try ev.setUpChildIo(
|
||||
try setUpChildIo(
|
||||
sync,
|
||||
options.spawn.stdout,
|
||||
options.stdout_pipe,
|
||||
linux.STDOUT_FILENO,
|
||||
options.dev_null_fd,
|
||||
);
|
||||
try ev.setUpChildIo(
|
||||
try setUpChildIo(
|
||||
sync,
|
||||
options.spawn.stderr,
|
||||
options.stderr_pipe,
|
||||
|
|
@ -4539,17 +4465,17 @@ fn setUpChild(ev: *Evented, sync: *CancelRegion.Sync, options: struct {
|
|||
|
||||
switch (options.spawn.cwd) {
|
||||
.inherit => {},
|
||||
.dir => |cwd_dir| try ev.fchdir(sync, cwd_dir.handle),
|
||||
.dir => |cwd_dir| try fchdir(sync, cwd_dir.handle),
|
||||
.path => |cwd_path| {
|
||||
var cwd_path_buffer: [PATH_MAX]u8 = undefined;
|
||||
const cwd_path_posix = try pathToPosix(cwd_path, &cwd_path_buffer);
|
||||
try ev.chdir(sync, cwd_path_posix);
|
||||
try chdir(sync, cwd_path_posix);
|
||||
},
|
||||
}
|
||||
|
||||
// Must happen after fchdir above, the cwd file descriptor might be
|
||||
// equal to prog_fileno and be clobbered by this dup2 call.
|
||||
if (options.prog_pipe != -1) try ev.dup2(sync, options.prog_pipe, prog_fileno);
|
||||
if (options.prog_pipe != -1) try dup2(sync, options.prog_pipe, prog_fileno);
|
||||
|
||||
if (options.spawn.gid) |gid| {
|
||||
switch (linux.errno(linux.setregid(gid, gid))) {
|
||||
|
|
@ -4589,7 +4515,7 @@ fn setUpChild(ev: *Evented, sync: *CancelRegion.Sync, options: struct {
|
|||
}
|
||||
}
|
||||
|
||||
return ev.execv(
|
||||
return execv(
|
||||
sync,
|
||||
options.spawn.expand_arg0,
|
||||
options.argv_buf.ptr[0].?,
|
||||
|
|
@ -4600,7 +4526,6 @@ fn setUpChild(ev: *Evented, sync: *CancelRegion.Sync, options: struct {
|
|||
}
|
||||
|
||||
fn setUpChildIo(
|
||||
ev: *Evented,
|
||||
sync: *CancelRegion.Sync,
|
||||
stdio: process.SpawnOptions.StdIo,
|
||||
pipe_fd: fd_t,
|
||||
|
|
@ -4608,13 +4533,13 @@ fn setUpChildIo(
|
|||
dev_null_fd: fd_t,
|
||||
) !void {
|
||||
switch (stdio) {
|
||||
.pipe => try ev.dup2(sync, pipe_fd, std_fileno),
|
||||
.pipe => try dup2(sync, pipe_fd, std_fileno),
|
||||
.close => _ = linux.close(std_fileno),
|
||||
.inherit => {},
|
||||
.ignore => try ev.dup2(sync, dev_null_fd, std_fileno),
|
||||
.ignore => try dup2(sync, dev_null_fd, std_fileno),
|
||||
.file => |file| {
|
||||
if (file.flags.nonblocking) @panic("TODO implement setUpChildIo when nonblocking file is used");
|
||||
try ev.dup2(sync, file.handle, std_fileno);
|
||||
try dup2(sync, file.handle, std_fileno);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
@ -4623,13 +4548,12 @@ pub const DupError = error{
|
|||
ProcessFdQuotaExceeded,
|
||||
SystemResources,
|
||||
} || Io.UnexpectedError || Io.Cancelable;
|
||||
pub fn dup2(ev: *Evented, sync: *CancelRegion.Sync, old_fd: fd_t, new_fd: fd_t) DupError!void {
|
||||
_ = ev;
|
||||
pub fn dup2(sync: *CancelRegion.Sync, old_fd: fd_t, new_fd: fd_t) DupError!void {
|
||||
while (true) {
|
||||
try sync.cancel_region.await(.nothing);
|
||||
switch (linux.errno(linux.dup2(old_fd, new_fd))) {
|
||||
.SUCCESS => {},
|
||||
.BUSY, .INTR => continue,
|
||||
.BUSY, .INTR => {},
|
||||
.INVAL => |err| return errnoBug(err), // invalid parameters
|
||||
.BADF => |err| return errnoBug(err), // use after free
|
||||
.MFILE => return error.ProcessFdQuotaExceeded,
|
||||
|
|
@ -4640,7 +4564,6 @@ pub fn dup2(ev: *Evented, sync: *CancelRegion.Sync, old_fd: fd_t, new_fd: fd_t)
|
|||
}
|
||||
|
||||
fn execv(
|
||||
ev: *Evented,
|
||||
sync: *CancelRegion.Sync,
|
||||
arg0_expand: process.ArgExpansion,
|
||||
file: [*:0]const u8,
|
||||
|
|
@ -4649,7 +4572,8 @@ fn execv(
|
|||
PATH: []const u8,
|
||||
) process.ReplaceError {
|
||||
const file_slice = std.mem.sliceTo(file, 0);
|
||||
if (std.mem.findScalar(u8, file_slice, '/') != null) return ev.execvPath(sync, file, child_argv, env_block);
|
||||
if (std.mem.findScalar(u8, file_slice, '/') != null)
|
||||
return execvPath(sync, file, child_argv, env_block);
|
||||
|
||||
// Use of PATH_MAX here is valid as the path_buf will be passed
|
||||
// directly to the operating system in posixExecvPath.
|
||||
|
|
@ -4677,7 +4601,7 @@ fn execv(
|
|||
.expand => child_argv[0] = full_path,
|
||||
.no_expand => {},
|
||||
}
|
||||
err = ev.execvPath(sync, full_path, child_argv, env_block);
|
||||
err = execvPath(sync, full_path, child_argv, env_block);
|
||||
switch (err) {
|
||||
error.AccessDenied => seen_eacces = true,
|
||||
error.FileNotFound, error.NotDir => {},
|
||||
|
|
@ -4689,13 +4613,11 @@ fn execv(
|
|||
}
|
||||
/// This function ignores PATH environment variable.
|
||||
pub fn execvPath(
|
||||
ev: *Evented,
|
||||
sync: *CancelRegion.Sync,
|
||||
path: [*:0]const u8,
|
||||
child_argv: [*:null]const ?[*:0]const u8,
|
||||
env_block: process.Environ.PosixBlock,
|
||||
) process.ReplaceError {
|
||||
_ = ev;
|
||||
try sync.cancel_region.await(.nothing);
|
||||
switch (linux.errno(linux.execve(path, child_argv, env_block.slice.ptr))) {
|
||||
.FAULT => |err| return errnoBug(err), // Bad pointer parameter.
|
||||
|
|
@ -4766,7 +4688,7 @@ fn childWait(userdata: ?*anyopaque, child: *process.Child) process.Child.WaitErr
|
|||
child.resource_usage_statistics.rusage = rusage;
|
||||
break;
|
||||
},
|
||||
.INTR, .CANCELED => continue,
|
||||
.INTR, .CANCELED => {},
|
||||
.CHILD => |err| return errnoBug(err), // Double-free.
|
||||
else => |err| return unexpectedErrno(err),
|
||||
}
|
||||
|
|
@ -4781,7 +4703,7 @@ fn childWait(userdata: ?*anyopaque, child: *process.Child) process.Child.WaitErr
|
|||
_, .CONTINUED => .{ .unknown = status },
|
||||
};
|
||||
},
|
||||
.INTR, .CANCELED => continue,
|
||||
.INTR, .CANCELED => {},
|
||||
.CHILD => |err| return errnoBug(err), // Double-free.
|
||||
else => |err| return unexpectedErrno(err),
|
||||
}
|
||||
|
|
@ -4798,7 +4720,7 @@ fn childKill(userdata: ?*anyopaque, child: *process.Child) void {
|
|||
const pid = child.id.?;
|
||||
while (true) switch (linux.errno(linux.kill(pid, .TERM))) {
|
||||
.SUCCESS => break,
|
||||
.INTR => continue,
|
||||
.INTR => {},
|
||||
.PERM => return,
|
||||
.INVAL => |err| return errnoBug(err) catch {},
|
||||
.SRCH => |err| return errnoBug(err) catch {},
|
||||
|
|
@ -4830,7 +4752,7 @@ fn childKill(userdata: ?*anyopaque, child: *process.Child) void {
|
|||
ev.yield(null, .nothing);
|
||||
switch (maybe_sync.cancel_region.errno()) {
|
||||
.SUCCESS => return,
|
||||
.INTR, .CANCELED => continue,
|
||||
.INTR, .CANCELED => {},
|
||||
.CHILD => |err| return errnoBug(err) catch {}, // Double-free.
|
||||
else => |err| return unexpectedErrno(err) catch {},
|
||||
}
|
||||
|
|
@ -4839,15 +4761,15 @@ fn childKill(userdata: ?*anyopaque, child: *process.Child) void {
|
|||
|
||||
fn childCleanup(ev: *Evented, child: *process.Child) void {
|
||||
if (child.stdin) |*stdin| {
|
||||
ev.close(stdin.handle);
|
||||
ev.closeAsync(stdin.handle);
|
||||
child.stdin = null;
|
||||
}
|
||||
if (child.stdout) |*stdout| {
|
||||
ev.close(stdout.handle);
|
||||
ev.closeAsync(stdout.handle);
|
||||
child.stdout = null;
|
||||
}
|
||||
if (child.stderr) |*stderr| {
|
||||
ev.close(stderr.handle);
|
||||
ev.closeAsync(stderr.handle);
|
||||
child.stderr = null;
|
||||
}
|
||||
child.id = null;
|
||||
|
|
@ -4953,14 +4875,10 @@ fn sleep(userdata: ?*anyopaque, timeout: Io.Timeout) Io.Cancelable!void {
|
|||
.resv = 0,
|
||||
};
|
||||
ev.yield(null, .nothing);
|
||||
switch (cancel_region.errno()) {
|
||||
// Handles SUCCESS as well as clock not available and unexpected
|
||||
// errors. The user had a chance to check clock resolution before
|
||||
// getting here, which would have reported 0, making this a legal
|
||||
// amount of time to sleep.
|
||||
else => return,
|
||||
.INTR, .CANCELED => return error.Canceled,
|
||||
}
|
||||
// Handles SUCCESS as well as clock not available and unexpected
|
||||
// errors. The user had a chance to check clock resolution before
|
||||
// getting here, which would have reported 0, making this a legal
|
||||
// amount of time to sleep.
|
||||
}
|
||||
|
||||
fn random(userdata: ?*anyopaque, buffer: []u8) void {
|
||||
|
|
@ -5037,7 +4955,7 @@ fn netBindIp(
|
|||
var maybe_sync: CancelRegion.Sync.Maybe = .{ .cancel_region = .init() };
|
||||
defer maybe_sync.deinit(ev);
|
||||
const socket_fd = try ev.socket(&maybe_sync.cancel_region, family, options);
|
||||
errdefer ev.close(socket_fd);
|
||||
errdefer ev.closeAsync(socket_fd);
|
||||
var storage: PosixAddress = undefined;
|
||||
var addr_len = addressToPosix(address, &storage);
|
||||
try ev.bind(&maybe_sync.cancel_region, socket_fd, &storage.any, addr_len);
|
||||
|
|
@ -5204,23 +5122,19 @@ fn netReceive(
|
|||
.data = data,
|
||||
.control = if (msg.control) |ptr| @as([*]u8, @ptrCast(ptr))[0..msg.controllen] else message.control,
|
||||
.flags = .{
|
||||
.eor = (msg.flags & linux.MSG.EOR) != 0,
|
||||
.trunc = (msg.flags & linux.MSG.TRUNC) != 0,
|
||||
.ctrunc = (msg.flags & linux.MSG.CTRUNC) != 0,
|
||||
.oob = (msg.flags & linux.MSG.OOB) != 0,
|
||||
.errqueue = if (@hasDecl(linux.MSG, "ERRQUEUE")) (msg.flags & linux.MSG.ERRQUEUE) != 0 else false,
|
||||
.eor = msg.flags & linux.MSG.EOR != 0,
|
||||
.trunc = msg.flags & linux.MSG.TRUNC != 0,
|
||||
.ctrunc = msg.flags & linux.MSG.CTRUNC != 0,
|
||||
.oob = msg.flags & linux.MSG.OOB != 0,
|
||||
.errqueue = msg.flags & linux.MSG.ERRQUEUE != 0,
|
||||
},
|
||||
};
|
||||
message_i += 1;
|
||||
continue;
|
||||
},
|
||||
.AGAIN => unreachable,
|
||||
.INTR, .CANCELED => {
|
||||
if (deadline) |d| {
|
||||
if (now(ev, d.clock).nanoseconds >= d.raw.nanoseconds) return .{ error.Timeout, message_i };
|
||||
}
|
||||
continue;
|
||||
},
|
||||
.INTR, .CANCELED => if (deadline) |d| if (now(ev, d.clock).nanoseconds >= d.raw.nanoseconds)
|
||||
return .{ error.Timeout, message_i },
|
||||
|
||||
.BADF => |err| return .{ errnoBug(err), message_i },
|
||||
.NFILE => return .{ error.SystemFdQuotaExceeded, message_i },
|
||||
|
|
@ -5323,7 +5237,7 @@ fn netShutdown(
|
|||
ev.yield(null, .nothing);
|
||||
switch (cancel_region.errno()) {
|
||||
.SUCCESS => return,
|
||||
.INTR, .CANCELED => continue,
|
||||
.INTR, .CANCELED => {},
|
||||
.BADF, .NOTSOCK, .INVAL => |err| return errnoBug(err),
|
||||
.NOTCONN => return error.SocketUnconnected,
|
||||
.NOBUFS => return error.SystemResources,
|
||||
|
|
@ -5393,7 +5307,7 @@ fn bind(
|
|||
ev.yield(null, .nothing);
|
||||
switch (cancel_region.errno()) {
|
||||
.SUCCESS => return,
|
||||
.INTR, .CANCELED => continue,
|
||||
.INTR, .CANCELED => {},
|
||||
.ADDRINUSE => return error.AddressInUse,
|
||||
.BADF => |err| return errnoBug(err), // File descriptor used after closed.
|
||||
.INVAL => |err| return errnoBug(err), // invalid parameters
|
||||
|
|
@ -5407,13 +5321,12 @@ fn bind(
|
|||
}
|
||||
}
|
||||
|
||||
fn chdir(ev: *Evented, sync: *CancelRegion.Sync, path: [*:0]const u8) ChdirError!void {
|
||||
_ = ev;
|
||||
fn chdir(sync: *CancelRegion.Sync, path: [*:0]const u8) ChdirError!void {
|
||||
while (true) {
|
||||
try sync.cancel_region.await(.nothing);
|
||||
switch (linux.errno(linux.chdir(path))) {
|
||||
.SUCCESS => return,
|
||||
.INTR => continue,
|
||||
.INTR => {},
|
||||
.ACCES => return error.AccessDenied,
|
||||
.IO => return error.FileSystem,
|
||||
.LOOP => return error.SymLinkLoop,
|
||||
|
|
@ -5429,6 +5342,36 @@ fn chdir(ev: *Evented, sync: *CancelRegion.Sync, path: [*:0]const u8) ChdirError
|
|||
}
|
||||
|
||||
fn close(ev: *Evented, fd: fd_t) void {
|
||||
var cancel_region: CancelRegion = .initBlocked();
|
||||
defer cancel_region.deinit();
|
||||
const thread = cancel_region.awaitIoUring() catch |err| switch (err) {
|
||||
error.Canceled => unreachable, // blocked
|
||||
};
|
||||
thread.enqueue().* = .{
|
||||
.opcode = .CLOSE,
|
||||
.flags = 0,
|
||||
.ioprio = 0,
|
||||
.fd = fd,
|
||||
.off = 0,
|
||||
.addr = 0,
|
||||
.len = 0,
|
||||
.rw_flags = 0,
|
||||
.user_data = @intFromPtr(cancel_region.fiber),
|
||||
.buf_index = 0,
|
||||
.personality = 0,
|
||||
.splice_fd_in = 0,
|
||||
.addr3 = 0,
|
||||
.resv = 0,
|
||||
};
|
||||
ev.yield(null, .nothing);
|
||||
switch (cancel_region.errno()) {
|
||||
.BADF => recoverableOsBugDetected(), // Always a race condition.
|
||||
.INTR => {}, // This is still a success. See https://github.com/ziglang/zig/issues/2425
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
|
||||
fn closeAsync(ev: *Evented, fd: fd_t) void {
|
||||
_ = ev;
|
||||
const thread: *Thread = .current();
|
||||
thread.enqueue().* = .{
|
||||
|
|
@ -5449,14 +5392,13 @@ fn close(ev: *Evented, fd: fd_t) void {
|
|||
};
|
||||
}
|
||||
|
||||
fn fchdir(ev: *Evented, sync: *CancelRegion.Sync, dir: fd_t) process.SetCurrentDirError!void {
|
||||
_ = ev;
|
||||
fn fchdir(sync: *CancelRegion.Sync, dir: fd_t) process.SetCurrentDirError!void {
|
||||
if (dir == linux.AT.FDCWD) return;
|
||||
while (true) {
|
||||
try sync.cancel_region.await(.nothing);
|
||||
switch (linux.errno(linux.fchdir(dir))) {
|
||||
.SUCCESS => return,
|
||||
.INTR => continue,
|
||||
.INTR => {},
|
||||
.ACCES => return error.AccessDenied,
|
||||
.NOTDIR => return error.NotDir,
|
||||
.IO => return error.FileSystem,
|
||||
|
|
@ -5479,7 +5421,7 @@ fn fchmodat(
|
|||
try sync.cancel_region.await(.nothing);
|
||||
switch (linux.errno(linux.fchmodat2(dir, path, mode, flags))) {
|
||||
.SUCCESS => return,
|
||||
.INTR => continue,
|
||||
.INTR => {},
|
||||
.BADF => |err| return errnoBug(err),
|
||||
.FAULT => |err| return errnoBug(err),
|
||||
.INVAL => |err| return errnoBug(err),
|
||||
|
|
@ -5511,7 +5453,7 @@ fn fchownat(
|
|||
try sync.cancel_region.await(.nothing);
|
||||
switch (linux.errno(linux.fchownat(dir, path, owner, group, flags))) {
|
||||
.SUCCESS => return,
|
||||
.INTR => continue,
|
||||
.INTR => {},
|
||||
.BADF => |err| return errnoBug(err), // likely fd refers to directory opened without `Dir.OpenOptions.iterate`
|
||||
.FAULT => |err| return errnoBug(err),
|
||||
.INVAL => |err| return errnoBug(err),
|
||||
|
|
@ -5543,7 +5485,7 @@ fn flock(
|
|||
.exclusive => LOCK.EX,
|
||||
})))) {
|
||||
.SUCCESS => return,
|
||||
.INTR => continue,
|
||||
.INTR => {},
|
||||
.BADF => |err| return errnoBug(err),
|
||||
.INVAL => |err| return errnoBug(err), // invalid parameters
|
||||
.NOLCK => return error.SystemResources,
|
||||
|
|
@ -5593,7 +5535,7 @@ fn getsockname(
|
|||
try sync.cancel_region.await(.nothing);
|
||||
switch (linux.errno(linux.getsockname(socket_fd, addr, addr_len))) {
|
||||
.SUCCESS => return,
|
||||
.INTR => continue,
|
||||
.INTR => {},
|
||||
.BADF => |err| return errnoBug(err), // File descriptor used after closed.
|
||||
.FAULT => |err| return errnoBug(err),
|
||||
.INVAL => |err| return errnoBug(err), // invalid parameters
|
||||
|
|
@ -5634,7 +5576,7 @@ fn linkat(
|
|||
ev.yield(null, .nothing);
|
||||
switch (cancel_region.errno()) {
|
||||
.SUCCESS => return,
|
||||
.INTR, .CANCELED => continue,
|
||||
.INTR, .CANCELED => {},
|
||||
.ACCES => return error.AccessDenied,
|
||||
.DQUOT => return error.DiskQuota,
|
||||
.EXIST => return error.PathAlreadyExists,
|
||||
|
|
@ -5674,7 +5616,7 @@ fn lseek(
|
|||
8 => linux.lseek(fd, @bitCast(offset), whence),
|
||||
})) {
|
||||
.SUCCESS => return,
|
||||
.INTR => continue,
|
||||
.INTR => {},
|
||||
.BADF => |err| return errnoBug(err), // File descriptor used after closed.
|
||||
.INVAL => return error.Unseekable,
|
||||
.OVERFLOW => return error.Unseekable,
|
||||
|
|
@ -5717,7 +5659,7 @@ fn openat(
|
|||
const completion = cancel_region.completion();
|
||||
switch (completion.errno()) {
|
||||
.SUCCESS => return completion.result,
|
||||
.INTR, .CANCELED => continue,
|
||||
.INTR, .CANCELED => {},
|
||||
.FAULT => |err| return errnoBug(err),
|
||||
.INVAL => return error.BadPathName,
|
||||
.BADF => |err| return errnoBug(err), // File descriptor used after closed.
|
||||
|
|
@ -5779,7 +5721,7 @@ fn preadv(
|
|||
const completion = cancel_region.completion();
|
||||
switch (completion.errno()) {
|
||||
.SUCCESS => return @as(u32, @bitCast(completion.result)),
|
||||
.INTR, .CANCELED => continue,
|
||||
.INTR, .CANCELED => {},
|
||||
.INVAL => |err| return errnoBug(err),
|
||||
.FAULT => |err| return errnoBug(err),
|
||||
.AGAIN => return error.WouldBlock,
|
||||
|
|
@ -5826,7 +5768,7 @@ fn pwritev(
|
|||
const completion = cancel_region.completion();
|
||||
switch (completion.errno()) {
|
||||
.SUCCESS => return @as(u32, @bitCast(completion.result)),
|
||||
.INTR, .CANCELED => continue,
|
||||
.INTR, .CANCELED => {},
|
||||
.INVAL => |err| return errnoBug(err),
|
||||
.FAULT => |err| return errnoBug(err),
|
||||
.AGAIN => return error.WouldBlock,
|
||||
|
|
@ -5876,7 +5818,7 @@ fn realPath(
|
|||
const rc = linux.readlink(proc_path, out_buffer.ptr, out_buffer.len);
|
||||
switch (linux.errno(rc)) {
|
||||
.SUCCESS => return rc,
|
||||
.INTR => continue,
|
||||
.INTR => {},
|
||||
.ACCES => return error.AccessDenied,
|
||||
.FAULT => |err| return errnoBug(err),
|
||||
.IO => return error.FileSystem,
|
||||
|
|
@ -5921,7 +5863,7 @@ fn renameat(
|
|||
ev.yield(null, .nothing);
|
||||
switch (cancel_region.errno()) {
|
||||
.SUCCESS => return,
|
||||
.INTR, .CANCELED => continue,
|
||||
.INTR, .CANCELED => {},
|
||||
.ACCES => return error.AccessDenied,
|
||||
.PERM => return error.PermissionDenied,
|
||||
.BUSY => return error.FileBusy,
|
||||
|
|
@ -5988,7 +5930,7 @@ fn setsockopt(
|
|||
ev.yield(null, .nothing);
|
||||
switch (cancel_region.errno()) {
|
||||
.SUCCESS => return,
|
||||
.INTR, .CANCELED => continue,
|
||||
.INTR, .CANCELED => {},
|
||||
.BADF => |err| return errnoBug(err), // File descriptor used after closed.
|
||||
.NOTSOCK => |err| return errnoBug(err),
|
||||
.INVAL => |err| return errnoBug(err),
|
||||
|
|
@ -6039,7 +5981,7 @@ fn socket(
|
|||
const completion = cancel_region.completion();
|
||||
switch (completion.errno()) {
|
||||
.SUCCESS => break completion.result,
|
||||
.INTR, .CANCELED => continue,
|
||||
.INTR, .CANCELED => {},
|
||||
.AFNOSUPPORT => return error.AddressFamilyUnsupported,
|
||||
.INVAL => return error.ProtocolUnsupportedBySystem,
|
||||
.MFILE => return error.ProcessFdQuotaExceeded,
|
||||
|
|
@ -6051,7 +5993,7 @@ fn socket(
|
|||
else => |err| return unexpectedErrno(err),
|
||||
}
|
||||
};
|
||||
errdefer ev.close(socket_fd);
|
||||
errdefer ev.closeAsync(socket_fd);
|
||||
|
||||
if (options.ip6_only) {
|
||||
if (linux.IPV6 == void) return error.OptionUnsupported;
|
||||
|
|
@ -6101,7 +6043,7 @@ fn statx(
|
|||
ev.yield(null, .nothing);
|
||||
switch (cancel_region.errno()) {
|
||||
.SUCCESS => return statFromLinux(&statx_buf),
|
||||
.INTR, .CANCELED => continue,
|
||||
.INTR, .CANCELED => {},
|
||||
.ACCES => return error.AccessDenied,
|
||||
.BADF => |err| return errnoBug(err), // File descriptor used after closed.
|
||||
.FAULT => |err| return errnoBug(err),
|
||||
|
|
@ -6140,7 +6082,7 @@ fn utimensat(
|
|||
try sync.cancel_region.await(.nothing);
|
||||
switch (linux.errno(linux.utimensat(dir, path, times, flags))) {
|
||||
.SUCCESS => return,
|
||||
.INTR => continue,
|
||||
.INTR => {},
|
||||
.BADF => |err| return errnoBug(err), // always a race condition
|
||||
.FAULT => |err| return errnoBug(err),
|
||||
.INVAL => |err| return errnoBug(err),
|
||||
|
|
@ -6152,19 +6094,33 @@ fn utimensat(
|
|||
}
|
||||
}
|
||||
|
||||
fn writeAll(
|
||||
ev: *Evented,
|
||||
cancel_region: *CancelRegion,
|
||||
fd: fd_t,
|
||||
buffer: []const u8,
|
||||
) (File.Writer.Error || error{EndOfStream})!void {
|
||||
fn writeAllSync(sync: *CancelRegion.Sync, fd: fd_t, buffer: []const u8) File.Writer.Error!void {
|
||||
var index: usize = 0;
|
||||
while (buffer.len - index != 0) {
|
||||
const len = try ev.pwritev(cancel_region, fd, &.{
|
||||
.{ .base = buffer[index..].ptr, .len = buffer.len - index },
|
||||
}, null);
|
||||
if (len == 0) return error.EndOfStream;
|
||||
index += len;
|
||||
while (buffer.len - index != 0) index += try writeSync(sync, fd, buffer[index..]);
|
||||
}
|
||||
|
||||
fn writeSync(sync: *CancelRegion.Sync, fd: fd_t, buffer: []const u8) File.Writer.Error!usize {
|
||||
while (true) {
|
||||
try sync.cancel_region.await(.nothing);
|
||||
const rc = linux.write(fd, buffer.ptr, buffer.len);
|
||||
switch (linux.errno(rc)) {
|
||||
.SUCCESS => return @intCast(rc),
|
||||
.INTR => {},
|
||||
.INVAL => |err| return errnoBug(err),
|
||||
.FAULT => |err| return errnoBug(err),
|
||||
.AGAIN => return error.WouldBlock,
|
||||
.BADF => return error.NotOpenForWriting, // Can be a race condition.
|
||||
.DESTADDRREQ => |err| return errnoBug(err), // `connect` was never called.
|
||||
.DQUOT => return error.DiskQuota,
|
||||
.FBIG => return error.FileTooBig,
|
||||
.IO => return error.InputOutput,
|
||||
.NOSPC => return error.NoSpaceLeft,
|
||||
.PERM => return error.PermissionDenied,
|
||||
.PIPE => return error.BrokenPipe,
|
||||
.CONNRESET => |err| return errnoBug(err), // Not a socket handle.
|
||||
.BUSY => return error.DeviceBusy,
|
||||
else => |err| return unexpectedErrno(err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -282,40 +282,6 @@ test "Group.concurrent" {
|
|||
try testing.expectEqualSlices(usize, &.{ 45, 245 }, &results);
|
||||
}
|
||||
|
||||
test "select" {
|
||||
const io = testing.io;
|
||||
|
||||
var queue: Io.Queue(u8) = .init(&.{});
|
||||
|
||||
var get_a = io.concurrent(Io.Queue(u8).getOne, .{ &queue, io }) catch |err| switch (err) {
|
||||
error.ConcurrencyUnavailable => {
|
||||
try testing.expect(builtin.single_threaded);
|
||||
return;
|
||||
},
|
||||
};
|
||||
defer _ = get_a.cancel(io) catch {};
|
||||
|
||||
var get_b = try io.concurrent(Io.Queue(u8).getOne, .{ &queue, io });
|
||||
defer _ = get_b.cancel(io) catch {};
|
||||
|
||||
var timeout = io.async(Io.sleep, .{ io, .fromMilliseconds(1), .awake });
|
||||
defer timeout.cancel(io) catch {};
|
||||
|
||||
switch (try io.select(.{
|
||||
.get_a = &get_a,
|
||||
.get_b = &get_b,
|
||||
.timeout = &timeout,
|
||||
})) {
|
||||
.get_a => return error.TestFailure,
|
||||
.get_b => return error.TestFailure,
|
||||
.timeout => {
|
||||
queue.close(io);
|
||||
try testing.expectError(error.Closed, get_a.await(io));
|
||||
try testing.expectError(error.Closed, get_b.await(io));
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn testQueue(comptime len: usize) !void {
|
||||
const io = testing.io;
|
||||
var buf: [len]usize = undefined;
|
||||
|
|
|
|||
|
|
@ -201,6 +201,10 @@ pub fn enter(self: *IoUring, to_submit: u32, min_complete: u32, flags: u32) !u32
|
|||
// The kernel believes our `self.fd` does not refer to an io_uring instance,
|
||||
// or the opcode is valid but not supported by this kernel (more likely):
|
||||
.OPNOTSUPP => return error.OpcodeNotSupported,
|
||||
// The thread submitting the work is invalid. This may occur if IORING_ENTER_GETEVENTS
|
||||
// and IORING_SETUP_DEFER_TASKRUN is set, but the submitting thread is not the thread
|
||||
// that initially created or enabled the io_uring associated with fd.
|
||||
.EXIST => return error.InvalidThread,
|
||||
// The operation was interrupted by a delivery of a signal before it could complete.
|
||||
// This can happen while waiting for events with IORING_ENTER_GETEVENTS:
|
||||
.INTR => return error.SignalInterrupt,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue