authorgravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2026-01-03 21:33:46+01:00
committergravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2026-01-03 21:33:46+01:00
log8226d706e2cb69845c400ce22d8b263c4a390f11
tree9ddb96b16c8d6f7ffb141e9d7792f07274c0f8c7
parent04226193ccb69f50936e47804be56bd1bdc316d9
parent4de33579d8d8fdf310cd1a496eb68a7c5c62d81f

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

17 files changed, 3721 insertions(+), 2778 deletions(-)

lib/compiler/build_runner.zig+1-1
......@@ -849,7 +849,7 @@ fn runStepNames(
849849 defer f.deinit();
850850
851851 f.start();
852 f.waitAndPrintReport();
852 try f.waitAndPrintReport();
853853 }
854854
855855 // Every test has a state
lib/std/Build/Fuzz.zig+2-2
......@@ -513,11 +513,11 @@ fn addEntryPoint(fuzz: *Fuzz, coverage_id: u64, addr: u64) error{ AlreadyReporte
513513 try coverage_map.entry_points.append(fuzz.gpa, @intCast(index));
514514}
515515
516pub fn waitAndPrintReport(fuzz: *Fuzz) void {
516pub fn waitAndPrintReport(fuzz: *Fuzz) Io.Cancelable!void {
517517 assert(fuzz.mode == .limit);
518518 const io = fuzz.io;
519519
520 fuzz.group.awaitUncancelable(io);
520 try fuzz.group.await(io);
521521 fuzz.group = .init;
522522
523523 std.debug.print("======= FUZZING REPORT =======\n", .{});
lib/std/Io.zig+41-58
......@@ -631,7 +631,7 @@ pub const VTable = struct {
631631 /// Copied and then passed to `start`.
632632 context: []const u8,
633633 context_alignment: std.mem.Alignment,
634 start: *const fn (*Group, context: *const anyopaque) Cancelable!void,
634 start: *const fn (context: *const anyopaque) Cancelable!void,
635635 ) void,
636636 /// Thread-safe.
637637 groupConcurrent: *const fn (
......@@ -642,7 +642,7 @@ pub const VTable = struct {
642642 /// Copied and then passed to `start`.
643643 context: []const u8,
644644 context_alignment: std.mem.Alignment,
645 start: *const fn (*Group, context: *const anyopaque) Cancelable!void,
645 start: *const fn (context: *const anyopaque) Cancelable!void,
646646 ) ConcurrentError!void,
647647 groupAwait: *const fn (?*anyopaque, *Group, token: *anyopaque) Cancelable!void,
648648 groupCancel: *const fn (?*anyopaque, *Group, token: *anyopaque) void,
......@@ -1050,40 +1050,40 @@ pub fn Future(Result: type) type {
10501050 };
10511051}
10521052
1053/// An unordered set of tasks which can only be awaited or canceled as a whole.
1054/// Tasks are spawned in the group with `Group.async` and `Group.concurrent`.
1055///
1056/// The resources associated with each task are *guaranteed* to be released when
1057/// the individual task returns, as opposed to when the whole group completes or
1058/// is awaited. For this reason, it is not a resource leak to have a long-lived
1059/// group which concurrent tasks are repeatedly added to. However, asynchronous
1060/// tasks are not guaranteed to run until `Group.await` or `Group.cancel` is
1061/// called, so adding async tasks to a group without ever awaiting it may leak
1062/// resources.
10531063pub const Group = struct {
1054 state: usize,
1055 context: ?*anyopaque,
10561064 /// This value indicates whether or not a group has pending tasks. `null`
10571065 /// means there are no pending tasks, and no resources associated with the
10581066 /// group, so `await` and `cancel` return immediately without calling the
10591067 /// implementation. This means that `token` must be accessed atomically to
10601068 /// avoid racing with the check in `await` and `cancel`.
10611069 token: std.atomic.Value(?*anyopaque),
1070 /// This value is available for the implementation to use as it wishes.
1071 state: usize,
10621072
1063 pub const init: Group = .{ .state = 0, .context = null, .token = .init(null) };
1073 pub const init: Group = .{ .token = .init(null), .state = 0 };
10641074
1065 /// Calls `function` with `args` asynchronously. The resource spawned is
1066 /// owned by the group.
1067 ///
1068 /// `function` *may* be called immediately, before `async` returns.
1075 /// Equivalent to `Io.async`, except the task is spawned in this `Group`
1076 /// instead of becoming associated with a `Future`.
10691077 ///
1070 /// When this function returns, it is guaranteed that `function` has
1071 /// already been called and completed, or it has successfully been assigned
1072 /// a unit of concurrency.
1078 /// The return type of `function` must be coercible to `Cancelable!void`.
10731079 ///
1074 /// After this is called, `await` or `cancel` must be called before the
1075 /// group is deinitialized.
1076 ///
1077 /// Threadsafe.
1078 ///
1079 /// See also:
1080 /// * `concurrent`
1081 /// * `Io.async`
1080 /// Once this function is called, there are resources associated with the
1081 /// group. To release those resources, `Group.await` or `Group.cancel` must
1082 /// eventually be called.
10821083 pub fn async(g: *Group, io: Io, function: anytype, args: std.meta.ArgsTuple(@TypeOf(function))) void {
10831084 const Args = @TypeOf(args);
10841085 const TypeErased = struct {
1085 fn start(group: *Group, context: *const anyopaque) Cancelable!void {
1086 _ = group;
1086 fn start(context: *const anyopaque) Cancelable!void {
10871087 const args_casted: *const Args = @ptrCast(@alignCast(context));
10881088 return @call(.auto, function, args_casted.*);
10891089 }
......@@ -1091,27 +1091,18 @@ pub const Group = struct {
10911091 io.vtable.groupAsync(io.userdata, g, @ptrCast(&args), .of(Args), TypeErased.start);
10921092 }
10931093
1094 /// Calls `function` with `args`, such that the function is not guaranteed
1095 /// to have returned until `await` is called, allowing the caller to
1096 /// progress while waiting for any `Io` operations.
1097 ///
1098 /// The resource spawned is owned by the group; after this is called,
1099 /// `await` or `cancel` must be called before the group is deinitialized.
1094 /// Equivalent to `Io.concurrent`, except the task is spawned in this
1095 /// `Group` instead of becoming associated with a `Future`.
11001096 ///
1101 /// This has stronger guarantee than `async`, placing restrictions on what kind
1102 /// of `Io` implementations are supported. By calling `async` instead, one
1103 /// allows, for example, stackful single-threaded blocking I/O.
1097 /// The return type of `function` must be coercible to `Cancelable!void`.
11041098 ///
1105 /// Threadsafe.
1106 ///
1107 /// See also:
1108 /// * `async`
1109 /// * `Io.concurrent`
1099 /// Once this function is called, there are resources associated with the
1100 /// group. To release those resources, `Group.await` or `Group.cancel` must
1101 /// eventually be called.
11101102 pub fn concurrent(g: *Group, io: Io, function: anytype, args: std.meta.ArgsTuple(@TypeOf(function))) ConcurrentError!void {
11111103 const Args = @TypeOf(args);
11121104 const TypeErased = struct {
1113 fn start(group: *Group, context: *const anyopaque) Cancelable!void {
1114 _ = group;
1105 fn start(context: *const anyopaque) Cancelable!void {
11151106 const args_casted: *const Args = @ptrCast(@alignCast(context));
11161107 return @call(.auto, function, args_casted.*);
11171108 }
......@@ -1120,7 +1111,9 @@ pub const Group = struct {
11201111 }
11211112
11221113 /// Blocks until all tasks of the group finish. During this time,
1123 /// cancelation requests propagate to all members of the group.
1114 /// cancelation requests propagate to all members of the group, and
1115 /// will also cause `error.Canceled` to be returned when the group
1116 /// does ultimately finish.
11241117 ///
11251118 /// Idempotent. Not threadsafe.
11261119 ///
......@@ -1133,17 +1126,6 @@ pub const Group = struct {
11331126 assert(g.token.raw == null);
11341127 }
11351128
1136 /// Equivalent to `await` but temporarily blocks cancelation while waiting.
1137 pub fn awaitUncancelable(g: *Group, io: Io) void {
1138 const token = g.token.load(.acquire) orelse return;
1139 const prev = swapCancelProtection(io, .blocked);
1140 defer _ = swapCancelProtection(io, prev);
1141 io.vtable.groupAwait(io.userdata, g, token) catch |err| switch (err) {
1142 error.Canceled => unreachable,
1143 };
1144 assert(g.token.raw == null);
1145 }
1146
11471129 /// Equivalent to `await` but immediately requests cancelation on all
11481130 /// members of the group.
11491131 ///
......@@ -1263,19 +1245,20 @@ pub fn Select(comptime U: type) type {
12631245 function: anytype,
12641246 args: std.meta.ArgsTuple(@TypeOf(function)),
12651247 ) void {
1266 const Args = @TypeOf(args);
1267 const TypeErased = struct {
1268 fn start(group: *Group, context: *const anyopaque) Cancelable!void {
1269 const args_casted: *const Args = @ptrCast(@alignCast(context));
1270 const unerased_select: *S = @fieldParentPtr("group", group);
1271 const elem = @unionInit(U, @tagName(field), @call(.auto, function, args_casted.*));
1272 unerased_select.queue.putOneUncancelable(unerased_select.io, elem) catch |err| switch (err) {
1248 const Context = struct {
1249 select: *S,
1250 args: @TypeOf(args),
1251 fn start(type_erased_context: *const anyopaque) Cancelable!void {
1252 const context: *const @This() = @ptrCast(@alignCast(type_erased_context));
1253 const elem = @unionInit(U, @tagName(field), @call(.auto, function, context.args));
1254 context.select.queue.putOneUncancelable(context.select.io, elem) catch |err| switch (err) {
12731255 error.Closed => unreachable,
12741256 };
12751257 }
12761258 };
1259 const context: Context = .{ .select = s, .args = args };
12771260 _ = @atomicRmw(usize, &s.outstanding, .Add, 1, .monotonic);
1278 s.io.vtable.groupAsync(s.io.userdata, &s.group, @ptrCast(&args), .of(Args), TypeErased.start);
1261 s.io.vtable.groupAsync(s.io.userdata, &s.group, @ptrCast(&context), .of(Context), Context.start);
12791262 }
12801263
12811264 /// Blocks until another task of the select finishes.
lib/std/Io/Threaded.zig+3373-2550
......@@ -37,9 +37,8 @@ cpu_count_error: ?std.Thread.CpuCountError,
3737/// available count, subtract this from either `async_limit` or
3838/// `concurrent_limit`.
3939busy_count: usize = 0,
40main_thread: Thread,
40worker_threads: std.atomic.Value(?*Thread),
4141pid: Pid = .unknown,
42robust_cancel: RobustCancel,
4342
4443wsa: if (is_windows) Wsa else struct {} = .{},
4544
......@@ -105,13 +104,6 @@ pub const Environ = struct {
105104 };
106105};
107106
108pub const RobustCancel = if (std.Thread.use_pthreads or native_os == .linux) enum {
109 enabled,
110 disabled,
111} else enum {
112 disabled,
113};
114
115107pub const Pid = if (native_os == .linux) enum(posix.pid_t) {
116108 unknown = 0,
117109 _,
......@@ -153,129 +145,507 @@ pub const UseFchmodat2 = if (have_fchmodat2 and !have_fchmodat_flags) enum {
153145 pub const default: UseFchmodat2 = .disabled;
154146};
155147
156const Thread = struct {
157 /// The value that needs to be passed to pthread_kill or tgkill in order to
158 /// send a signal.
159 signal_id: SignaleeId,
160 current_closure: ?*Closure,
161 /// Only populated if `current_closure != null`. Indicates the current cancel protection mode.
162 cancel_protection: Io.CancelProtection,
163
164 const SignaleeId = if (std.Thread.use_pthreads) std.c.pthread_t else std.Thread.Id;
148const Runnable = struct {
149 node: std.SinglyLinkedList.Node,
150 startFn: *const fn (*Runnable, *Thread, *Threaded) void,
151};
165152
166 threadlocal var current: ?*Thread = null;
153const Group = struct {
154 ptr: *Io.Group,
167155
168 fn getCurrent(t: *Threaded) *Thread {
169 return current orelse return &t.main_thread;
156 /// Returns a correctly-typed pointer to the `Io.Group.token` field.
157 ///
158 /// The status indicates how many pending tasks are in the group, whether the group has been
159 /// canceled, and whether the group has been awaited.
160 ///
161 /// Note that the zero value of `Status` intentionally represents the initial group state (empty
162 /// with no awaiters). This is a requirement of `Io.Group`.
163 fn status(g: Group) *std.atomic.Value(Status) {
164 return @ptrCast(&g.ptr.token);
165 }
166 /// Returns a correctly-typed pointer to the `Io.Group.state` field. The double-pointer here is
167 /// intentional, because the `state` field itself stores a pointer, and this function returns a
168 /// pointer to that field.
169 ///
170 /// On completion of the whole group, if `status` indicates that there is an awaiter, the last
171 /// task must increment this `u32` and do a futex wake on it to signal that awaiter.
172 fn awaiter(g: Group) **std.atomic.Value(u32) {
173 return @ptrCast(&g.ptr.state);
170174 }
171175
172 fn checkCancel(thread: *Thread) error{Canceled}!void {
173 const closure = thread.current_closure orelse return;
176 const Status = packed struct(usize) {
177 num_running: @Int(.unsigned, @bitSizeOf(usize) - 2),
178 have_awaiter: bool,
179 canceled: bool,
180 };
174181
175 switch (thread.cancel_protection) {
176 .unblocked => {},
177 .blocked => return,
182 const Task = struct {
183 runnable: Runnable,
184 group: *Io.Group,
185 func: *const fn (context: *const anyopaque) Io.Cancelable!void,
186 context_alignment: Alignment,
187 alloc_len: usize,
188
189 /// `Task.runnable.node` is `undefined` in the created `Task`.
190 fn create(
191 gpa: Allocator,
192 group: Group,
193 context: []const u8,
194 context_alignment: Alignment,
195 func: *const fn (context: *const anyopaque) Io.Cancelable!void,
196 ) Allocator.Error!*Task {
197 const max_context_misalignment = context_alignment.toByteUnits() -| @alignOf(Task);
198 const worst_case_context_offset = context_alignment.forward(@sizeOf(Task) + max_context_misalignment);
199 const alloc_len = worst_case_context_offset + context.len;
200
201 const task: *Task = @ptrCast(@alignCast(try gpa.alignedAlloc(u8, .of(Task), alloc_len)));
202 errdefer comptime unreachable;
203
204 task.* = .{
205 .runnable = .{
206 .node = undefined,
207 .startFn = &start,
208 },
209 .group = group.ptr,
210 .func = func,
211 .context_alignment = context_alignment,
212 .alloc_len = alloc_len,
213 };
214 @memcpy(task.contextPointer()[0..context.len], context);
215 return task;
216 }
217
218 fn destroy(task: *Task, gpa: Allocator) void {
219 const base: [*]align(@alignOf(Task)) u8 = @ptrCast(task);
220 gpa.free(base[0..task.alloc_len]);
221 }
222
223 fn contextPointer(task: *Task) [*]u8 {
224 const base: [*]u8 = @ptrCast(task);
225 const offset = task.context_alignment.forward(@intFromPtr(base) + @sizeOf(Task)) - @intFromPtr(base);
226 return base + offset;
227 }
228
229 fn start(r: *Runnable, thread: *Thread, t: *Threaded) void {
230 const task: *Task = @fieldParentPtr("runnable", r);
231 const group: Group = .{ .ptr = task.group };
232
233 // This would be a simple store, but it's upgraded to an RMW so we can use `.acquire` to
234 // enforce the ordering between this and the `group.status().load` below. Paired with
235 // the `.release` rmw on `Thread.status` in `cancelThreads`, this creates a StoreLoad
236 // barrier which guarantees that when a group is canceled, either we see the cancelation
237 // in the group status, or the canceler sees our thread status so can directly notify us
238 // of the cancelation.
239 _ = thread.status.swap(.{
240 .cancelation = .none,
241 .awaitable = .fromGroup(group.ptr),
242 }, .acquire);
243 if (group.status().load(.monotonic).canceled) {
244 thread.status.store(.{
245 .cancelation = .canceling,
246 .awaitable = .fromGroup(group.ptr),
247 }, .monotonic);
248 }
249
250 const result = task.func(task.contextPointer());
251 const cancel_acknowledged = switch (thread.status.load(.monotonic).cancelation) {
252 .none, .canceling => false,
253 .canceled => true,
254 .parked => unreachable,
255 .blocked => unreachable,
256 .blocked_windows_dns => unreachable,
257 .blocked_canceling => unreachable,
258 };
259 if (result) {
260 assert(!cancel_acknowledged); // group task acknowledged cancelation but did not return `error.Canceled`
261 } else |err| switch (err) {
262 error.Canceled => assert(cancel_acknowledged), // group task returned `error.Canceled` but was never canceled
263 }
264
265 thread.status.store(.{ .cancelation = .none, .awaitable = .null }, .monotonic);
266 const old_status = group.status().fetchSub(.{
267 .num_running = 1,
268 .have_awaiter = false,
269 .canceled = false,
270 }, .acq_rel); // acquire `group.awaiter()`, release task results
271 assert(old_status.num_running > 0);
272 if (old_status.have_awaiter and old_status.num_running == 1) {
273 const to_signal = group.awaiter().*;
274 // `awaiter` should only be modified by us. For another thread to see `num_running`
275 // drop to 0 after this point would indicate that another task started up, meaning
276 // `async`/`cancel` was racing with awaited group completion.
277 group.awaiter().* = undefined;
278 _ = to_signal.fetchAdd(1, .release); // release results
279 Thread.futexWake(&to_signal.raw, 1);
280 }
281
282 // Task completed. Self-destruct sequence initiated.
283 task.destroy(t.allocator);
178284 }
285 };
179286
180 switch (@cmpxchgStrong(
181 CancelStatus,
182 &closure.cancel_status,
183 .requested,
184 .acknowledged,
185 .acq_rel,
186 .acquire,
187 ) orelse return error.Canceled) {
188 .requested => unreachable,
189 .acknowledged => unreachable,
190 .none, _ => {},
287 /// Assumes the caller has already atomically updated the group status to indicate cancelation,
288 /// and notifies any already-running threads of this cancelation.
289 fn cancelThreads(g: Group, t: *Threaded) bool {
290 var any_blocked = false;
291 var it = t.worker_threads.load(.acquire); // acquire `Thread` values
292 while (it) |thread| : (it = thread.next) {
293 // This non-mutating RMW exists for ordering reasons: see comment in `Group.Task.start` for reasons.
294 _ = thread.status.fetchOr(.{ .cancelation = @enumFromInt(0), .awaitable = .null }, .release);
295 if (thread.cancelAwaitable(.fromGroup(g.ptr))) any_blocked = true;
191296 }
297 return any_blocked;
192298 }
193299
194 fn beginSyscall(thread: *Thread) error{Canceled}!void {
195 const closure = thread.current_closure orelse return;
196
197 switch (thread.cancel_protection) {
198 .unblocked => {},
199 .blocked => return,
300 /// Uses `Thread.signalCanceledSyscall` to signal any threads which are still blocked in a
301 /// syscall for this group and have not observed a cancelation request yet. Returns `true` if
302 /// more signals may be necessary, in which case the caller must call this again after a delay.
303 fn signalAllCanceledSyscalls(g: Group, t: *Threaded) bool {
304 var any_signaled = false;
305 var it = t.worker_threads.load(.acquire); // acquire `Thread` values
306 while (it) |thread| : (it = thread.next) {
307 if (thread.signalCanceledSyscall(t, .fromGroup(g.ptr))) any_signaled = true;
200308 }
309 return any_signaled;
310 }
201311
202 switch (@cmpxchgStrong(
203 CancelStatus,
204 &closure.cancel_status,
205 .none,
206 .fromSignaleeId(thread.signal_id),
207 .acq_rel,
208 .acquire,
209 ) orelse return) {
210 .none => unreachable,
211 .requested => {
212 @atomicStore(CancelStatus, &closure.cancel_status, .acknowledged, .release);
213 return error.Canceled;
214 },
215 .acknowledged => return,
216 _ => unreachable,
312 /// The caller has canceled `g`. Inform any threads working on that group of the cancelation if
313 /// necessary, and wait for `g` to finish (indicated by `num_completed` being incremented from 0
314 /// to 1), while sending regular signals to threads if necessary for them to unblock from any
315 /// cancelable syscalls.
316 ///
317 /// `skip_signals` means it is already known that no threads are currently working on the group
318 /// so no notifications or signals are necessary.
319 fn waitForCancelWithSignaling(
320 g: Group,
321 t: *Threaded,
322 num_completed: *std.atomic.Value(u32),
323 skip_signals: bool,
324 ) void {
325 var need_signal: bool = !skip_signals and g.cancelThreads(t);
326 var timeout_ns: u64 = 1 << 10;
327 while (true) {
328 need_signal = need_signal and g.signalAllCanceledSyscalls(t);
329 Thread.futexWaitUncancelable(&num_completed.raw, 0, if (need_signal) timeout_ns else null);
330 switch (num_completed.load(.acquire)) { // acquire task results
331 0 => {},
332 1 => break,
333 else => unreachable,
334 }
335 timeout_ns <<|= 1;
217336 }
218337 }
338};
219339
220 fn endSyscall(thread: *Thread) void {
221 const closure = thread.current_closure orelse return;
340/// Trailing data:
341/// 1. context
342/// 2. result
343const Future = struct {
344 runnable: Runnable,
345 func: *const fn (context: *const anyopaque, result: *anyopaque) void,
346 status: std.atomic.Value(Status),
347 /// On completion, increment this `u32` and do a futex wake on it.
348 awaiter: *std.atomic.Value(u32),
349 context_alignment: Alignment,
350 result_offset: usize,
351 alloc_len: usize,
222352
223 switch (thread.cancel_protection) {
224 .unblocked => {},
225 .blocked => return,
226 }
353 const Status = packed struct(usize) {
354 /// The values of this enum are chosen so that await/cancel can just OR with 0b01 and 0b11
355 /// respectively. That *does* clobber `.done`, but that's actually fine, because if the tag
356 /// is `.done` then only the awaiter is referencing this `Future` anyway.
357 tag: enum(u2) {
358 /// The future is queued or running (depending on whether `thread` is set).
359 pending = 0b00,
360 /// Like `pending`, but the future is being awaited. `Future.awaiter` is populated.
361 pending_awaited = 0b01,
362 /// Like `pending`, but the future is being canceled. `Future.awaiter` is populated.
363 pending_canceled = 0b11,
364 /// The future has already completed. `thread` is `.null`, unless the future terminated
365 /// with an acknowledged cancel request, in which case `thread` is `.all_ones`.
366 done = 0b10,
367 },
368 /// When the future begins execution, this is atomically updated from `null` to the thread running the
369 /// `Future`, so that cancelation knows which thread to cancel.
370 thread: Thread.PackedPtr,
371 };
372
373 /// `Future.runnable.node` is `undefined` in the created `Future`.
374 fn create(
375 gpa: Allocator,
376 result_len: usize,
377 result_alignment: Alignment,
378 context: []const u8,
379 context_alignment: Alignment,
380 func: *const fn (context: *const anyopaque, result: *anyopaque) void,
381 ) Allocator.Error!*Future {
382 const max_context_misalignment = context_alignment.toByteUnits() -| @alignOf(Future);
383 const worst_case_context_offset = context_alignment.forward(@sizeOf(Future) + max_context_misalignment);
384 const worst_case_result_offset = result_alignment.forward(worst_case_context_offset + context.len);
385 const alloc_len = worst_case_result_offset + result_len;
386
387 const future: *Future = @ptrCast(@alignCast(try gpa.alignedAlloc(u8, .of(Future), alloc_len)));
388 errdefer comptime unreachable;
227389
228 _ = @cmpxchgStrong(
229 CancelStatus,
230 &closure.cancel_status,
231 .fromSignaleeId(thread.signal_id),
232 .none,
233 .acq_rel,
234 .acquire,
235 ) orelse return;
390 const actual_context_addr = context_alignment.forward(@intFromPtr(future) + @sizeOf(Future));
391 const actual_result_addr = result_alignment.forward(actual_context_addr + context.len);
392 const actual_result_offset = actual_result_addr - @intFromPtr(future);
393 future.* = .{
394 .runnable = .{
395 .node = undefined,
396 .startFn = &start,
397 },
398 .func = func,
399 .status = .init(.{
400 .tag = .pending,
401 .thread = .null,
402 }),
403 .awaiter = undefined,
404 .context_alignment = context_alignment,
405 .result_offset = actual_result_offset,
406 .alloc_len = alloc_len,
407 };
408 @memcpy(future.contextPointer()[0..context.len], context);
409 return future;
236410 }
237411
238 fn endSyscallErrnoBug(thread: *Thread, err: posix.E) Io.UnexpectedError {
239 @branchHint(.cold);
240 thread.endSyscall();
241 return errnoBug(err);
412 fn destroy(future: *Future, gpa: Allocator) void {
413 const base: [*]align(@alignOf(Future)) u8 = @ptrCast(future);
414 gpa.free(base[0..future.alloc_len]);
242415 }
243416
244 fn endSyscallUnexpectedErrno(thread: *Thread, err: posix.E) Io.UnexpectedError {
245 @branchHint(.cold);
246 thread.endSyscall();
247 return posix.unexpectedErrno(err);
417 fn resultPointer(future: *Future) [*]u8 {
418 const base: [*]u8 = @ptrCast(future);
419 return base + future.result_offset;
248420 }
249421
250 /// inline to make error return traces slightly shallower.
251 inline fn endSyscallError(thread: *Thread, err: anytype) @TypeOf(err) {
252 thread.endSyscall();
253 return err;
422 fn contextPointer(future: *Future) [*]u8 {
423 const base: [*]u8 = @ptrCast(future);
424 const context_offset = future.context_alignment.forward(@intFromPtr(future) + @sizeOf(Future)) - @intFromPtr(future);
425 return base + context_offset;
426 }
427
428 fn start(r: *Runnable, thread: *Thread, t: *Threaded) void {
429 _ = t;
430 const future: *Future = @fieldParentPtr("runnable", r);
431
432 thread.status.store(.{
433 .cancelation = .none,
434 .awaitable = .fromFuture(future),
435 }, .monotonic);
436 {
437 const old_status = future.status.fetchOr(.{
438 .tag = .pending,
439 .thread = .pack(thread),
440 }, .release);
441 assert(old_status.thread == .null);
442 switch (old_status.tag) {
443 .pending, .pending_awaited => {},
444 .pending_canceled => thread.status.store(.{
445 .cancelation = .canceling,
446 .awaitable = .fromFuture(future),
447 }, .monotonic),
448 .done => unreachable,
449 }
450 }
451
452 future.func(future.contextPointer(), future.resultPointer());
453
454 const had_acknowledged_cancel = switch (thread.status.load(.monotonic).cancelation) {
455 .none, .canceling => false,
456 .canceled => true,
457 .parked => unreachable,
458 .blocked => unreachable,
459 .blocked_windows_dns => unreachable,
460 .blocked_canceling => unreachable,
461 };
462 thread.status.store(.{ .cancelation = .none, .awaitable = .null }, .monotonic);
463 const old_status = future.status.swap(.{
464 .tag = .done,
465 .thread = if (had_acknowledged_cancel) .all_ones else .null,
466 }, .acq_rel); // acquire `future.awaiter`, release results
467 switch (old_status.tag) {
468 .pending => {},
469 .pending_awaited, .pending_canceled => {
470 const to_signal = future.awaiter;
471 _ = to_signal.fetchAdd(1, .release); // release results
472 Thread.futexWake(&to_signal.raw, 1);
473 },
474 .done => unreachable,
475 }
476 }
477
478 /// The caller has canceled `future`. `thread` is the thread currently running that future.
479 /// Inform `thread` of the cancelation if necessary, and wait for `future` to finish (indicated
480 /// by `num_completed` being incremented from 0 to 1), while sending regular signals to `thread`
481 /// if necessary for it to unblock from a cancelable syscall.
482 fn waitForCancelWithSignaling(
483 future: *Future,
484 t: *Threaded,
485 num_completed: *std.atomic.Value(u32),
486 thread: ?*Thread,
487 ) void {
488 var need_signal: bool = thread != null and thread.?.cancelAwaitable(.fromFuture(future));
489 var timeout_ns: u64 = 1 << 10;
490 while (true) {
491 need_signal = need_signal and thread.?.signalCanceledSyscall(t, .fromFuture(future));
492 Thread.futexWaitUncancelable(&num_completed.raw, 0, if (need_signal) timeout_ns else null);
493 switch (num_completed.load(.acquire)) { // acquire task results
494 0 => {},
495 1 => break,
496 else => unreachable,
497 }
498 timeout_ns <<|= 1;
499 }
500 }
501};
502
503/// A sequence of (ptr_bit_width - 3) bits which uniquely identifies a group or future. The bits are
504/// the MSBs of the `*Io.Group` or `*Future`. These things do not necessarily have 3 zero bits at
505/// the end (they are pointer-aligned, so on 32-bit targets only have 2), but because they both have
506/// a *size* of at least 8 bytes, no two groups/futures in memory at the same time will have the
507/// same value for all of these bits. In other words, given a group/future pointer, the next group
508/// or future must be at least 8 bytes later, so its address will have a different value for one of
509/// the top (ptr_bit_width - 3) bits.
510const AwaitableId = enum(@Int(.unsigned, @bitSizeOf(usize) - 3)) {
511 comptime {
512 assert(@sizeOf(Future) >= 8);
513 assert(@sizeOf(Io.Group) >= 8);
514 }
515 null = 0,
516 all_ones = std.math.maxInt(@Int(.unsigned, @bitSizeOf(usize) - 3)),
517 _,
518 const Split = packed struct(usize) { low: u3, high: AwaitableId };
519 fn fromGroup(g: *Io.Group) AwaitableId {
520 const split: Split = @bitCast(@intFromPtr(g));
521 return split.high;
254522 }
523 fn fromFuture(f: *Future) AwaitableId {
524 const split: Split = @bitCast(@intFromPtr(f));
525 return split.high;
526 }
527};
528
529const Thread = struct {
530 next: ?*Thread,
531
532 id: std.Thread.Id,
533 handle: Handle,
534
535 status: std.atomic.Value(Status),
536
537 cancel_protection: Io.CancelProtection,
538 /// Always released when `Status.cancelation` is set to `.parked`.
539 futex_waiter: if (use_parking_futex) ?*parking_futex.Waiter else ?noreturn,
540
541 const Handle = Handle: {
542 if (std.Thread.use_pthreads) break :Handle std.c.pthread_t;
543 if (builtin.target.os.tag == .windows) break :Handle windows.HANDLE;
544 break :Handle void;
545 };
546
547 const Status = packed struct(usize) {
548 /// The specific values of these enum fields are chosen to simplify the implementation of
549 /// the transformations we need to apply to this state.
550 cancelation: enum(u3) {
551 /// The thread has not yet been canceled, and is not in a cancelable operation.
552 /// To request cancelation, just set the status to `.canceling`.
553 none = 0b000,
554
555 /// The thread is parked in a cancelable futex wait or sleep.
556 /// Only applicable if `use_parking_futex` or `use_parking_sleep`.
557 /// To request cancelation, set the status to `.canceling` and unpark the thread.
558 /// To unpark for another reason (futex wake), set the status to `.none` and unpark the thread.
559 parked = 0b001,
560
561 /// The thread is blocked in a cancelable system call.
562 /// To request cancelation, set the status to `.blocked_canceling` and repeatedly interrupt the system call until the status changes.
563 blocked = 0b011,
564
565 /// Windows-only: the thread is blocked in a call to `GetAddrInfoExW`.
566 /// To request cancelation, set the status to `.canceling` and call `GetAddrInfoExCancel`.
567 blocked_windows_dns = 0b010,
568
569 /// The thread has an outstanding cancelation request but is not in a cancelable operation.
570 /// When it acknowledges the cancelation, it will set the status to `.canceled`.
571 canceling = 0b110,
572
573 /// The thread has received and acknowledged a cancelation request.
574 /// If `recancel` is called, the status will revert to `.canceling`, but otherwise, the status
575 /// will not change for the remainder of this task's execution.
576 canceled = 0b111,
577
578 /// 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
579 /// until its status changes.
580 blocked_canceling = 0b101,
581 },
582
583 /// We cannot turn this value back into a pointer. Instead, it exists so that a task can be
584 /// canceled by a cmpxchg on thread status: if it is running the task we want to cancel,
585 /// then update the `cancelation` field.
586 awaitable: AwaitableId,
587 };
255588
256 fn currentSignalId() SignaleeId {
257 return if (std.Thread.use_pthreads) std.c.pthread_self() else std.Thread.getCurrentId();
589 const SignaleeId = if (std.Thread.use_pthreads) std.c.pthread_t else std.Thread.Id;
590
591 threadlocal var current: ?*Thread = null;
592
593 /// The thread is neither in a syscall nor entering one, but we want to check for cancelation
594 /// anyway. If there is a pending cancel request, acknowledge it and return `error.Canceled`.
595 fn checkCancel() Io.Cancelable!void {
596 const thread = Thread.current orelse return;
597 switch (thread.cancel_protection) {
598 .blocked => return,
599 .unblocked => {},
600 }
601 // Here, unlike `Syscall.checkCancel`, it's not particularly likely that we're canceled, so
602 // it seems preferable to do a cheap atomic load and, in the unlikely case, a separate store
603 // to acknowledge. Besides, the state transitions we need here can't be done with one atomic
604 // OR/AND/XOR on `Status.cancelation`, so we don't actually have any other option.
605 const status = thread.status.load(.monotonic);
606 switch (status.cancelation) {
607 .parked => unreachable,
608 .blocked => unreachable,
609 .blocked_windows_dns => unreachable,
610 .blocked_canceling => unreachable,
611 .none, .canceled => {},
612 .canceling => {
613 thread.status.store(.{
614 .cancelation = .canceled,
615 .awaitable = status.awaitable,
616 }, .monotonic);
617 return error.Canceled;
618 },
619 }
258620 }
259621
260 fn futexWaitUncancelable(ptr: *const u32, expect: u32) void {
261 return Thread.futexWaitTimed(null, ptr, expect, null) catch unreachable;
622 fn futexWaitUncancelable(ptr: *const u32, expect: u32, timeout_ns: ?u64) void {
623 return Thread.futexWaitInner(ptr, expect, true, timeout_ns) catch unreachable;
262624 }
263625
264 fn futexWait(thread: *Thread, ptr: *const u32, expect: u32) Io.Cancelable!void {
265 return Thread.futexWaitTimed(thread, ptr, expect, null) catch |err| switch (err) {
266 error.Canceled => return error.Canceled,
267 error.Timeout => unreachable,
268 };
626 fn futexWait(ptr: *const u32, expect: u32, timeout_ns: ?u64) Io.Cancelable!void {
627 return Thread.futexWaitInner(ptr, expect, false, timeout_ns);
269628 }
270629
271 fn futexWaitTimed(thread: ?*Thread, ptr: *const u32, expect: u32, timeout_ns: ?u64) Io.Cancelable!void {
630 fn futexWaitInner(ptr: *const u32, expect: u32, uncancelable: bool, timeout_ns: ?u64) Io.Cancelable!void {
272631 @branchHint(.cold);
273632
274633 if (builtin.single_threaded) unreachable; // nobody would ever wake us
275634
276 if (builtin.cpu.arch.isWasm()) {
635 if (use_parking_futex) {
636 return parking_futex.wait(
637 ptr,
638 expect,
639 uncancelable,
640 if (timeout_ns) |ns| .{ .duration = .{
641 .raw = .fromNanoseconds(ns),
642 .clock = .boot,
643 } } else .none,
644 );
645 } else if (builtin.cpu.arch.isWasm()) {
277646 comptime assert(builtin.cpu.has(.wasm, .atomics));
278 if (thread) |t| try t.checkCancel();
647 // TODO implement cancelation for WASM futex waits by signaling the futex
648 if (!uncancelable) try Thread.checkCancel();
279649 const to: i64 = if (timeout_ns) |ns| ns else -1;
280650 const signed_expect: i32 = @bitCast(expect);
281651 const result = asm volatile (
......@@ -303,9 +673,9 @@ const Thread = struct {
303673 ts_buffer = timestampToPosix(ns);
304674 break :ts &ts_buffer;
305675 } else null;
306 if (thread) |t| try t.beginSyscall();
676 const syscall: Syscall = if (uncancelable) .{ .thread = null } else try .start();
307677 const rc = linux.futex_4arg(ptr, .{ .cmd = .WAIT, .private = true }, expect, ts);
308 if (thread) |t| t.endSyscall();
678 syscall.finish();
309679 switch (linux.errno(rc)) {
310680 .SUCCESS => {}, // notified by `wake()`
311681 .INTR => {}, // caller's responsibility to retry
......@@ -322,7 +692,7 @@ const Thread = struct {
322692 .op = .COMPARE_AND_WAIT,
323693 .NO_ERRNO = true,
324694 };
325 if (thread) |t| try t.beginSyscall();
695 const syscall: Syscall = if (uncancelable) .{ .thread = null } else try .start();
326696 const status = switch (darwin_supports_ulock_wait2) {
327697 true => c.__ulock_wait2(flags, ptr, expect, ns: {
328698 const ns = timeout_ns orelse break :ns 0;
......@@ -336,7 +706,7 @@ const Thread = struct {
336706 break :us us;
337707 }),
338708 };
339 if (thread) |t| t.endSyscall();
709 syscall.finish();
340710 if (status >= 0) return;
341711 switch (@as(c.E, @enumFromInt(-status))) {
342712 .INTR => {}, // spurious wake
......@@ -348,24 +718,6 @@ const Thread = struct {
348718 else => recoverableOsBugDetected(),
349719 }
350720 },
351 .windows => {
352 var timeout_value: windows.LARGE_INTEGER = undefined;
353 var timeout_ptr: ?*const windows.LARGE_INTEGER = null;
354 // NTDLL functions work with time in units of 100 nanoseconds.
355 // Positive values are absolute deadlines while negative values are relative durations.
356 if (timeout_ns) |delay| {
357 timeout_value = @as(windows.LARGE_INTEGER, @intCast(delay / 100));
358 timeout_value = -timeout_value;
359 timeout_ptr = &timeout_value;
360 }
361 if (thread) |t| try t.checkCancel();
362 switch (windows.ntdll.RtlWaitOnAddress(ptr, &expect, @sizeOf(@TypeOf(expect)), timeout_ptr)) {
363 .SUCCESS => {},
364 .CANCELLED => {},
365 .TIMEOUT => {}, // timeout
366 else => recoverableOsBugDetected(),
367 }
368 },
369721 .freebsd => {
370722 const flags = @intFromEnum(std.c.UMTX_OP.WAIT_UINT_PRIVATE);
371723 var tm_size: usize = 0;
......@@ -378,9 +730,9 @@ const Thread = struct {
378730 tm.clockid = .MONOTONIC;
379731 tm.timeout = timestampToPosix(ns);
380732 }
381 if (thread) |t| try t.beginSyscall();
733 const syscall: Syscall = if (uncancelable) .{ .thread = null } else try .start();
382734 const rc = std.c._umtx_op(@intFromPtr(ptr), flags, @as(c_ulong, expect), tm_size, @intFromPtr(tm_ptr));
383 if (thread) |t| t.endSyscall();
735 syscall.finish();
384736 if (is_debug) switch (posix.errno(rc)) {
385737 .SUCCESS => {},
386738 .FAULT => unreachable, // one of the args points to invalid memory
......@@ -397,7 +749,7 @@ const Thread = struct {
397749 tm_ptr = &tm;
398750 tm = timestampToPosix(ns);
399751 }
400 if (thread) |t| try t.beginSyscall();
752 const syscall: Syscall = if (uncancelable) .{ .thread = null } else try .start();
401753 const rc = std.c.futex(
402754 ptr,
403755 std.c.FUTEX.WAIT | std.c.FUTEX.PRIVATE_FLAG,
......@@ -405,7 +757,7 @@ const Thread = struct {
405757 tm_ptr,
406758 null, // uaddr2 is ignored
407759 );
408 if (thread) |t| t.endSyscall();
760 syscall.finish();
409761 if (is_debug) switch (posix.errno(rc)) {
410762 .SUCCESS => {},
411763 .NOSYS => unreachable, // constant op known good value
......@@ -424,9 +776,9 @@ const Thread = struct {
424776 } else {
425777 timeout_us = 0;
426778 }
427 if (thread) |t| try t.beginSyscall();
779 const syscall: Syscall = if (uncancelable) .{ .thread = null } else try .start();
428780 const rc = std.c.umtx_sleep(@ptrCast(ptr), @bitCast(expect), timeout_us);
429 if (thread) |t| t.endSyscall();
781 syscall.finish();
430782 if (is_debug) switch (std.posix.errno(rc)) {
431783 .SUCCESS => {},
432784 .BUSY => {}, // ptr != expect
......@@ -436,14 +788,7 @@ const Thread = struct {
436788 else => unreachable,
437789 };
438790 },
439 else => if (std.Thread.use_pthreads) {
440 // TODO integrate the following function being called with robust cancelation.
441 return pthreads_futex.wait(ptr, expect, timeout_ns) catch |err| switch (err) {
442 error.Timeout => {},
443 };
444 } else {
445 @compileError("unimplemented: futexWait");
446 },
791 else => @compileError("unimplemented: futexWait"),
447792 }
448793 }
449794
......@@ -453,7 +798,9 @@ const Thread = struct {
453798
454799 if (builtin.single_threaded) return; // nothing to wake up
455800
456 if (builtin.cpu.arch.isWasm()) {
801 if (use_parking_futex) {
802 return parking_futex.wake(ptr, max_waiters);
803 } else if (builtin.cpu.arch.isWasm()) {
457804 comptime assert(builtin.cpu.has(.wasm, .atomics));
458805 const woken_count = asm volatile (
459806 \\local.get %[ptr]
......@@ -498,12 +845,6 @@ const Thread = struct {
498845 }
499846 }
500847 },
501 .windows => {
502 switch (max_waiters) {
503 1 => windows.ntdll.RtlWakeAddressSingle(ptr),
504 else => windows.ntdll.RtlWakeAddressAll(ptr),
505 }
506 },
507848 .freebsd => {
508849 const rc = std.c._umtx_op(
509850 @intFromPtr(ptr),
......@@ -536,130 +877,239 @@ const Thread = struct {
536877 @min(max_waiters, std.math.maxInt(c_int)),
537878 );
538879 },
539 else => if (std.Thread.use_pthreads) {
540 return pthreads_futex.wake(ptr, max_waiters);
541 } else {
542 @compileError("unimplemented: futexWake");
543 },
880 else => @compileError("unimplemented: futexWake"),
544881 }
545882 }
546};
547
548const max_iovecs_len = 8;
549const splat_buffer_size = 64;
550883
551comptime {
552 if (@TypeOf(posix.IOV_MAX) != void) assert(max_iovecs_len <= posix.IOV_MAX);
553}
884 /// Cancels `thread` if it is working on `awaitable`.
885 ///
886 /// It is possible that `thread` gets canceled by this function, but is blocked in a syscall. In
887 /// that case, the thread may need to be sent a signal to interrupt the call. This function will
888 /// return `true` to indicate this, in which case the caller must call `signalCanceledSyscall`.
889 fn cancelAwaitable(thread: *Thread, awaitable: AwaitableId) bool {
890 var status = thread.status.load(.monotonic);
891 while (true) {
892 if (status.awaitable != awaitable) return false; // thread is working on something else
893 status = switch (status.cancelation) {
894 .none => thread.status.cmpxchgWeak(
895 .{ .cancelation = .none, .awaitable = awaitable },
896 .{ .cancelation = .canceling, .awaitable = awaitable },
897 .monotonic,
898 .monotonic,
899 ) orelse return false,
900
901 .parked => thread.status.cmpxchgWeak(
902 .{ .cancelation = .parked, .awaitable = awaitable },
903 .{ .cancelation = .canceling, .awaitable = awaitable },
904 .acquire, // acquire `thread.futex_waiter`
905 .monotonic,
906 ) orelse {
907 if (!use_parking_futex and !use_parking_sleep) unreachable;
908 if (thread.futex_waiter) |futex_waiter| {
909 parking_futex.removeCanceledWaiter(futex_waiter);
910 }
911 unpark(&.{thread.id}, null);
912 return false;
913 },
554914
555const CancelStatus = enum(usize) {
556 /// Cancellation has neither been requested, nor checked. The async
557 /// operation will check status before entering a blocking syscall.
558 /// This is also the status used for uninteruptible tasks.
559 none = 0,
560 /// Cancellation has been requested and the status will be checked before
561 /// entering a blocking syscall.
562 requested = std.math.maxInt(usize) - 1,
563 /// Cancellation has been acknowledged and is in progress. Signals should
564 /// not be sent.
565 acknowledged = std.math.maxInt(usize),
566 /// Stores a `Thread.SignaleeId` and indicates that sending a signal to this thread
567 /// is needed in order to cancel. This state is set before going into
568 /// a blocking operation that needs to get unblocked via signal.
569 _,
915 .blocked => thread.status.cmpxchgWeak(
916 .{ .cancelation = .blocked, .awaitable = awaitable },
917 .{ .cancelation = .blocked_canceling, .awaitable = awaitable },
918 .monotonic,
919 .monotonic,
920 ) orelse return true,
921
922 .blocked_windows_dns => thread.status.cmpxchgWeak(
923 .{ .cancelation = .blocked_windows_dns, .awaitable = awaitable },
924 .{ .cancelation = .canceling, .awaitable = awaitable },
925 .monotonic,
926 .monotonic,
927 ) orelse {
928 if (builtin.target.os.tag != .windows) unreachable;
929 if (true) {
930 // TODO: cancel Windows DNS queries. This code path is currently impossible
931 // as `netLookupFallible` doesn't actually use `.blocked_windows_dns` yet.
932 unreachable;
933 }
934 return false;
935 },
570936
571 const Unpacked = union(enum) {
572 none,
573 requested,
574 acknowledged,
575 signal_id: Thread.SignaleeId,
576 };
577
578 fn unpack(cs: CancelStatus) Unpacked {
579 return switch (cs) {
580 .none => .none,
581 .requested => .requested,
582 .acknowledged => .acknowledged,
583 _ => |signal_id| .{
584 .signal_id = if (std.Thread.use_pthreads)
585 @ptrFromInt(@intFromEnum(signal_id))
586 else
587 @truncate(@intFromEnum(signal_id)),
588 },
589 };
590 }
937 .canceling, .canceled => {
938 // This can happen when the task start raced with the cancelation, so the thread
939 // saw the cancelation on the future/group *and* we are trying to signal the
940 // thread here.
941 return false;
942 },
591943
592 fn fromSignaleeId(signal_id: Thread.SignaleeId) CancelStatus {
593 return if (std.Thread.use_pthreads)
594 @enumFromInt(@intFromPtr(signal_id))
595 else
596 @enumFromInt(signal_id);
944 .blocked_canceling => unreachable,
945 };
946 }
597947 }
598};
599
600const Closure = struct {
601 start: Start,
602 node: std.SinglyLinkedList.Node = .{},
603 cancel_status: CancelStatus,
604948
605 const Start = *const fn (*Closure, *Threaded) void;
606
607 fn requestCancel(closure: *Closure, t: *Threaded) void {
608 var signal_id = switch (@atomicRmw(CancelStatus, &closure.cancel_status, .Xchg, .requested, .monotonic).unpack()) {
609 .none, .acknowledged, .requested => return,
610 .signal_id => |signal_id| signal_id,
611 };
612 // The task will enter a blocking syscall before checking for cancellation again.
613 // We can send a signal to interrupt the syscall, but if it arrives before
614 // the syscall instruction, it will be missed. Therefore, this code tries
615 // again until the cancellation request is acknowledged.
616
617 // 1 << 10 ns is about 1 microsecond, approximately syscall overhead.
618 // 1 << 20 ns is about 1 millisecond.
619 // 1 << 30 ns is about 1 second.
620 //
621 // On a heavily loaded Linux 6.17.5, I observed a maximum of 20
622 // attempts not acknowledged before the timeout (including exponential
623 // backoff) was sufficient, despite the heavy load.
624 const max_attempts = 22;
625
626 for (0..max_attempts) |attempt_index| {
627 if (std.Thread.use_pthreads) {
628 if (std.c.pthread_kill(signal_id, .IO) != 0) return;
629 } else if (native_os == .linux) {
630 const pid: posix.pid_t = p: {
949 /// Sends a signal to `thread` if it is still blocked in a syscall (i.e. has not yet observed
950 /// the cancelation request from `cancelAwaitable`).
951 ///
952 /// Unfortunately, the signal could arrive before the syscall actually starts, so the interrupt
953 /// is missed. To handle this, we may need to send multiple signals. As such, if this function
954 /// returns `true`, then it should be called again after a short delay to send another signal if
955 /// the thread is still blocked. For the implementation, `Future.waitForCancelWithSignaling` and
956 /// `Group.waitForCancelWithSignaling`: they use exponential backoff starting at a 1us delay and
957 /// doubling each call. In practice, it is rare to send more than one signal.
958 fn signalCanceledSyscall(thread: *Thread, t: *Threaded, awaitable: AwaitableId) bool {
959 const bad_status: Status = .{ .cancelation = .blocked_canceling, .awaitable = awaitable };
960 if (thread.status.load(.monotonic) != bad_status) return false;
961
962 // The thread ID and/or handle can be read non-atomically because they never change and were
963 // released by the store that made `thread` available to us.
964
965 if (std.Thread.use_pthreads) {
966 return switch (std.c.pthread_kill(thread.handle, .IO)) {
967 0 => true,
968 else => false,
969 };
970 } else switch (builtin.target.os.tag) {
971 .linux => {
972 const pid: posix.pid_t = pid: {
631973 const cached_pid = @atomicLoad(Pid, &t.pid, .monotonic);
632 if (cached_pid != .unknown) break :p @intFromEnum(cached_pid);
974 if (cached_pid != .unknown) break :pid @intFromEnum(cached_pid);
633975 const pid = std.os.linux.getpid();
634976 @atomicStore(Pid, &t.pid, @enumFromInt(pid), .monotonic);
635 break :p pid;
977 break :pid pid;
636978 };
637 if (std.os.linux.tgkill(pid, @bitCast(signal_id), .IO) != 0) return;
638 } else {
639 return;
640 }
979 return switch (std.os.linux.tgkill(pid, @bitCast(thread.id), .IO)) {
980 0 => true,
981 else => false,
982 };
983 },
984 .windows => {
985 var iosb: windows.IO_STATUS_BLOCK = undefined;
986 return switch (windows.ntdll.NtCancelSynchronousIoFile(thread.handle, null, &iosb)) {
987 .NOT_FOUND => true, // this might mean the operation hasn't started yet
988 .SUCCESS => false, // the OS confirmed that our cancelation worked
989 else => false,
990 };
991 },
992 else => return false,
993 }
994 }
641995
642 if (t.robust_cancel != .enabled) return;
996 /// Like a `*Thread`, but 2 bits smaller than a pointer (because the LSBs are always 0 due to
997 /// alignment) so that those two bits can be used in a `packed struct`.
998 const PackedPtr = enum(@Int(.unsigned, @bitSizeOf(usize) - 2)) {
999 null = 0,
1000 all_ones = std.math.maxInt(@Int(.unsigned, @bitSizeOf(usize) - 2)),
1001 _,
6431002
644 var timespec: posix.timespec = .{
645 .sec = 0,
646 .nsec = @as(isize, 1) << @intCast(attempt_index),
647 };
648 if (native_os == .linux) {
649 _ = std.os.linux.clock_nanosleep(posix.CLOCK.MONOTONIC, .{ .ABSTIME = false }, &timespec, &timespec);
650 } else {
651 _ = posix.system.nanosleep(&timespec, &timespec);
652 }
1003 const Split = packed struct(usize) { low: u2, high: PackedPtr };
1004 fn pack(ptr: *Thread) PackedPtr {
1005 const split: Split = @bitCast(@intFromPtr(ptr));
1006 assert(split.low == 0);
1007 return split.high;
1008 }
1009 fn unpack(ptr: PackedPtr) ?*Thread {
1010 const split: Split = .{ .low = 0, .high = ptr };
1011 return @ptrFromInt(@as(usize, @bitCast(split)));
1012 }
1013 };
1014};
6531015
654 switch (@atomicRmw(CancelStatus, &closure.cancel_status, .Xchg, .requested, .monotonic).unpack()) {
655 .requested => continue, // Retry needed in case other thread hasn't yet entered the syscall.
656 .none, .acknowledged => return,
657 .signal_id => |new_signal_id| signal_id = new_signal_id,
658 }
1016const Syscall = struct {
1017 thread: ?*Thread,
1018 /// Marks entry to a syscall region. This should be tightly scoped around the actual syscall
1019 /// to minimize races. The syscall must be marked as "finished" by `checkCancel`, `finish`,
1020 /// or one of the wrappers of `finish`.
1021 fn start() Io.Cancelable!Syscall {
1022 const thread = Thread.current orelse return .{ .thread = null };
1023 switch (thread.cancel_protection) {
1024 .blocked => return .{ .thread = null },
1025 .unblocked => {},
6591026 }
1027 switch (thread.status.fetchOr(.{
1028 .cancelation = @enumFromInt(0b011),
1029 .awaitable = .null,
1030 }, .monotonic).cancelation) {
1031 .parked => unreachable,
1032 .blocked => unreachable,
1033 .blocked_windows_dns => unreachable,
1034 .blocked_canceling => unreachable,
1035 .none => return .{ .thread = thread }, // new status is `.blocked`
1036 .canceling => return error.Canceled, // new status is `.canceled`
1037 .canceled => return .{ .thread = null }, // new status is `.canceled` (unchanged)
1038 }
1039 }
1040 /// Checks whether this syscall has been canceled. This should be called when a syscall is
1041 /// interrupted through a mechanism which may indicate cancelation, or may be spurious. If
1042 /// the syscall was canceled, it is finished and `error.Canceled` is returned. Otherwise,
1043 /// the syscall is not marked finished, and the caller should retry.
1044 fn checkCancel(s: Syscall) Io.Cancelable!void {
1045 const thread = s.thread orelse return;
1046 switch (thread.status.fetchOr(.{
1047 .cancelation = @enumFromInt(0b010),
1048 .awaitable = .null,
1049 }, .monotonic).cancelation) {
1050 .none => unreachable,
1051 .parked => unreachable,
1052 .blocked_windows_dns => unreachable,
1053 .canceling => unreachable,
1054 .canceled => unreachable,
1055 .blocked => {}, // new status is `.blocked` (unchanged)
1056 .blocked_canceling => return error.Canceled, // new status is `.canceled`
1057 }
1058 }
1059 /// Marks this syscall as finished.
1060 fn finish(s: Syscall) void {
1061 const thread = s.thread orelse return;
1062 switch (thread.status.fetchXor(.{
1063 .cancelation = @enumFromInt(0b011),
1064 .awaitable = .null,
1065 }, .monotonic).cancelation) {
1066 .none => unreachable,
1067 .parked => unreachable,
1068 .blocked_windows_dns => unreachable,
1069 .canceling => unreachable,
1070 .canceled => unreachable,
1071 .blocked => {}, // new status is `.none`
1072 .blocked_canceling => {}, // new status is `.canceling`
1073 }
1074 }
1075 /// Convenience wrapper which calls `finish`, then returns `err`.
1076 fn fail(s: Syscall, err: anytype) @TypeOf(err) {
1077 s.finish();
1078 return err;
1079 }
1080 /// Convenience wrapper which calls `finish`, then calls `Threaded.errnoBug`.
1081 fn errnoBug(s: Syscall, err: posix.E) Io.UnexpectedError {
1082 @branchHint(.cold);
1083 s.finish();
1084 return Threaded.errnoBug(err);
1085 }
1086 /// Convenience wrapper which calls `finish`, then calls `posix.unexpectedErrno`.
1087 fn unexpectedErrno(s: Syscall, err: posix.E) Io.UnexpectedError {
1088 @branchHint(.cold);
1089 s.finish();
1090 return posix.unexpectedErrno(err);
1091 }
1092 /// Convenience wrapper which calls `finish`, then calls `windows.statusBug`.
1093 fn ntstatusBug(s: Syscall, status: windows.NTSTATUS) Io.UnexpectedError {
1094 @branchHint(.cold);
1095 s.finish();
1096 return windows.statusBug(status);
1097 }
1098 /// Convenience wrapper which calls `finish`, then calls `windows.unexpectedStatus`.
1099 fn unexpectedNtstatus(s: Syscall, status: windows.NTSTATUS) Io.UnexpectedError {
1100 @branchHint(.cold);
1101 s.finish();
1102 return windows.unexpectedStatus(status);
6601103 }
6611104};
6621105
1106const max_iovecs_len = 8;
1107const splat_buffer_size = 64;
1108
1109comptime {
1110 if (@TypeOf(posix.IOV_MAX) != void) assert(max_iovecs_len <= posix.IOV_MAX);
1111}
1112
6631113pub const InitOptions = struct {
6641114 /// Affects how many bytes are memory-mapped for threads.
6651115 stack_size: usize = std.Thread.SpawnConfig.default_stack_size,
......@@ -681,17 +1131,6 @@ pub const InitOptions = struct {
6811131 /// concurrent tasks. After this number, calls to `Io.concurrent` return
6821132 /// `error.ConcurrencyUnavailable`.
6831133 concurrent_limit: Io.Limit = .unlimited,
684 /// When a cancel request is made, blocking syscalls can be unblocked by
685 /// issuing a signal. However, if the signal arrives after the check and before
686 /// the syscall instruction, it is missed.
687 ///
688 /// This option solves the race condition by retrying the signal delivery
689 /// until it is acknowledged, with an exponential backoff.
690 ///
691 /// Unfortunately, trying again until the cancellation request is acknowledged
692 /// has been observed to be relatively slow, and usually strong cancellation
693 /// guarantees are not needed, so this defaults to off.
694 robust_cancel: RobustCancel = .disabled,
6951134 /// Affects the following operations:
6961135 /// * `processExecutablePath` on OpenBSD and Haiku.
6971136 argv0: Argv0 = .{},
......@@ -727,14 +1166,9 @@ pub fn init(
7271166 .old_sig_io = undefined,
7281167 .old_sig_pipe = undefined,
7291168 .have_signal_handler = false,
730 .main_thread = .{
731 .signal_id = Thread.currentSignalId(),
732 .current_closure = null,
733 .cancel_protection = .unblocked,
734 },
7351169 .argv0 = options.argv0,
7361170 .environ = options.environ,
737 .robust_cancel = options.robust_cancel,
1171 .worker_threads = .init(null),
7381172 };
7391173
7401174 if (posix.Sigaction != void) {
......@@ -768,14 +1202,9 @@ pub const init_single_threaded: Threaded = .{
7681202 .old_sig_io = undefined,
7691203 .old_sig_pipe = undefined,
7701204 .have_signal_handler = false,
771 .main_thread = .{
772 .signal_id = undefined,
773 .current_closure = null,
774 .cancel_protection = .unblocked,
775 },
776 .robust_cancel = .disabled,
7771205 .argv0 = .{},
7781206 .environ = .{},
1207 .worker_threads = .init(null),
7791208};
7801209
7811210var global_single_threaded_instance: Threaded = .init_single_threaded;
......@@ -822,22 +1251,70 @@ fn join(t: *Threaded) void {
8221251
8231252fn worker(t: *Threaded) void {
8241253 var thread: Thread = .{
825 .signal_id = Thread.currentSignalId(),
826 .current_closure = null,
1254 .next = undefined,
1255 .id = std.Thread.getCurrentId(),
1256 .handle = handle: {
1257 if (std.Thread.use_pthreads) break :handle std.c.pthread_self();
1258 if (builtin.target.os.tag == .windows) break :handle undefined; // populated below
1259 },
1260 .status = .init(.{
1261 .cancelation = .none,
1262 .awaitable = .null,
1263 }),
8271264 .cancel_protection = .unblocked,
1265 .futex_waiter = undefined,
8281266 };
8291267 Thread.current = &thread;
8301268
1269 if (builtin.target.os.tag == .windows) {
1270 assert(windows.ntdll.NtOpenThread(
1271 &thread.handle,
1272 .{
1273 .SPECIFIC = .{
1274 .THREAD = .{
1275 .TERMINATE = true, // for `NtCancelSynchronousIoFile`
1276 },
1277 },
1278 },
1279 &.{
1280 .Length = @sizeOf(windows.OBJECT_ATTRIBUTES),
1281 .RootDirectory = null,
1282 .ObjectName = null,
1283 .Attributes = .{},
1284 .SecurityDescriptor = null,
1285 .SecurityQualityOfService = null,
1286 },
1287 &windows.teb().ClientId,
1288 ) == .SUCCESS);
1289 }
1290 defer if (builtin.target.os.tag == .windows) {
1291 windows.CloseHandle(thread.handle);
1292 };
1293
1294 {
1295 var head = t.worker_threads.load(.monotonic);
1296 while (true) {
1297 thread.next = head;
1298 head = t.worker_threads.cmpxchgWeak(
1299 head,
1300 &thread,
1301 .release,
1302 .monotonic,
1303 ) orelse break;
1304 }
1305 }
1306
8311307 defer t.wait_group.finish();
8321308
8331309 t.mutex.lock();
8341310 defer t.mutex.unlock();
8351311
8361312 while (true) {
837 while (t.run_queue.popFirst()) |closure_node| {
1313 while (t.run_queue.popFirst()) |runnable_node| {
8381314 t.mutex.unlock();
839 const closure: *Closure = @fieldParentPtr("node", closure_node);
840 closure.start(closure, t);
1315 thread.cancel_protection = .unblocked;
1316 const runnable: *Runnable = @fieldParentPtr("node", runnable_node);
1317 runnable.startFn(runnable, &thread, t);
8411318 t.mutex.lock();
8421319 t.busy_count -= 1;
8431320 }
......@@ -1145,103 +1622,6 @@ const linux_copy_file_range_use_c = std.c.versionCheck(if (builtin.abi.isAndroid
11451622});
11461623const linux_copy_file_range_sys = if (linux_copy_file_range_use_c) std.c else std.os.linux;
11471624
1148/// Trailing data:
1149/// 1. context
1150/// 2. result
1151const AsyncClosure = struct {
1152 closure: Closure,
1153 func: *const fn (context: *anyopaque, result: *anyopaque) void,
1154 event: Io.Event,
1155 select_condition: ?*Io.Event,
1156 context_alignment: Alignment,
1157 result_offset: usize,
1158 alloc_len: usize,
1159
1160 const done_event: *Io.Event = @ptrFromInt(@alignOf(Io.Event));
1161
1162 fn start(closure: *Closure, t: *Threaded) void {
1163 const ac: *AsyncClosure = @alignCast(@fieldParentPtr("closure", closure));
1164 const current_thread = Thread.getCurrent(t);
1165
1166 current_thread.current_closure = closure;
1167 current_thread.cancel_protection = .unblocked;
1168
1169 ac.func(ac.contextPointer(), ac.resultPointer());
1170
1171 current_thread.current_closure = null;
1172 current_thread.cancel_protection = undefined;
1173
1174 if (@atomicRmw(?*Io.Event, &ac.select_condition, .Xchg, done_event, .release)) |select_event| {
1175 assert(select_event != done_event);
1176 select_event.set(ioBasic(t));
1177 }
1178 ac.event.set(ioBasic(t));
1179 }
1180
1181 fn resultPointer(ac: *AsyncClosure) [*]u8 {
1182 const base: [*]u8 = @ptrCast(ac);
1183 return base + ac.result_offset;
1184 }
1185
1186 fn contextPointer(ac: *AsyncClosure) [*]u8 {
1187 const base: [*]u8 = @ptrCast(ac);
1188 const context_offset = ac.context_alignment.forward(@intFromPtr(ac) + @sizeOf(AsyncClosure)) - @intFromPtr(ac);
1189 return base + context_offset;
1190 }
1191
1192 fn init(
1193 gpa: Allocator,
1194 result_len: usize,
1195 result_alignment: Alignment,
1196 context: []const u8,
1197 context_alignment: Alignment,
1198 func: *const fn (context: *const anyopaque, result: *anyopaque) void,
1199 ) Allocator.Error!*AsyncClosure {
1200 const max_context_misalignment = context_alignment.toByteUnits() -| @alignOf(AsyncClosure);
1201 const worst_case_context_offset = context_alignment.forward(@sizeOf(AsyncClosure) + max_context_misalignment);
1202 const worst_case_result_offset = result_alignment.forward(worst_case_context_offset + context.len);
1203 const alloc_len = worst_case_result_offset + result_len;
1204
1205 const ac: *AsyncClosure = @ptrCast(@alignCast(try gpa.alignedAlloc(u8, .of(AsyncClosure), alloc_len)));
1206 errdefer comptime unreachable;
1207
1208 const actual_context_addr = context_alignment.forward(@intFromPtr(ac) + @sizeOf(AsyncClosure));
1209 const actual_result_addr = result_alignment.forward(actual_context_addr + context.len);
1210 const actual_result_offset = actual_result_addr - @intFromPtr(ac);
1211 ac.* = .{
1212 .closure = .{
1213 .cancel_status = .none,
1214 .start = start,
1215 },
1216 .func = func,
1217 .context_alignment = context_alignment,
1218 .result_offset = actual_result_offset,
1219 .alloc_len = alloc_len,
1220 .event = .unset,
1221 .select_condition = null,
1222 };
1223 @memcpy(ac.contextPointer()[0..context.len], context);
1224 return ac;
1225 }
1226
1227 fn waitAndDeinit(ac: *AsyncClosure, t: *Threaded, result: []u8) void {
1228 ac.event.wait(ioBasic(t)) catch |err| switch (err) {
1229 error.Canceled => {
1230 ac.closure.requestCancel(t);
1231 ac.event.waitUncancelable(ioBasic(t));
1232 recancel(t);
1233 },
1234 };
1235 @memcpy(result, ac.resultPointer()[0..result.len]);
1236 ac.deinit(t.allocator);
1237 }
1238
1239 fn deinit(ac: *AsyncClosure, gpa: Allocator) void {
1240 const base: [*]align(@alignOf(AsyncClosure)) u8 = @ptrCast(ac);
1241 gpa.free(base[0..ac.alloc_len]);
1242 }
1243};
1244
12451625fn async(
12461626 userdata: ?*anyopaque,
12471627 result: []u8,
......@@ -1255,10 +1635,13 @@ fn async(
12551635 start(context.ptr, result.ptr);
12561636 return null;
12571637 }
1638
12581639 const gpa = t.allocator;
1259 const ac = AsyncClosure.init(gpa, result.len, result_alignment, context, context_alignment, start) catch {
1260 start(context.ptr, result.ptr);
1261 return null;
1640 const future = Future.create(gpa, result.len, result_alignment, context, context_alignment, start) catch |err| switch (err) {
1641 error.OutOfMemory => {
1642 start(context.ptr, result.ptr);
1643 return null;
1644 },
12621645 };
12631646
12641647 t.mutex.lock();
......@@ -1267,7 +1650,7 @@ fn async(
12671650
12681651 if (busy_count >= @intFromEnum(t.async_limit)) {
12691652 t.mutex.unlock();
1270 ac.deinit(gpa);
1653 future.destroy(gpa);
12711654 start(context.ptr, result.ptr);
12721655 return null;
12731656 }
......@@ -1281,17 +1664,18 @@ fn async(
12811664 t.wait_group.finish();
12821665 t.busy_count = busy_count;
12831666 t.mutex.unlock();
1284 ac.deinit(gpa);
1667 future.destroy(gpa);
12851668 start(context.ptr, result.ptr);
12861669 return null;
12871670 };
12881671 thread.detach();
12891672 }
12901673
1291 t.run_queue.prepend(&ac.closure.node);
1674 t.run_queue.prepend(&future.runnable.node);
1675
12921676 t.mutex.unlock();
12931677 t.cond.signal();
1294 return @ptrCast(ac);
1678 return @ptrCast(future);
12951679}
12961680
12971681fn concurrent(
......@@ -1307,9 +1691,10 @@ fn concurrent(
13071691 const t: *Threaded = @ptrCast(@alignCast(userdata));
13081692
13091693 const gpa = t.allocator;
1310 const ac = AsyncClosure.init(gpa, result_len, result_alignment, context, context_alignment, start) catch
1311 return error.ConcurrencyUnavailable;
1312 errdefer ac.deinit(gpa);
1694 const future = Future.create(gpa, result_len, result_alignment, context, context_alignment, start) catch |err| switch (err) {
1695 error.OutOfMemory => return error.ConcurrencyUnavailable,
1696 };
1697 errdefer future.destroy(gpa);
13131698
13141699 t.mutex.lock();
13151700 defer t.mutex.unlock();
......@@ -1329,110 +1714,32 @@ fn concurrent(
13291714
13301715 const thread = std.Thread.spawn(.{ .stack_size = t.stack_size }, worker, .{t}) catch
13311716 return error.ConcurrencyUnavailable;
1717
13321718 thread.detach();
13331719 }
13341720
1335 t.run_queue.prepend(&ac.closure.node);
1721 t.run_queue.prepend(&future.runnable.node);
1722
13361723 t.cond.signal();
1337 return @ptrCast(ac);
1724 return @ptrCast(future);
13381725}
13391726
1340const GroupClosure = struct {
1341 closure: Closure,
1342 group: *Io.Group,
1343 /// Points to sibling `GroupClosure`. Used for walking the group to cancel all.
1344 node: std.SinglyLinkedList.Node,
1345 func: *const fn (*Io.Group, context: *anyopaque) Io.Cancelable!void,
1346 context_alignment: Alignment,
1347 alloc_len: usize,
1348
1349 fn start(closure: *Closure, t: *Threaded) void {
1350 const gc: *GroupClosure = @alignCast(@fieldParentPtr("closure", closure));
1351 const current_thread = Thread.getCurrent(t);
1352 const group = gc.group;
1353 const group_state: *std.atomic.Value(usize) = @ptrCast(&group.state);
1354 const event: *Io.Event = @ptrCast(&group.context);
1355 current_thread.current_closure = closure;
1356 current_thread.cancel_protection = .unblocked;
1357
1358 assertResult(closure, gc.func(group, gc.contextPointer()));
1359
1360 current_thread.current_closure = null;
1361 current_thread.cancel_protection = undefined;
1362
1363 const prev_state = group_state.fetchSub(sync_one_pending, .acq_rel);
1364 assert((prev_state / sync_one_pending) > 0);
1365 if (prev_state == (sync_one_pending | sync_is_waiting)) event.set(ioBasic(t));
1366 }
1367
1368 fn assertResult(closure: *Closure, result: Io.Cancelable!void) void {
1369 if (result) |_| switch (closure.cancel_status.unpack()) {
1370 .none, .requested => {},
1371 .acknowledged => unreachable, // task illegally swallowed error.Canceled
1372 .signal_id => unreachable,
1373 } else |err| switch (err) {
1374 error.Canceled => assert(closure.cancel_status == .acknowledged),
1375 }
1376 }
1377
1378 fn contextPointer(gc: *GroupClosure) [*]u8 {
1379 const base: [*]u8 = @ptrCast(gc);
1380 const context_offset = gc.context_alignment.forward(@intFromPtr(gc) + @sizeOf(GroupClosure)) - @intFromPtr(gc);
1381 return base + context_offset;
1382 }
1383
1384 /// Does not initialize the `node` field.
1385 fn init(
1386 gpa: Allocator,
1387 group: *Io.Group,
1388 context: []const u8,
1389 context_alignment: Alignment,
1390 func: *const fn (*Io.Group, context: *const anyopaque) Io.Cancelable!void,
1391 ) Allocator.Error!*GroupClosure {
1392 const max_context_misalignment = context_alignment.toByteUnits() -| @alignOf(GroupClosure);
1393 const worst_case_context_offset = context_alignment.forward(@sizeOf(GroupClosure) + max_context_misalignment);
1394 const alloc_len = worst_case_context_offset + context.len;
1395
1396 const gc: *GroupClosure = @ptrCast(@alignCast(try gpa.alignedAlloc(u8, .of(GroupClosure), alloc_len)));
1397 errdefer comptime unreachable;
1398
1399 gc.* = .{
1400 .closure = .{
1401 .cancel_status = .none,
1402 .start = start,
1403 },
1404 .group = group,
1405 .node = undefined,
1406 .func = func,
1407 .context_alignment = context_alignment,
1408 .alloc_len = alloc_len,
1409 };
1410 @memcpy(gc.contextPointer()[0..context.len], context);
1411 return gc;
1412 }
1413
1414 fn deinit(gc: *GroupClosure, gpa: Allocator) void {
1415 const base: [*]align(@alignOf(GroupClosure)) u8 = @ptrCast(gc);
1416 gpa.free(base[0..gc.alloc_len]);
1417 }
1418
1419 const sync_is_waiting: usize = 1 << 0;
1420 const sync_one_pending: usize = 1 << 1;
1421};
1422
14231727fn groupAsync(
14241728 userdata: ?*anyopaque,
1425 group: *Io.Group,
1729 type_erased: *Io.Group,
14261730 context: []const u8,
14271731 context_alignment: Alignment,
1428 start: *const fn (*Io.Group, context: *const anyopaque) Io.Cancelable!void,
1732 start: *const fn (context: *const anyopaque) Io.Cancelable!void,
14291733) void {
14301734 const t: *Threaded = @ptrCast(@alignCast(userdata));
1431 if (builtin.single_threaded) return start(group, context.ptr) catch unreachable;
1735 const g: Group = .{ .ptr = type_erased };
1736
1737 if (builtin.single_threaded) return groupAsyncEager(start, context.ptr);
14321738
14331739 const gpa = t.allocator;
1434 const gc = GroupClosure.init(gpa, group, context, context_alignment, start) catch
1435 return t.assertGroupResult(start(group, context.ptr));
1740 const task = Group.Task.create(gpa, g, context, context_alignment, start) catch |err| switch (err) {
1741 error.OutOfMemory => return groupAsyncEager(start, context.ptr),
1742 };
14361743
14371744 t.mutex.lock();
14381745
......@@ -1440,8 +1747,8 @@ fn groupAsync(
14401747
14411748 if (busy_count >= @intFromEnum(t.async_limit)) {
14421749 t.mutex.unlock();
1443 gc.deinit(gpa);
1444 return t.assertGroupResult(start(group, context.ptr));
1750 task.destroy(gpa);
1751 return groupAsyncEager(start, context.ptr);
14451752 }
14461753
14471754 t.busy_count = busy_count + 1;
......@@ -1453,48 +1760,84 @@ fn groupAsync(
14531760 t.wait_group.finish();
14541761 t.busy_count = busy_count;
14551762 t.mutex.unlock();
1456 gc.deinit(gpa);
1457 return t.assertGroupResult(start(group, context.ptr));
1763 task.destroy(gpa);
1764 return groupAsyncEager(start, context.ptr);
14581765 };
14591766 thread.detach();
14601767 }
14611768
1462 // Append to the group linked list inside the mutex to make `Io.Group.async` thread-safe.
1463 gc.node = .{ .next = @ptrCast(@alignCast(group.token.load(.monotonic))) };
1464 group.token.store(&gc.node, .monotonic);
1465
1466 t.run_queue.prepend(&gc.closure.node);
1467
1468 // This needs to be done before unlocking the mutex to avoid a race with
1469 // the associated task finishing.
1470 const group_state: *std.atomic.Value(usize) = @ptrCast(&group.state);
1471 const prev_state = group_state.fetchAdd(GroupClosure.sync_one_pending, .monotonic);
1472 assert((prev_state / GroupClosure.sync_one_pending) < (std.math.maxInt(usize) / GroupClosure.sync_one_pending));
1769 // TODO: if this logic is changed to be lock-free, this `fetchAdd` must be released by the queue
1770 // prepend so that the task doesn't finish without observing this and try to decrement the count
1771 // below zero.
1772 _ = g.status().fetchAdd(.{
1773 .num_running = 1,
1774 .have_awaiter = false,
1775 .canceled = false,
1776 }, .monotonic);
1777 t.run_queue.prepend(&task.runnable.node);
14731778
14741779 t.mutex.unlock();
14751780 t.cond.signal();
14761781}
1782fn groupAsyncEager(
1783 start: *const fn (context: *const anyopaque) Io.Cancelable!void,
1784 context: *const anyopaque,
1785) void {
1786 const pre_acknowledged = if (Thread.current) |thread| ack: {
1787 break :ack switch (thread.status.load(.monotonic).cancelation) {
1788 .none, .canceling => false,
1789 .canceled => true,
1790 .parked => unreachable,
1791 .blocked => unreachable,
1792 .blocked_windows_dns => unreachable,
1793 .blocked_canceling => unreachable,
1794 };
1795 } else false;
1796 const result = start(context);
1797 const post_acknowledged = if (Thread.current) |thread| ack: {
1798 break :ack switch (thread.status.load(.monotonic).cancelation) {
1799 .none, .canceling => false,
1800 .canceled => true,
1801 .parked => unreachable,
1802 .blocked => unreachable,
1803 .blocked_windows_dns => unreachable,
1804 .blocked_canceling => unreachable,
1805 };
1806 } else false;
14771807
1478fn assertGroupResult(t: *Threaded, result: Io.Cancelable!void) void {
1479 const current_thread: *Thread = .getCurrent(t);
1480 const current_closure = current_thread.current_closure orelse return;
1481 GroupClosure.assertResult(current_closure, result);
1808 if (result) {
1809 if (pre_acknowledged) {
1810 assert(post_acknowledged); // group task called `recancel` but was not canceled
1811 } else {
1812 assert(!post_acknowledged); // group task acknowledged cancelation but did not return `error.Canceled`
1813 }
1814 } else |err| switch (err) {
1815 // Don't swallow the cancelation: make it visible to the `Group.async` caller.
1816 error.Canceled => {
1817 assert(!pre_acknowledged); // group task called `recancel` but was not canceled
1818 assert(post_acknowledged); // group task returned `error.Canceled` but was never canceled
1819 recancelInner();
1820 },
1821 }
14821822}
14831823
14841824fn groupConcurrent(
14851825 userdata: ?*anyopaque,
1486 group: *Io.Group,
1826 type_erased: *Io.Group,
14871827 context: []const u8,
14881828 context_alignment: Alignment,
1489 start: *const fn (*Io.Group, context: *const anyopaque) Io.Cancelable!void,
1829 start: *const fn (context: *const anyopaque) Io.Cancelable!void,
14901830) Io.ConcurrentError!void {
14911831 if (builtin.single_threaded) return error.ConcurrencyUnavailable;
14921832
14931833 const t: *Threaded = @ptrCast(@alignCast(userdata));
1834 const g: Group = .{ .ptr = type_erased };
14941835
14951836 const gpa = t.allocator;
1496 const gc = GroupClosure.init(gpa, group, context, context_alignment, start) catch
1497 return error.ConcurrencyUnavailable;
1837 const task = Group.Task.create(gpa, g, context, context_alignment, start) catch |err| switch (err) {
1838 error.OutOfMemory => return error.ConcurrencyUnavailable,
1839 };
1840 errdefer task.destroy(gpa);
14981841
14991842 t.mutex.lock();
15001843 defer t.mutex.unlock();
......@@ -1514,115 +1857,144 @@ fn groupConcurrent(
15141857
15151858 const thread = std.Thread.spawn(.{ .stack_size = t.stack_size }, worker, .{t}) catch
15161859 return error.ConcurrencyUnavailable;
1860
15171861 thread.detach();
15181862 }
15191863
1520 // Append to the group linked list inside the mutex to make `Io.Group.concurrent` thread-safe.
1521 gc.node = .{ .next = @ptrCast(@alignCast(group.token.load(.monotonic))) };
1522 group.token.store(&gc.node, .monotonic);
1523
1524 t.run_queue.prepend(&gc.closure.node);
1525
1526 // This needs to be done before unlocking the mutex to avoid a race with
1527 // the associated task finishing.
1528 const group_state: *std.atomic.Value(usize) = @ptrCast(&group.state);
1529 const prev_state = group_state.fetchAdd(GroupClosure.sync_one_pending, .monotonic);
1530 assert((prev_state / GroupClosure.sync_one_pending) < (std.math.maxInt(usize) / GroupClosure.sync_one_pending));
1864 // TODO: if this logic is changed to be lock-free, this `fetchAdd` must be released by the queue
1865 // prepend so that the task doesn't finish without observing this and try to decrement the count
1866 // below zero.
1867 _ = g.status().fetchAdd(.{
1868 .num_running = 1,
1869 .have_awaiter = false,
1870 .canceled = false,
1871 }, .monotonic);
1872 t.run_queue.prepend(&task.runnable.node);
15311873
15321874 t.cond.signal();
15331875}
15341876
1535fn groupAwait(userdata: ?*anyopaque, group: *Io.Group, initial_token: *anyopaque) Io.Cancelable!void {
1877fn groupAwait(userdata: ?*anyopaque, type_erased: *Io.Group, initial_token: *anyopaque) Io.Cancelable!void {
1878 _ = initial_token; // we need to load `token` *after* the group finishes
15361879 const t: *Threaded = @ptrCast(@alignCast(userdata));
1537 const gpa = t.allocator;
1880 const g: Group = .{ .ptr = type_erased };
15381881
1539 _ = initial_token; // we need to load `token` *after* the group finishes
1882 var num_completed: std.atomic.Value(u32) = .init(0);
1883 g.awaiter().* = &num_completed;
15401884
1541 if (builtin.single_threaded) unreachable; // we never set `group.token` to non-`null`
1885 const pre_await_status = g.status().fetchOr(.{
1886 .num_running = 0,
1887 .have_awaiter = true,
1888 .canceled = false,
1889 }, .acq_rel); // acquire results if complete; release `g.awaiter()`
15421890
1543 const group_state: *std.atomic.Value(usize) = @ptrCast(&group.state);
1544 const event: *Io.Event = @ptrCast(&group.context);
1545 const prev_state = group_state.fetchAdd(GroupClosure.sync_is_waiting, .acquire);
1546 assert(prev_state & GroupClosure.sync_is_waiting == 0);
1547 {
1548 errdefer _ = group_state.fetchSub(GroupClosure.sync_is_waiting, .monotonic);
1549 // This event.wait can return error.Canceled, in which case this logic does
1550 // *not* propagate cancel requests to each group member. Instead, the user
1551 // code will likely do this with a defered call to groupCancel, or,
1552 // intentionally not do this.
1553 if ((prev_state / GroupClosure.sync_one_pending) > 0) try event.wait(ioBasic(t));
1891 assert(!pre_await_status.have_awaiter);
1892 assert(!pre_await_status.canceled);
1893 if (pre_await_status.num_running == 0) {
1894 // Already done. Since the group is finished, it's illegal to spawn more tasks in it
1895 // until we return, so we can access `g.status()` non-atomically.
1896 g.status().raw.have_awaiter = false;
1897 return;
15541898 }
15551899
1556 // Since the group has now finished, it's illegal to add more tasks to it until we return. It's
1557 // also illegal for us to race with another `await` or `cancel`. Therefore, we must be the only
1558 // thread who can access `group` right now.
1559 var it: ?*std.SinglyLinkedList.Node = @ptrCast(@alignCast(group.token.raw));
1560 group.token.raw = null;
1561 while (it) |node| {
1562 it = node.next; // update `it` now, because `deinit` will invalidate `node`
1563 const gc: *GroupClosure = @fieldParentPtr("node", node);
1564 gc.deinit(gpa);
1900 while (Thread.futexWait(&num_completed.raw, 0, null)) {
1901 switch (num_completed.load(.acquire)) { // acquire task results
1902 0 => continue,
1903 1 => break,
1904 else => unreachable, // group was reused before `await` returned
1905 }
1906 } else |err| switch (err) {
1907 error.Canceled => {
1908 const pre_cancel_status = g.status().fetchOr(.{
1909 .num_running = 0,
1910 .have_awaiter = false,
1911 .canceled = true,
1912 }, .acq_rel); // acquire results if complete; release `g.awaiter()`
1913 assert(pre_cancel_status.have_awaiter);
1914 assert(!pre_cancel_status.canceled);
1915
1916 // Even if `pre_cancel_status.num_running == 0`, we still need to wait for the signal,
1917 // because in that case the last member of the group is already trying to modify it.
1918 // However, if we know everything is done, we *can* skip signaling blocked threads.
1919 const skip_signals = pre_cancel_status.num_running == 0;
1920 g.waitForCancelWithSignaling(t, &num_completed, skip_signals);
1921
1922 // The group is finished, so it's illegal to spawn more tasks in it until we return, so
1923 // we can access `g.status()` non-atomically.
1924 g.status().raw.canceled = false;
1925 g.status().raw.have_awaiter = false;
1926 return error.Canceled;
1927 },
15651928 }
1929
1930 // The group is finished, so it's illegal to spawn more tasks in it until we return, so
1931 // we can access `g.status()` non-atomically.
1932 g.status().raw.have_awaiter = false;
15661933}
15671934
1568fn groupCancel(userdata: ?*anyopaque, group: *Io.Group, initial_token: *anyopaque) void {
1935fn groupCancel(userdata: ?*anyopaque, type_erased: *Io.Group, initial_token: *anyopaque) void {
1936 _ = initial_token;
15691937 const t: *Threaded = @ptrCast(@alignCast(userdata));
1570 const gpa = t.allocator;
1938 const g: Group = .{ .ptr = type_erased };
15711939
1572 _ = initial_token; // we need to load `token` *after* the group finishes
1940 var num_completed: std.atomic.Value(u32) = .init(0);
1941 g.awaiter().* = &num_completed;
15731942
1574 if (builtin.single_threaded) unreachable; // we never set `group.token` to non-`null`
1943 const pre_cancel_status = g.status().fetchOr(.{
1944 .num_running = 0,
1945 .have_awaiter = true,
1946 .canceled = true,
1947 }, .acq_rel); // acquire results if complete; release `g.awaiter()`
15751948
1576 {
1577 var it: ?*std.SinglyLinkedList.Node = @ptrCast(@alignCast(group.token.load(.monotonic)));
1578 while (it) |node| : (it = node.next) {
1579 const gc: *GroupClosure = @fieldParentPtr("node", node);
1580 gc.closure.requestCancel(t);
1581 }
1949 assert(!pre_cancel_status.have_awaiter);
1950 assert(!pre_cancel_status.canceled);
1951 if (pre_cancel_status.num_running == 0) {
1952 // Already done. Since the group is finished, it's illegal to spawn more tasks in it
1953 // until we return, so we can access `g.status()` non-atomically.
1954 g.status().raw.have_awaiter = false;
1955 g.status().raw.canceled = false;
1956 return;
15821957 }
15831958
1584 const group_state: *std.atomic.Value(usize) = @ptrCast(&group.state);
1585 const event: *Io.Event = @ptrCast(&group.context);
1586 const prev_state = group_state.fetchAdd(GroupClosure.sync_is_waiting, .acquire);
1587 assert(prev_state & GroupClosure.sync_is_waiting == 0);
1588 if ((prev_state / GroupClosure.sync_one_pending) > 0) event.waitUncancelable(ioBasic(t));
1959 g.waitForCancelWithSignaling(t, &num_completed, false);
15891960
1590 // Since the group has now finished, it's illegal to add more tasks to it until we return. It's
1591 // also illegal for us to race with another `await` or `cancel`. Therefore, we must be the only
1592 // thread who can access `group` right now.
1593 var it: ?*std.SinglyLinkedList.Node = @ptrCast(@alignCast(group.token.raw));
1594 group.token.raw = null;
1595 while (it) |node| {
1596 it = node.next; // update `it` now, because `deinit` will invalidate `node`
1597 const gc: *GroupClosure = @fieldParentPtr("node", node);
1598 gc.deinit(gpa);
1599 }
1961 g.status().raw = .{ .num_running = 0, .have_awaiter = false, .canceled = false };
16001962}
16011963
16021964fn recancel(userdata: ?*anyopaque) void {
16031965 const t: *Threaded = @ptrCast(@alignCast(userdata));
1604 const current_thread: *Thread = .getCurrent(t);
1605 const cancel_status = &current_thread.current_closure.?.cancel_status;
1606 switch (@atomicLoad(CancelStatus, cancel_status, .monotonic)) {
1607 .none => unreachable, // called `recancel` when not canceled
1608 .requested => unreachable, // called `recancel` when cancelation was already outstanding
1609 .acknowledged => {},
1610 _ => unreachable, // invalid state: not in a syscall
1966 _ = t;
1967 recancelInner();
1968}
1969fn recancelInner() void {
1970 const thread = Thread.current.?; // called `recancel` but was not canceled
1971 switch (thread.status.fetchXor(.{
1972 .cancelation = @enumFromInt(0b001),
1973 .awaitable = .null,
1974 }, .monotonic).cancelation) {
1975 .canceled => {},
1976 .none => unreachable, // called `recancel` but was not canceled
1977 .canceling => unreachable, // called `recancel` but cancelation was already pending
1978 .parked => unreachable,
1979 .blocked => unreachable,
1980 .blocked_windows_dns => unreachable,
1981 .blocked_canceling => unreachable,
16111982 }
1612 @atomicStore(CancelStatus, cancel_status, .requested, .monotonic);
16131983}
16141984
16151985fn swapCancelProtection(userdata: ?*anyopaque, new: Io.CancelProtection) Io.CancelProtection {
16161986 const t: *Threaded = @ptrCast(@alignCast(userdata));
1617 const current_thread: *Thread = .getCurrent(t);
1618 const old = current_thread.cancel_protection;
1619 current_thread.cancel_protection = new;
1987 _ = t;
1988 const thread = Thread.current orelse return .unblocked;
1989 const old = thread.cancel_protection;
1990 thread.cancel_protection = new;
16201991 return old;
16211992}
16221993
16231994fn checkCancel(userdata: ?*anyopaque) Io.Cancelable!void {
16241995 const t: *Threaded = @ptrCast(@alignCast(userdata));
1625 return Thread.getCurrent(t).checkCancel();
1996 _ = t;
1997 return Thread.checkCancel();
16261998}
16271999
16282000fn await(
......@@ -1633,8 +2005,59 @@ fn await(
16332005) void {
16342006 _ = result_alignment;
16352007 const t: *Threaded = @ptrCast(@alignCast(userdata));
1636 const closure: *AsyncClosure = @ptrCast(@alignCast(any_future));
1637 closure.waitAndDeinit(t, result);
2008 const future: *Future = @ptrCast(@alignCast(any_future));
2009
2010 var num_completed: std.atomic.Value(u32) = .init(0);
2011 future.awaiter = &num_completed;
2012
2013 const pre_await_status = future.status.fetchOr(.{
2014 .tag = .pending_awaited,
2015 .thread = .null,
2016 }, .acq_rel); // acquire results if complete; release `future.awaiter`
2017 switch (pre_await_status.tag) {
2018 .pending => while (Thread.futexWait(&num_completed.raw, 0, null)) {
2019 switch (num_completed.load(.acquire)) { // acquire task results
2020 0 => continue,
2021 1 => break,
2022 else => unreachable, // group was reused before `await` returned
2023 }
2024 } else |err| switch (err) {
2025 error.Canceled => {
2026 const pre_cancel_status = future.status.fetchOr(.{
2027 .tag = .pending_canceled,
2028 .thread = .null,
2029 }, .acq_rel); // acquire results if complete; release `future.awaiter`
2030 switch (pre_cancel_status.tag) {
2031 .pending => unreachable, // invalid state: we already awaited
2032 .pending_awaited => {
2033 const working_thread = pre_cancel_status.thread.unpack();
2034 future.waitForCancelWithSignaling(t, &num_completed, @alignCast(working_thread));
2035 },
2036 .pending_canceled => unreachable, // `await` raced with `cancel`
2037 .done => {
2038 // The task just finished, but we still need to wait for the signal, because the
2039 // task thread already figured out that they need to update `future.awaiter`.
2040 future.waitForCancelWithSignaling(t, &num_completed, null);
2041 },
2042 }
2043 // If the future did not acknowledge the cancelation, we need to mark it outstanding
2044 // for us. Because `future.status.tag == .done`, the information about whether there
2045 // was an acknowledged cancelation is encoded in `future.status.thread`.
2046 const final_status = future.status.load(.monotonic);
2047 assert(final_status.tag == .done);
2048 switch (final_status.thread) {
2049 .null => recancelInner(), // cancelation was not acknowledged, so it's ours
2050 .all_ones => {}, // cancelation was acknowledged, so it was this task's job to propagate it
2051 _ => unreachable,
2052 }
2053 },
2054 },
2055 .pending_awaited => unreachable, // `await` raced with `await`
2056 .pending_canceled => unreachable, // `await` raced with `cancel`
2057 .done => {},
2058 }
2059 @memcpy(result, future.resultPointer());
2060 future.destroy(t.allocator);
16382061}
16392062
16402063fn cancel(
......@@ -1645,28 +2068,44 @@ fn cancel(
16452068) void {
16462069 _ = result_alignment;
16472070 const t: *Threaded = @ptrCast(@alignCast(userdata));
1648 const ac: *AsyncClosure = @ptrCast(@alignCast(any_future));
1649 ac.closure.requestCancel(t);
1650 ac.waitAndDeinit(t, result);
2071 const future: *Future = @ptrCast(@alignCast(any_future));
2072
2073 var num_completed: std.atomic.Value(u32) = .init(0);
2074 future.awaiter = &num_completed;
2075
2076 const pre_cancel_status = future.status.fetchOr(.{
2077 .tag = .pending_canceled,
2078 .thread = .null,
2079 }, .acq_rel); // acquire results if complete; release `future.awaiter`
2080 switch (pre_cancel_status.tag) {
2081 .pending => {
2082 const working_thread = pre_cancel_status.thread.unpack();
2083 future.waitForCancelWithSignaling(t, &num_completed, @alignCast(working_thread));
2084 },
2085 .pending_awaited => unreachable, // `await` raced with `await`
2086 .pending_canceled => unreachable, // `await` raced with `cancel`
2087 .done => {},
2088 }
2089 @memcpy(result, future.resultPointer());
2090 future.destroy(t.allocator);
16512091}
16522092
16532093fn futexWait(userdata: ?*anyopaque, ptr: *const u32, expected: u32, timeout: Io.Timeout) Io.Cancelable!void {
16542094 if (builtin.single_threaded) unreachable; // Deadlock.
16552095 const t: *Threaded = @ptrCast(@alignCast(userdata));
1656 const current_thread = Thread.getCurrent(t);
16572096 const t_io = ioBasic(t);
16582097 const timeout_ns: ?u64 = ns: {
16592098 const d = (timeout.toDurationFromNow(t_io) catch break :ns 10) orelse break :ns null;
16602099 break :ns std.math.lossyCast(u64, d.raw.toNanoseconds());
16612100 };
1662 return Thread.futexWaitTimed(current_thread, ptr, expected, timeout_ns);
2101 return Thread.futexWait(ptr, expected, timeout_ns);
16632102}
16642103
16652104fn futexWaitUncancelable(userdata: ?*anyopaque, ptr: *const u32, expected: u32) void {
16662105 if (builtin.single_threaded) unreachable; // Deadlock.
16672106 const t: *Threaded = @ptrCast(@alignCast(userdata));
16682107 _ = t;
1669 Thread.futexWaitUncancelable(ptr, expected);
2108 Thread.futexWaitUncancelable(ptr, expected, null);
16702109}
16712110
16722111fn futexWake(userdata: ?*anyopaque, ptr: *const u32, max_waiters: u32) void {
......@@ -1684,24 +2123,24 @@ const dirCreateDir = switch (native_os) {
16842123
16852124fn dirCreateDirPosix(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, permissions: Dir.Permissions) Dir.CreateDirError!void {
16862125 const t: *Threaded = @ptrCast(@alignCast(userdata));
1687 const current_thread = Thread.getCurrent(t);
2126 _ = t;
16882127
16892128 var path_buffer: [posix.PATH_MAX]u8 = undefined;
16902129 const sub_path_posix = try pathToPosix(sub_path, &path_buffer);
16912130
1692 try current_thread.beginSyscall();
2131 const syscall: Syscall = try .start();
16932132 while (true) {
16942133 switch (posix.errno(posix.system.mkdirat(dir.handle, sub_path_posix, permissions.toMode()))) {
16952134 .SUCCESS => {
1696 current_thread.endSyscall();
2135 syscall.finish();
16972136 return;
16982137 },
16992138 .INTR => {
1700 try current_thread.checkCancel();
2139 try syscall.checkCancel();
17012140 continue;
17022141 },
17032142 else => |e| {
1704 current_thread.endSyscall();
2143 syscall.finish();
17052144 switch (e) {
17062145 .ACCES => return error.AccessDenied,
17072146 .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
17302169fn dirCreateDirWasi(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, permissions: Dir.Permissions) Dir.CreateDirError!void {
17312170 if (builtin.link_libc) return dirCreateDirPosix(userdata, dir, sub_path, permissions);
17322171 const t: *Threaded = @ptrCast(@alignCast(userdata));
1733 const current_thread = Thread.getCurrent(t);
1734 try current_thread.beginSyscall();
2172 _ = t;
2173 const syscall: Syscall = try .start();
17352174 while (true) {
17362175 switch (std.os.wasi.path_create_directory(dir.handle, sub_path.ptr, sub_path.len)) {
17372176 .SUCCESS => {
1738 current_thread.endSyscall();
2177 syscall.finish();
17392178 return;
17402179 },
17412180 .INTR => {
1742 try current_thread.checkCancel();
2181 try syscall.checkCancel();
17432182 continue;
17442183 },
17452184 else => |e| {
1746 current_thread.endSyscall();
2185 syscall.finish();
17472186 switch (e) {
17482187 .ACCES => return error.AccessDenied,
17492188 .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
17702209
17712210fn dirCreateDirWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, permissions: Dir.Permissions) Dir.CreateDirError!void {
17722211 const t: *Threaded = @ptrCast(@alignCast(userdata));
1773 const current_thread = Thread.getCurrent(t);
1774 try current_thread.checkCancel();
2212 _ = t;
17752213
17762214 const sub_path_w = try windows.sliceToPrefixedFileW(dir.handle, sub_path);
17772215 _ = permissions; // TODO use this value
1778 const sub_dir_handle = windows.OpenFile(sub_path_w.span(), .{
1779 .dir = dir.handle,
1780 .access_mask = .{
1781 .GENERIC = .{ .READ = true },
1782 .STANDARD = .{ .SYNCHRONIZE = true },
1783 },
1784 .creation = .CREATE,
1785 .filter = .dir_only,
1786 }) catch |err| switch (err) {
1787 error.IsDir => return error.Unexpected,
1788 error.PipeBusy => return error.Unexpected,
1789 error.NoDevice => return error.Unexpected,
1790 error.WouldBlock => return error.Unexpected,
1791 error.AntivirusInterference => return error.Unexpected,
1792 else => |e| return e,
2216
2217 const syscall: Syscall = try .start();
2218 const sub_dir_handle = while (true) {
2219 break windows.OpenFile(sub_path_w.span(), .{
2220 .dir = dir.handle,
2221 .access_mask = .{
2222 .GENERIC = .{ .READ = true },
2223 .STANDARD = .{ .SYNCHRONIZE = true },
2224 },
2225 .creation = .CREATE,
2226 .filter = .dir_only,
2227 }) catch |err| switch (err) {
2228 error.IsDir => return syscall.fail(error.Unexpected),
2229 error.PipeBusy => return syscall.fail(error.Unexpected),
2230 error.NoDevice => return syscall.fail(error.Unexpected),
2231 error.WouldBlock => return syscall.fail(error.Unexpected),
2232 error.AntivirusInterference => return syscall.fail(error.Unexpected),
2233 error.OperationCanceled => {
2234 try syscall.checkCancel();
2235 continue;
2236 },
2237 else => |e| return syscall.fail(e),
2238 };
17932239 };
2240 syscall.finish();
17942241 windows.CloseHandle(sub_dir_handle);
17952242}
17962243
......@@ -1858,7 +2305,6 @@ fn dirCreateDirPathOpenWindows(
18582305 options: Dir.OpenOptions,
18592306) Dir.CreateDirPathOpenError!Dir {
18602307 const t: *Threaded = @ptrCast(@alignCast(userdata));
1861 const current_thread = Thread.getCurrent(t);
18622308 const w = windows;
18632309
18642310 _ = permissions; // TODO apply these permissions
......@@ -1870,9 +2316,7 @@ fn dirCreateDirPathOpenWindows(
18702316 .path = sub_path,
18712317 };
18722318
1873 while (true) {
1874 try current_thread.checkCancel();
1875
2319 components: while (true) {
18762320 const sub_path_w_array = try w.sliceToPrefixedFileW(dir.handle, component.path);
18772321 const sub_path_w = sub_path_w_array.span();
18782322 const is_last = it.peekNext() == null;
......@@ -1887,7 +2331,9 @@ fn dirCreateDirPathOpenWindows(
18872331 .Buffer = @constCast(sub_path_w.ptr),
18882332 };
18892333 var io_status_block: w.IO_STATUS_BLOCK = undefined;
1890 const rc = w.ntdll.NtCreateFile(
2334
2335 const syscall: Syscall = try .start();
2336 while (true) switch (w.ntdll.NtCreateFile(
18912337 &result.handle,
18922338 .{
18932339 .SPECIFIC = .{ .FILE_DIRECTORY = .{
......@@ -1922,16 +2368,20 @@ fn dirCreateDirPathOpenWindows(
19222368 },
19232369 null,
19242370 0,
1925 );
1926
1927 switch (rc) {
2371 )) {
19282372 .SUCCESS => {
2373 syscall.finish();
19292374 component = it.next() orelse return result;
19302375 w.CloseHandle(result.handle);
2376 continue :components;
2377 },
2378 .CANCELLED => {
2379 try syscall.checkCancel();
19312380 continue;
19322381 },
1933 .OBJECT_NAME_INVALID => return error.BadPathName,
2382 .OBJECT_NAME_INVALID => return syscall.fail(error.BadPathName),
19342383 .OBJECT_NAME_COLLISION => {
2384 syscall.finish();
19352385 assert(!is_last);
19362386 // stat the file and return an error if it's not a directory
19372387 // this is important because otherwise a dangling symlink
......@@ -1942,23 +2392,24 @@ fn dirCreateDirPathOpenWindows(
19422392 if (fstat.kind != .directory) return error.NotDir;
19432393
19442394 component = it.next().?;
1945 continue;
2395 continue :components;
19462396 },
19472397
19482398 .OBJECT_NAME_NOT_FOUND,
19492399 .OBJECT_PATH_NOT_FOUND,
19502400 => {
2401 syscall.finish();
19512402 component = it.previous() orelse return error.FileNotFound;
1952 continue;
2403 continue :components;
19532404 },
19542405
1955 .NOT_A_DIRECTORY => return error.NotDir,
2406 .NOT_A_DIRECTORY => return syscall.fail(error.NotDir),
19562407 // This can happen if the directory has 'List folder contents' permission set to 'Deny'
19572408 // and the directory is trying to be opened for iteration.
1958 .ACCESS_DENIED => return error.AccessDenied,
1959 .INVALID_PARAMETER => |err| return w.statusBug(err),
1960 else => return w.unexpectedStatus(rc),
1961 }
2409 .ACCESS_DENIED => return syscall.fail(error.AccessDenied),
2410 .INVALID_PARAMETER => |s| return syscall.ntstatusBug(s),
2411 else => |s| return syscall.unexpectedNtstatus(s),
2412 };
19622413 }
19632414}
19642415
......@@ -2000,7 +2451,7 @@ fn dirStatFileLinux(
20002451 options: Dir.StatFileOptions,
20012452) Dir.StatFileError!File.Stat {
20022453 const t: *Threaded = @ptrCast(@alignCast(userdata));
2003 const current_thread = Thread.getCurrent(t);
2454 _ = t;
20042455 const linux = std.os.linux;
20052456 const use_c = std.c.versionCheck(if (builtin.abi.isAndroid())
20062457 .{ .major = 30, .minor = 0, .patch = 0 }
......@@ -2014,20 +2465,20 @@ fn dirStatFileLinux(
20142465 const flags: u32 = linux.AT.NO_AUTOMOUNT |
20152466 @as(u32, if (!options.follow_symlinks) linux.AT.SYMLINK_NOFOLLOW else 0);
20162467
2017 try current_thread.beginSyscall();
2468 const syscall: Syscall = try .start();
20182469 while (true) {
20192470 var statx = std.mem.zeroes(linux.Statx);
20202471 switch (sys.errno(sys.statx(dir.handle, sub_path_posix, flags, linux_statx_request, &statx))) {
20212472 .SUCCESS => {
2022 current_thread.endSyscall();
2473 syscall.finish();
20232474 return statFromLinux(&statx);
20242475 },
20252476 .INTR => {
2026 try current_thread.checkCancel();
2477 try syscall.checkCancel();
20272478 continue;
20282479 },
20292480 else => |e| {
2030 current_thread.endSyscall();
2481 syscall.finish();
20312482 switch (e) {
20322483 .ACCES => return error.AccessDenied,
20332484 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
......@@ -2052,31 +2503,31 @@ fn dirStatFilePosix(
20522503 options: Dir.StatFileOptions,
20532504) Dir.StatFileError!File.Stat {
20542505 const t: *Threaded = @ptrCast(@alignCast(userdata));
2055 const current_thread = Thread.getCurrent(t);
2506 _ = t;
20562507
20572508 var path_buffer: [posix.PATH_MAX]u8 = undefined;
20582509 const sub_path_posix = try pathToPosix(sub_path, &path_buffer);
20592510
20602511 const flags: u32 = if (!options.follow_symlinks) posix.AT.SYMLINK_NOFOLLOW else 0;
20612512
2062 return posixStatFile(current_thread, dir.handle, sub_path_posix, flags);
2513 return posixStatFile(dir.handle, sub_path_posix, flags);
20632514}
20642515
2065fn posixStatFile(current_thread: *Thread, dir_fd: posix.fd_t, sub_path: [:0]const u8, flags: u32) Dir.StatFileError!File.Stat {
2066 try current_thread.beginSyscall();
2516fn posixStatFile(dir_fd: posix.fd_t, sub_path: [:0]const u8, flags: u32) Dir.StatFileError!File.Stat {
2517 const syscall: Syscall = try .start();
20672518 while (true) {
20682519 var stat = std.mem.zeroes(posix.Stat);
20692520 switch (posix.errno(fstatat_sym(dir_fd, sub_path, &stat, flags))) {
20702521 .SUCCESS => {
2071 current_thread.endSyscall();
2522 syscall.finish();
20722523 return statFromPosix(&stat);
20732524 },
20742525 .INTR => {
2075 try current_thread.checkCancel();
2526 try syscall.checkCancel();
20762527 continue;
20772528 },
20782529 else => |e| {
2079 current_thread.endSyscall();
2530 syscall.finish();
20802531 switch (e) {
20812532 .INVAL => |err| return errnoBug(err),
20822533 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
......@@ -2118,25 +2569,25 @@ fn dirStatFileWasi(
21182569) Dir.StatFileError!File.Stat {
21192570 if (builtin.link_libc) return dirStatFilePosix(userdata, dir, sub_path, options);
21202571 const t: *Threaded = @ptrCast(@alignCast(userdata));
2121 const current_thread = Thread.getCurrent(t);
2572 _ = t;
21222573 const wasi = std.os.wasi;
21232574 const flags: wasi.lookupflags_t = .{
21242575 .SYMLINK_FOLLOW = options.follow_symlinks,
21252576 };
21262577 var stat: wasi.filestat_t = undefined;
2127 try current_thread.beginSyscall();
2578 const syscall: Syscall = try .start();
21282579 while (true) {
21292580 switch (wasi.path_filestat_get(dir.handle, flags, sub_path.ptr, sub_path.len, &stat)) {
21302581 .SUCCESS => {
2131 current_thread.endSyscall();
2582 syscall.finish();
21322583 return statFromWasi(&stat);
21332584 },
21342585 .INTR => {
2135 try current_thread.checkCancel();
2586 try syscall.checkCancel();
21362587 continue;
21372588 },
21382589 else => |e| {
2139 current_thread.endSyscall();
2590 syscall.finish();
21402591 switch (e) {
21412592 .INVAL => |err| return errnoBug(err),
21422593 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
......@@ -2159,24 +2610,23 @@ fn fileLength(userdata: ?*anyopaque, file: File) File.LengthError!u64 {
21592610 const t: *Threaded = @ptrCast(@alignCast(userdata));
21602611
21612612 if (native_os == .linux) {
2162 const current_thread = Thread.getCurrent(t);
21632613 const linux = std.os.linux;
21642614
2165 try current_thread.beginSyscall();
2615 const syscall: Syscall = try .start();
21662616 while (true) {
21672617 var statx = std.mem.zeroes(linux.Statx);
21682618 switch (linux.errno(linux.statx(file.handle, "", linux.AT.EMPTY_PATH, .{ .SIZE = true }, &statx))) {
21692619 .SUCCESS => {
2170 current_thread.endSyscall();
2620 syscall.finish();
21712621 if (!statx.mask.SIZE) return error.Unexpected;
21722622 return statx.size;
21732623 },
21742624 .INTR => {
2175 try current_thread.checkCancel();
2625 try syscall.checkCancel();
21762626 continue;
21772627 },
21782628 else => |e| {
2179 current_thread.endSyscall();
2629 syscall.finish();
21802630 switch (e) {
21812631 .ACCES => |err| return errnoBug(err),
21822632 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
......@@ -2209,24 +2659,24 @@ const fileStat = switch (native_os) {
22092659
22102660fn fileStatPosix(userdata: ?*anyopaque, file: File) File.StatError!File.Stat {
22112661 const t: *Threaded = @ptrCast(@alignCast(userdata));
2212 const current_thread = Thread.getCurrent(t);
2662 _ = t;
22132663
22142664 if (posix.Stat == void) return error.Streaming;
22152665
2216 try current_thread.beginSyscall();
2666 const syscall: Syscall = try .start();
22172667 while (true) {
22182668 var stat = std.mem.zeroes(posix.Stat);
22192669 switch (posix.errno(fstat_sym(file.handle, &stat))) {
22202670 .SUCCESS => {
2221 current_thread.endSyscall();
2671 syscall.finish();
22222672 return statFromPosix(&stat);
22232673 },
22242674 .INTR => {
2225 try current_thread.checkCancel();
2675 try syscall.checkCancel();
22262676 continue;
22272677 },
22282678 else => |e| {
2229 current_thread.endSyscall();
2679 syscall.finish();
22302680 switch (e) {
22312681 .INVAL => |err| return errnoBug(err),
22322682 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
......@@ -2241,7 +2691,7 @@ fn fileStatPosix(userdata: ?*anyopaque, file: File) File.StatError!File.Stat {
22412691
22422692fn fileStatLinux(userdata: ?*anyopaque, file: File) File.StatError!File.Stat {
22432693 const t: *Threaded = @ptrCast(@alignCast(userdata));
2244 const current_thread = Thread.getCurrent(t);
2694 _ = t;
22452695 const linux = std.os.linux;
22462696 const use_c = std.c.versionCheck(if (builtin.abi.isAndroid())
22472697 .{ .major = 30, .minor = 0, .patch = 0 }
......@@ -2249,20 +2699,20 @@ fn fileStatLinux(userdata: ?*anyopaque, file: File) File.StatError!File.Stat {
22492699 .{ .major = 2, .minor = 28, .patch = 0 });
22502700 const sys = if (use_c) std.c else std.os.linux;
22512701
2252 try current_thread.beginSyscall();
2702 const syscall: Syscall = try .start();
22532703 while (true) {
22542704 var statx = std.mem.zeroes(linux.Statx);
22552705 switch (sys.errno(sys.statx(file.handle, "", linux.AT.EMPTY_PATH, linux_statx_request, &statx))) {
22562706 .SUCCESS => {
2257 current_thread.endSyscall();
2707 syscall.finish();
22582708 return statFromLinux(&statx);
22592709 },
22602710 .INTR => {
2261 try current_thread.checkCancel();
2711 try syscall.checkCancel();
22622712 continue;
22632713 },
22642714 else => |e| {
2265 current_thread.endSyscall();
2715 syscall.finish();
22662716 switch (e) {
22672717 .ACCES => |err| return errnoBug(err),
22682718 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
......@@ -2282,21 +2732,32 @@ fn fileStatLinux(userdata: ?*anyopaque, file: File) File.StatError!File.Stat {
22822732
22832733fn fileStatWindows(userdata: ?*anyopaque, file: File) File.StatError!File.Stat {
22842734 const t: *Threaded = @ptrCast(@alignCast(userdata));
2285 const current_thread = Thread.getCurrent(t);
2286 try current_thread.checkCancel();
2735 _ = t;
22872736
22882737 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
22892738 var info: windows.FILE.ALL_INFORMATION = undefined;
2290 const rc = windows.ntdll.NtQueryInformationFile(file.handle, &io_status_block, &info, @sizeOf(windows.FILE.ALL_INFORMATION), .All);
2291 switch (rc) {
2292 .SUCCESS => {},
2293 // Buffer overflow here indicates that there is more information available than was able to be stored in the buffer
2294 // size provided. This is treated as success because the type of variable-length information that this would be relevant for
2295 // (name, volume name, etc) we don't care about.
2296 .BUFFER_OVERFLOW => {},
2297 .INVALID_PARAMETER => |err| return windows.statusBug(err),
2298 .ACCESS_DENIED => return error.AccessDenied,
2299 else => return windows.unexpectedStatus(rc),
2739 {
2740 const syscall: Syscall = try .start();
2741 while (true) switch (windows.ntdll.NtQueryInformationFile(
2742 file.handle,
2743 &io_status_block,
2744 &info,
2745 @sizeOf(windows.FILE.ALL_INFORMATION),
2746 .All,
2747 )) {
2748 .SUCCESS => break syscall.finish(),
2749 // Buffer overflow here indicates that there is more information available than was able to be stored in the buffer
2750 // size provided. This is treated as success because the type of variable-length information that this would be relevant for
2751 // (name, volume name, etc) we don't care about.
2752 .BUFFER_OVERFLOW => break syscall.finish(),
2753 .INVALID_PARAMETER => |err| return syscall.ntstatusBug(err),
2754 .ACCESS_DENIED => return syscall.fail(error.AccessDenied),
2755 .CANCELLED => {
2756 try syscall.checkCancel();
2757 continue;
2758 },
2759 else => |s| return syscall.unexpectedNtstatus(s),
2760 };
23002761 }
23012762 return .{
23022763 .inode = info.InternalInformation.IndexNumber,
......@@ -2304,15 +2765,25 @@ fn fileStatWindows(userdata: ?*anyopaque, file: File) File.StatError!File.Stat {
23042765 .permissions = .default_file,
23052766 .kind = if (info.BasicInformation.FileAttributes.REPARSE_POINT) reparse_point: {
23062767 var tag_info: windows.FILE.ATTRIBUTE_TAG_INFO = undefined;
2307 const tag_rc = windows.ntdll.NtQueryInformationFile(file.handle, &io_status_block, &tag_info, @sizeOf(windows.FILE.ATTRIBUTE_TAG_INFO), .AttributeTag);
2308 switch (tag_rc) {
2309 .SUCCESS => {},
2768 const syscall: Syscall = try .start();
2769 while (true) switch (windows.ntdll.NtQueryInformationFile(
2770 file.handle,
2771 &io_status_block,
2772 &tag_info,
2773 @sizeOf(windows.FILE.ATTRIBUTE_TAG_INFO),
2774 .AttributeTag,
2775 )) {
2776 .SUCCESS => break syscall.finish(),
23102777 // INFO_LENGTH_MISMATCH and ACCESS_DENIED are the only documented possible errors
23112778 // https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-fscc/d295752f-ce89-4b98-8553-266d37c84f0e
2312 .INFO_LENGTH_MISMATCH => |err| return windows.statusBug(err),
2313 .ACCESS_DENIED => return error.AccessDenied,
2314 else => return windows.unexpectedStatus(rc),
2315 }
2779 .INFO_LENGTH_MISMATCH => |err| return syscall.ntstatusBug(err),
2780 .ACCESS_DENIED => return syscall.fail(error.AccessDenied),
2781 .CANCELLED => {
2782 try syscall.checkCancel();
2783 continue;
2784 },
2785 else => |s| return syscall.unexpectedNtstatus(s),
2786 };
23162787 if (tag_info.ReparseTag.IsSurrogate) break :reparse_point .sym_link;
23172788 // Unknown reparse point
23182789 break :reparse_point .unknown;
......@@ -2331,22 +2802,22 @@ fn fileStatWasi(userdata: ?*anyopaque, file: File) File.StatError!File.Stat {
23312802 if (builtin.link_libc) return fileStatPosix(userdata, file);
23322803
23332804 const t: *Threaded = @ptrCast(@alignCast(userdata));
2334 const current_thread = Thread.getCurrent(t);
2805 _ = t;
23352806
2336 try current_thread.beginSyscall();
2807 const syscall: Syscall = try .start();
23372808 while (true) {
23382809 var stat: std.os.wasi.filestat_t = undefined;
23392810 switch (std.os.wasi.fd_filestat_get(file.handle, &stat)) {
23402811 .SUCCESS => {
2341 current_thread.endSyscall();
2812 syscall.finish();
23422813 return statFromWasi(&stat);
23432814 },
23442815 .INTR => {
2345 try current_thread.checkCancel();
2816 try syscall.checkCancel();
23462817 continue;
23472818 },
23482819 else => |e| {
2349 current_thread.endSyscall();
2820 syscall.finish();
23502821 switch (e) {
23512822 .INVAL => |err| return errnoBug(err),
23522823 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
......@@ -2373,7 +2844,7 @@ fn dirAccessPosix(
23732844 options: Dir.AccessOptions,
23742845) Dir.AccessError!void {
23752846 const t: *Threaded = @ptrCast(@alignCast(userdata));
2376 const current_thread = Thread.getCurrent(t);
2847 _ = t;
23772848
23782849 var path_buffer: [posix.PATH_MAX]u8 = undefined;
23792850 const sub_path_posix = try pathToPosix(sub_path, &path_buffer);
......@@ -2385,19 +2856,19 @@ fn dirAccessPosix(
23852856 @as(u32, if (options.write) posix.W_OK else 0) |
23862857 @as(u32, if (options.execute) posix.X_OK else 0);
23872858
2388 try current_thread.beginSyscall();
2859 const syscall: Syscall = try .start();
23892860 while (true) {
23902861 switch (posix.errno(posix.system.faccessat(dir.handle, sub_path_posix, mode, flags))) {
23912862 .SUCCESS => {
2392 current_thread.endSyscall();
2863 syscall.finish();
23932864 return;
23942865 },
23952866 .INTR => {
2396 try current_thread.checkCancel();
2867 try syscall.checkCancel();
23972868 continue;
23982869 },
23992870 else => |e| {
2400 current_thread.endSyscall();
2871 syscall.finish();
24012872 switch (e) {
24022873 .ACCES => return error.AccessDenied,
24032874 .PERM => return error.PermissionDenied,
......@@ -2427,26 +2898,26 @@ fn dirAccessWasi(
24272898) Dir.AccessError!void {
24282899 if (builtin.link_libc) return dirAccessPosix(userdata, dir, sub_path, options);
24292900 const t: *Threaded = @ptrCast(@alignCast(userdata));
2430 const current_thread = Thread.getCurrent(t);
2901 _ = t;
24312902 const wasi = std.os.wasi;
24322903 const flags: wasi.lookupflags_t = .{
24332904 .SYMLINK_FOLLOW = options.follow_symlinks,
24342905 };
24352906 var stat: wasi.filestat_t = undefined;
24362907
2437 try current_thread.beginSyscall();
2908 const syscall: Syscall = try .start();
24382909 while (true) {
24392910 switch (wasi.path_filestat_get(dir.handle, flags, sub_path.ptr, sub_path.len, &stat)) {
24402911 .SUCCESS => {
2441 current_thread.endSyscall();
2912 syscall.finish();
24422913 break;
24432914 },
24442915 .INTR => {
2445 try current_thread.checkCancel();
2916 try syscall.checkCancel();
24462917 continue;
24472918 },
24482919 else => |e| {
2449 current_thread.endSyscall();
2920 syscall.finish();
24502921 switch (e) {
24512922 .INVAL => |err| return errnoBug(err),
24522923 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
......@@ -2498,8 +2969,7 @@ fn dirAccessWindows(
24982969 options: Dir.AccessOptions,
24992970) Dir.AccessError!void {
25002971 const t: *Threaded = @ptrCast(@alignCast(userdata));
2501 const current_thread = Thread.getCurrent(t);
2502 try current_thread.checkCancel();
2972 _ = t;
25032973
25042974 _ = options; // TODO
25052975
......@@ -2525,16 +2995,21 @@ fn dirAccessWindows(
25252995 .SecurityQualityOfService = null,
25262996 };
25272997 var basic_info: windows.FILE.BASIC_INFORMATION = undefined;
2528 switch (windows.ntdll.NtQueryAttributesFile(&attr, &basic_info)) {
2529 .SUCCESS => return,
2530 .OBJECT_NAME_NOT_FOUND => return error.FileNotFound,
2531 .OBJECT_PATH_NOT_FOUND => return error.FileNotFound,
2532 .OBJECT_NAME_INVALID => |err| return windows.statusBug(err),
2533 .INVALID_PARAMETER => |err| return windows.statusBug(err),
2534 .ACCESS_DENIED => return error.AccessDenied,
2535 .OBJECT_PATH_SYNTAX_BAD => |err| return windows.statusBug(err),
2536 else => |rc| return windows.unexpectedStatus(rc),
2537 }
2998 const syscall: Syscall = try .start();
2999 while (true) switch (windows.ntdll.NtQueryAttributesFile(&attr, &basic_info)) {
3000 .SUCCESS => return syscall.finish(),
3001 .CANCELLED => {
3002 try syscall.checkCancel();
3003 continue;
3004 },
3005 .OBJECT_NAME_NOT_FOUND => return syscall.fail(error.FileNotFound),
3006 .OBJECT_PATH_NOT_FOUND => return syscall.fail(error.FileNotFound),
3007 .OBJECT_NAME_INVALID => |err| return syscall.ntstatusBug(err),
3008 .INVALID_PARAMETER => |err| return syscall.ntstatusBug(err),
3009 .ACCESS_DENIED => return syscall.fail(error.AccessDenied),
3010 .OBJECT_PATH_SYNTAX_BAD => |err| return syscall.ntstatusBug(err),
3011 else => |rc| return syscall.unexpectedNtstatus(rc),
3012 };
25383013}
25393014
25403015const dirCreateFile = switch (native_os) {
......@@ -2550,7 +3025,7 @@ fn dirCreateFilePosix(
25503025 flags: File.CreateFlags,
25513026) File.OpenError!File {
25523027 const t: *Threaded = @ptrCast(@alignCast(userdata));
2553 const current_thread = Thread.getCurrent(t);
3028 _ = t;
25543029
25553030 var path_buffer: [posix.PATH_MAX]u8 = undefined;
25563031 const sub_path_posix = try pathToPosix(sub_path, &path_buffer);
......@@ -2579,49 +3054,51 @@ fn dirCreateFilePosix(
25793054 },
25803055 };
25813056
2582 try current_thread.beginSyscall();
2583 const fd: posix.fd_t = while (true) {
2584 const rc = openat_sym(dir.handle, sub_path_posix, os_flags, flags.permissions.toMode());
2585 switch (posix.errno(rc)) {
2586 .SUCCESS => {
2587 current_thread.endSyscall();
2588 break @intCast(rc);
2589 },
2590 .INTR => {
2591 try current_thread.checkCancel();
2592 continue;
2593 },
2594 else => |e| {
2595 current_thread.endSyscall();
2596 switch (e) {
2597 .FAULT => |err| return errnoBug(err),
2598 .INVAL => return error.BadPathName,
2599 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
2600 .ACCES => return error.AccessDenied,
2601 .FBIG => return error.FileTooBig,
2602 .OVERFLOW => return error.FileTooBig,
2603 .ISDIR => return error.IsDir,
2604 .LOOP => return error.SymLinkLoop,
2605 .MFILE => return error.ProcessFdQuotaExceeded,
2606 .NAMETOOLONG => return error.NameTooLong,
2607 .NFILE => return error.SystemFdQuotaExceeded,
2608 .NODEV => return error.NoDevice,
2609 .NOENT => return error.FileNotFound,
2610 .SRCH => return error.FileNotFound, // Linux when accessing procfs.
2611 .NOMEM => return error.SystemResources,
2612 .NOSPC => return error.NoSpaceLeft,
2613 .NOTDIR => return error.NotDir,
2614 .PERM => return error.PermissionDenied,
2615 .EXIST => return error.PathAlreadyExists,
2616 .BUSY => return error.DeviceBusy,
2617 .OPNOTSUPP => return error.FileLocksUnsupported,
2618 .AGAIN => return error.WouldBlock,
2619 .TXTBSY => return error.FileBusy,
2620 .NXIO => return error.NoDevice,
2621 .ILSEQ => return error.BadPathName,
2622 else => |err| return posix.unexpectedErrno(err),
2623 }
2624 },
3057 const fd: posix.fd_t = fd: {
3058 const syscall: Syscall = try .start();
3059 while (true) {
3060 const rc = openat_sym(dir.handle, sub_path_posix, os_flags, flags.permissions.toMode());
3061 switch (posix.errno(rc)) {
3062 .SUCCESS => {
3063 syscall.finish();
3064 break :fd @intCast(rc);
3065 },
3066 .INTR => {
3067 try syscall.checkCancel();
3068 continue;
3069 },
3070 else => |e| {
3071 syscall.finish();
3072 switch (e) {
3073 .FAULT => |err| return errnoBug(err),
3074 .INVAL => return error.BadPathName,
3075 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
3076 .ACCES => return error.AccessDenied,
3077 .FBIG => return error.FileTooBig,
3078 .OVERFLOW => return error.FileTooBig,
3079 .ISDIR => return error.IsDir,
3080 .LOOP => return error.SymLinkLoop,
3081 .MFILE => return error.ProcessFdQuotaExceeded,
3082 .NAMETOOLONG => return error.NameTooLong,
3083 .NFILE => return error.SystemFdQuotaExceeded,
3084 .NODEV => return error.NoDevice,
3085 .NOENT => return error.FileNotFound,
3086 .SRCH => return error.FileNotFound, // Linux when accessing procfs.
3087 .NOMEM => return error.SystemResources,
3088 .NOSPC => return error.NoSpaceLeft,
3089 .NOTDIR => return error.NotDir,
3090 .PERM => return error.PermissionDenied,
3091 .EXIST => return error.PathAlreadyExists,
3092 .BUSY => return error.DeviceBusy,
3093 .OPNOTSUPP => return error.FileLocksUnsupported,
3094 .AGAIN => return error.WouldBlock,
3095 .TXTBSY => return error.FileBusy,
3096 .NXIO => return error.NoDevice,
3097 .ILSEQ => return error.BadPathName,
3098 else => |err| return posix.unexpectedErrno(err),
3099 }
3100 },
3101 }
26253102 }
26263103 };
26273104 errdefer posix.close(fd);
......@@ -2634,19 +3111,19 @@ fn dirCreateFilePosix(
26343111 .exclusive => posix.LOCK.EX | lock_nonblocking,
26353112 };
26363113
2637 try current_thread.beginSyscall();
3114 const syscall: Syscall = try .start();
26383115 while (true) {
26393116 switch (posix.errno(posix.system.flock(fd, lock_flags))) {
26403117 .SUCCESS => {
2641 current_thread.endSyscall();
3118 syscall.finish();
26423119 break;
26433120 },
26443121 .INTR => {
2645 try current_thread.checkCancel();
3122 try syscall.checkCancel();
26463123 continue;
26473124 },
26483125 else => |e| {
2649 current_thread.endSyscall();
3126 syscall.finish();
26503127 switch (e) {
26513128 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
26523129 .INVAL => |err| return errnoBug(err), // invalid parameters
......@@ -2661,40 +3138,42 @@ fn dirCreateFilePosix(
26613138 }
26623139
26633140 if (have_flock_open_flags and flags.lock_nonblocking) {
2664 try current_thread.beginSyscall();
2665 var fl_flags: usize = while (true) {
2666 const rc = posix.system.fcntl(fd, posix.F.GETFL, @as(usize, 0));
2667 switch (posix.errno(rc)) {
2668 .SUCCESS => {
2669 current_thread.endSyscall();
2670 break @intCast(rc);
2671 },
2672 .INTR => {
2673 try current_thread.checkCancel();
2674 continue;
2675 },
2676 else => |err| {
2677 current_thread.endSyscall();
2678 return posix.unexpectedErrno(err);
2679 },
3141 var fl_flags: usize = fl: {
3142 const syscall: Syscall = try .start();
3143 while (true) {
3144 const rc = posix.system.fcntl(fd, posix.F.GETFL, @as(usize, 0));
3145 switch (posix.errno(rc)) {
3146 .SUCCESS => {
3147 syscall.finish();
3148 break :fl @intCast(rc);
3149 },
3150 .INTR => {
3151 try syscall.checkCancel();
3152 continue;
3153 },
3154 else => |err| {
3155 syscall.finish();
3156 return posix.unexpectedErrno(err);
3157 },
3158 }
26803159 }
26813160 };
26823161
26833162 fl_flags |= @as(usize, 1 << @bitOffsetOf(posix.O, "NONBLOCK"));
26843163
2685 try current_thread.beginSyscall();
3164 const syscall: Syscall = try .start();
26863165 while (true) {
26873166 switch (posix.errno(posix.system.fcntl(fd, posix.F.SETFL, fl_flags))) {
26883167 .SUCCESS => {
2689 current_thread.endSyscall();
3168 syscall.finish();
26903169 break;
26913170 },
26923171 .INTR => {
2693 try current_thread.checkCancel();
3172 try syscall.checkCancel();
26943173 continue;
26953174 },
26963175 else => |err| {
2697 current_thread.endSyscall();
3176 syscall.finish();
26983177 return posix.unexpectedErrno(err);
26993178 },
27003179 }
......@@ -2712,28 +3191,41 @@ fn dirCreateFileWindows(
27123191) File.OpenError!File {
27133192 const w = windows;
27143193 const t: *Threaded = @ptrCast(@alignCast(userdata));
2715 const current_thread = Thread.getCurrent(t);
2716 try current_thread.checkCancel();
3194 _ = t;
27173195
27183196 const sub_path_w_array = try w.sliceToPrefixedFileW(dir.handle, sub_path);
27193197 const sub_path_w = sub_path_w_array.span();
27203198
2721 const handle = try w.OpenFile(sub_path_w, .{
2722 .dir = dir.handle,
2723 .access_mask = .{
2724 .STANDARD = .{ .SYNCHRONIZE = true },
2725 .GENERIC = .{
2726 .WRITE = true,
2727 .READ = flags.read,
2728 },
2729 },
2730 .creation = if (flags.exclusive)
2731 .CREATE
2732 else if (flags.truncate)
2733 .OVERWRITE_IF
2734 else
2735 .OPEN_IF,
2736 });
3199 const handle = handle: {
3200 const syscall: Syscall = try .start();
3201 while (true) {
3202 if (w.OpenFile(sub_path_w, .{
3203 .dir = dir.handle,
3204 .access_mask = .{
3205 .STANDARD = .{ .SYNCHRONIZE = true },
3206 .GENERIC = .{
3207 .WRITE = true,
3208 .READ = flags.read,
3209 },
3210 },
3211 .creation = if (flags.exclusive)
3212 .CREATE
3213 else if (flags.truncate)
3214 .OVERWRITE_IF
3215 else
3216 .OPEN_IF,
3217 })) |handle| {
3218 syscall.finish();
3219 break :handle handle;
3220 } else |err| switch (err) {
3221 error.OperationCanceled => {
3222 try syscall.checkCancel();
3223 continue;
3224 },
3225 else => |e| return syscall.fail(e),
3226 }
3227 }
3228 };
27373229 errdefer w.CloseHandle(handle);
27383230
27393231 var io_status_block: w.IO_STATUS_BLOCK = undefined;
......@@ -2742,7 +3234,8 @@ fn dirCreateFileWindows(
27423234 .shared => false,
27433235 .exclusive => true,
27443236 };
2745 const status = w.ntdll.NtLockFile(
3237 const syscall: Syscall = try .start();
3238 while (true) switch (w.ntdll.NtLockFile(
27463239 handle,
27473240 null,
27483241 null,
......@@ -2753,16 +3246,16 @@ fn dirCreateFileWindows(
27533246 null,
27543247 @intFromBool(flags.lock_nonblocking),
27553248 @intFromBool(exclusive),
2756 );
2757 switch (status) {
2758 .SUCCESS => {},
2759 .INSUFFICIENT_RESOURCES => return error.SystemResources,
2760 .LOCK_NOT_GRANTED => return error.WouldBlock,
2761 .ACCESS_VIOLATION => |err| return windows.statusBug(err), // bad io_status_block pointer
2762 else => return windows.unexpectedStatus(status),
2763 }
2764
2765 return .{ .handle = handle };
3249 )) {
3250 .SUCCESS => {
3251 syscall.finish();
3252 return .{ .handle = handle };
3253 },
3254 .INSUFFICIENT_RESOURCES => return syscall.fail(error.SystemResources),
3255 .LOCK_NOT_GRANTED => return syscall.fail(error.WouldBlock),
3256 .ACCESS_VIOLATION => |err| return syscall.ntstatusBug(err), // bad io_status_block pointer
3257 else => |status| return syscall.unexpectedNtstatus(status),
3258 };
27663259}
27673260
27683261fn dirCreateFileWasi(
......@@ -2772,7 +3265,7 @@ fn dirCreateFileWasi(
27723265 flags: File.CreateFlags,
27733266) File.OpenError!File {
27743267 const t: *Threaded = @ptrCast(@alignCast(userdata));
2775 const current_thread = Thread.getCurrent(t);
3268 _ = t;
27763269 const wasi = std.os.wasi;
27773270 const lookup_flags: wasi.lookupflags_t = .{};
27783271 const oflags: wasi.oflags_t = .{
......@@ -2800,19 +3293,19 @@ fn dirCreateFileWasi(
28003293 };
28013294 const inheriting: wasi.rights_t = .{};
28023295 var fd: posix.fd_t = undefined;
2803 try current_thread.beginSyscall();
3296 const syscall: Syscall = try .start();
28043297 while (true) {
28053298 switch (wasi.path_open(dir.handle, lookup_flags, sub_path.ptr, sub_path.len, oflags, base, inheriting, fdflags, &fd)) {
28063299 .SUCCESS => {
2807 current_thread.endSyscall();
3300 syscall.finish();
28083301 return .{ .handle = fd };
28093302 },
28103303 .INTR => {
2811 try current_thread.checkCancel();
3304 try syscall.checkCancel();
28123305 continue;
28133306 },
28143307 else => |e| {
2815 current_thread.endSyscall();
3308 syscall.finish();
28163309 switch (e) {
28173310 .FAULT => |err| return errnoBug(err),
28183311 .INVAL => return error.BadPathName,
......@@ -2855,7 +3348,6 @@ fn dirOpenFilePosix(
28553348 flags: File.OpenFlags,
28563349) File.OpenError!File {
28573350 const t: *Threaded = @ptrCast(@alignCast(userdata));
2858 const current_thread = Thread.getCurrent(t);
28593351
28603352 var path_buffer: [posix.PATH_MAX]u8 = undefined;
28613353 const sub_path_posix = try pathToPosix(sub_path, &path_buffer);
......@@ -2895,49 +3387,51 @@ fn dirOpenFilePosix(
28953387 },
28963388 };
28973389
2898 try current_thread.beginSyscall();
2899 const fd: posix.fd_t = while (true) {
2900 const rc = openat_sym(dir.handle, sub_path_posix, os_flags, @as(posix.mode_t, 0));
2901 switch (posix.errno(rc)) {
2902 .SUCCESS => {
2903 current_thread.endSyscall();
2904 break @intCast(rc);
2905 },
2906 .INTR => {
2907 try current_thread.checkCancel();
2908 continue;
2909 },
2910 else => |e| {
2911 current_thread.endSyscall();
2912 switch (e) {
2913 .FAULT => |err| return errnoBug(err),
2914 .INVAL => return error.BadPathName,
2915 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
2916 .ACCES => return error.AccessDenied,
2917 .FBIG => return error.FileTooBig,
2918 .OVERFLOW => return error.FileTooBig,
2919 .ISDIR => return error.IsDir,
2920 .LOOP => return error.SymLinkLoop,
2921 .MFILE => return error.ProcessFdQuotaExceeded,
2922 .NAMETOOLONG => return error.NameTooLong,
2923 .NFILE => return error.SystemFdQuotaExceeded,
2924 .NODEV => return error.NoDevice,
2925 .NOENT => return error.FileNotFound,
2926 .SRCH => return error.FileNotFound, // Linux when opening procfs files.
2927 .NOMEM => return error.SystemResources,
2928 .NOSPC => return error.NoSpaceLeft,
2929 .NOTDIR => return error.NotDir,
2930 .PERM => return error.PermissionDenied,
2931 .EXIST => return error.PathAlreadyExists,
2932 .BUSY => return error.DeviceBusy,
2933 .OPNOTSUPP => return error.FileLocksUnsupported,
2934 .AGAIN => return error.WouldBlock,
2935 .TXTBSY => return error.FileBusy,
2936 .NXIO => return error.NoDevice,
2937 .ILSEQ => return error.BadPathName,
2938 else => |err| return posix.unexpectedErrno(err),
2939 }
2940 },
3390 const fd: posix.fd_t = fd: {
3391 const syscall: Syscall = try .start();
3392 while (true) {
3393 const rc = openat_sym(dir.handle, sub_path_posix, os_flags, @as(posix.mode_t, 0));
3394 switch (posix.errno(rc)) {
3395 .SUCCESS => {
3396 syscall.finish();
3397 break :fd @intCast(rc);
3398 },
3399 .INTR => {
3400 try syscall.checkCancel();
3401 continue;
3402 },
3403 else => |e| {
3404 syscall.finish();
3405 switch (e) {
3406 .FAULT => |err| return errnoBug(err),
3407 .INVAL => return error.BadPathName,
3408 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
3409 .ACCES => return error.AccessDenied,
3410 .FBIG => return error.FileTooBig,
3411 .OVERFLOW => return error.FileTooBig,
3412 .ISDIR => return error.IsDir,
3413 .LOOP => return error.SymLinkLoop,
3414 .MFILE => return error.ProcessFdQuotaExceeded,
3415 .NAMETOOLONG => return error.NameTooLong,
3416 .NFILE => return error.SystemFdQuotaExceeded,
3417 .NODEV => return error.NoDevice,
3418 .NOENT => return error.FileNotFound,
3419 .SRCH => return error.FileNotFound, // Linux when opening procfs files.
3420 .NOMEM => return error.SystemResources,
3421 .NOSPC => return error.NoSpaceLeft,
3422 .NOTDIR => return error.NotDir,
3423 .PERM => return error.PermissionDenied,
3424 .EXIST => return error.PathAlreadyExists,
3425 .BUSY => return error.DeviceBusy,
3426 .OPNOTSUPP => return error.FileLocksUnsupported,
3427 .AGAIN => return error.WouldBlock,
3428 .TXTBSY => return error.FileBusy,
3429 .NXIO => return error.NoDevice,
3430 .ILSEQ => return error.BadPathName,
3431 else => |err| return posix.unexpectedErrno(err),
3432 }
3433 },
3434 }
29413435 }
29423436 };
29433437 errdefer posix.close(fd);
......@@ -2961,19 +3455,19 @@ fn dirOpenFilePosix(
29613455 .shared => posix.LOCK.SH | lock_nonblocking,
29623456 .exclusive => posix.LOCK.EX | lock_nonblocking,
29633457 };
2964 try current_thread.beginSyscall();
3458 const syscall: Syscall = try .start();
29653459 while (true) {
29663460 switch (posix.errno(posix.system.flock(fd, lock_flags))) {
29673461 .SUCCESS => {
2968 current_thread.endSyscall();
3462 syscall.finish();
29693463 break;
29703464 },
29713465 .INTR => {
2972 try current_thread.checkCancel();
3466 try syscall.checkCancel();
29733467 continue;
29743468 },
29753469 else => |e| {
2976 current_thread.endSyscall();
3470 syscall.finish();
29773471 switch (e) {
29783472 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
29793473 .INVAL => |err| return errnoBug(err), // invalid parameters
......@@ -2988,40 +3482,42 @@ fn dirOpenFilePosix(
29883482 }
29893483
29903484 if (have_flock_open_flags and flags.lock_nonblocking) {
2991 try current_thread.beginSyscall();
2992 var fl_flags: usize = while (true) {
2993 const rc = posix.system.fcntl(fd, posix.F.GETFL, @as(usize, 0));
2994 switch (posix.errno(rc)) {
2995 .SUCCESS => {
2996 current_thread.endSyscall();
2997 break @intCast(rc);
2998 },
2999 .INTR => {
3000 try current_thread.checkCancel();
3001 continue;
3002 },
3003 else => |err| {
3004 current_thread.endSyscall();
3005 return posix.unexpectedErrno(err);
3006 },
3485 var fl_flags: usize = fl: {
3486 const syscall: Syscall = try .start();
3487 while (true) {
3488 const rc = posix.system.fcntl(fd, posix.F.GETFL, @as(usize, 0));
3489 switch (posix.errno(rc)) {
3490 .SUCCESS => {
3491 syscall.finish();
3492 break :fl @intCast(rc);
3493 },
3494 .INTR => {
3495 try syscall.checkCancel();
3496 continue;
3497 },
3498 else => |err| {
3499 syscall.finish();
3500 return posix.unexpectedErrno(err);
3501 },
3502 }
30073503 }
30083504 };
30093505
30103506 fl_flags |= @as(usize, 1 << @bitOffsetOf(posix.O, "NONBLOCK"));
30113507
3012 try current_thread.beginSyscall();
3508 const syscall: Syscall = try .start();
30133509 while (true) {
30143510 switch (posix.errno(posix.system.fcntl(fd, posix.F.SETFL, fl_flags))) {
30153511 .SUCCESS => {
3016 current_thread.endSyscall();
3512 syscall.finish();
30173513 break;
30183514 },
30193515 .INTR => {
3020 try current_thread.checkCancel();
3516 try syscall.checkCancel();
30213517 continue;
30223518 },
30233519 else => |err| {
3024 current_thread.endSyscall();
3520 syscall.finish();
30253521 return posix.unexpectedErrno(err);
30263522 },
30273523 }
......@@ -3038,14 +3534,14 @@ fn dirOpenFileWindows(
30383534 flags: File.OpenFlags,
30393535) File.OpenError!File {
30403536 const t: *Threaded = @ptrCast(@alignCast(userdata));
3537 _ = t;
30413538 const sub_path_w_array = try windows.sliceToPrefixedFileW(dir.handle, sub_path);
30423539 const sub_path_w = sub_path_w_array.span();
30433540 const dir_handle = if (std.fs.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle;
3044 return dirOpenFileWtf16(t, dir_handle, sub_path_w, flags);
3541 return dirOpenFileWtf16(dir_handle, sub_path_w, flags);
30453542}
30463543
30473544pub fn dirOpenFileWtf16(
3048 t: *Threaded,
30493545 dir_handle: ?windows.HANDLE,
30503546 sub_path_w: [:0]const u16,
30513547 flags: File.OpenFlags,
......@@ -3054,7 +3550,6 @@ pub fn dirOpenFileWtf16(
30543550 if (!allow_directory and std.mem.eql(u16, sub_path_w, &.{'.'})) return error.IsDir;
30553551 if (!allow_directory and std.mem.eql(u16, sub_path_w, &.{ '.', '.' })) return error.IsDir;
30563552 const path_len_bytes = std.math.cast(u16, sub_path_w.len * 2) orelse return error.NameTooLong;
3057 const current_thread = Thread.getCurrent(t);
30583553 const w = windows;
30593554
30603555 var nt_name: w.UNICODE_STRING = .{
......@@ -3076,11 +3571,10 @@ pub fn dirOpenFileWtf16(
30763571 const max_attempts = 13;
30773572 var attempt: u5 = 0;
30783573
3574 var syscall: Syscall = try .start();
30793575 const handle = while (true) {
3080 try current_thread.checkCancel();
3081
30823576 var result: w.HANDLE = undefined;
3083 const rc = w.ntdll.NtCreateFile(
3577 switch (w.ntdll.NtCreateFile(
30843578 &result,
30853579 .{
30863580 .STANDARD = .{ .SYNCHRONIZE = true },
......@@ -3102,49 +3596,59 @@ pub fn dirOpenFileWtf16(
31023596 },
31033597 null,
31043598 0,
3105 );
3106 switch (rc) {
3107 .SUCCESS => break result,
3108 .OBJECT_NAME_INVALID => return error.BadPathName,
3109 .OBJECT_NAME_NOT_FOUND => return error.FileNotFound,
3110 .OBJECT_PATH_NOT_FOUND => return error.FileNotFound,
3111 .BAD_NETWORK_PATH => return error.NetworkNotFound, // \\server was not found
3112 .BAD_NETWORK_NAME => return error.NetworkNotFound, // \\server was found but \\server\share wasn't
3113 .NO_MEDIA_IN_DEVICE => return error.NoDevice,
3114 .INVALID_PARAMETER => |err| return w.statusBug(err),
3599 )) {
3600 .SUCCESS => {
3601 syscall.finish();
3602 break result;
3603 },
3604 .OBJECT_NAME_INVALID => return syscall.fail(error.BadPathName),
3605 .OBJECT_NAME_NOT_FOUND => return syscall.fail(error.FileNotFound),
3606 .OBJECT_PATH_NOT_FOUND => return syscall.fail(error.FileNotFound),
3607 .BAD_NETWORK_PATH => return syscall.fail(error.NetworkNotFound), // \\server was not found
3608 .BAD_NETWORK_NAME => return syscall.fail(error.NetworkNotFound), // \\server was found but \\server\share wasn't
3609 .NO_MEDIA_IN_DEVICE => return syscall.fail(error.NoDevice),
3610 .INVALID_PARAMETER => |err| return syscall.ntstatusBug(err),
3611 .CANCELLED => {
3612 try syscall.checkCancel();
3613 continue;
3614 },
31153615 .SHARING_VIOLATION => {
31163616 // This occurs if the file attempting to be opened is a running
31173617 // executable. However, there's a kernel bug: the error may be
31183618 // incorrectly returned for an indeterminate amount of time
31193619 // after an executable file is closed. Here we work around the
31203620 // kernel bug with retry attempts.
3621 syscall.finish();
31213622 if (max_attempts - attempt == 0) return error.SharingViolation;
31223623 _ = w.kernel32.SleepEx((@as(u32, 1) << attempt) >> 1, w.TRUE);
31233624 attempt += 1;
3625 syscall = try .start();
31243626 continue;
31253627 },
3126 .ACCESS_DENIED => return error.AccessDenied,
3127 .PIPE_BUSY => return error.PipeBusy,
3128 .PIPE_NOT_AVAILABLE => return error.NoDevice,
3129 .OBJECT_PATH_SYNTAX_BAD => |err| return w.statusBug(err),
3130 .OBJECT_NAME_COLLISION => return error.PathAlreadyExists,
3131 .FILE_IS_A_DIRECTORY => return error.IsDir,
3132 .NOT_A_DIRECTORY => return error.NotDir,
3133 .USER_MAPPED_FILE => return error.AccessDenied,
3134 .INVALID_HANDLE => |err| return w.statusBug(err),
3628 .ACCESS_DENIED => return syscall.fail(error.AccessDenied),
3629 .PIPE_BUSY => return syscall.fail(error.PipeBusy),
3630 .PIPE_NOT_AVAILABLE => return syscall.fail(error.NoDevice),
3631 .OBJECT_PATH_SYNTAX_BAD => |err| return syscall.ntstatusBug(err),
3632 .OBJECT_NAME_COLLISION => return syscall.fail(error.PathAlreadyExists),
3633 .FILE_IS_A_DIRECTORY => return syscall.fail(error.IsDir),
3634 .NOT_A_DIRECTORY => return syscall.fail(error.NotDir),
3635 .USER_MAPPED_FILE => return syscall.fail(error.AccessDenied),
3636 .INVALID_HANDLE => |err| return syscall.ntstatusBug(err),
31353637 .DELETE_PENDING => {
31363638 // This error means that there *was* a file in this location on
31373639 // the file system, but it was deleted. However, the OS is not
31383640 // finished with the deletion operation, and so this CreateFile
31393641 // call has failed. Here, we simulate the kernel bug being
31403642 // fixed by sleeping and retrying until the error goes away.
3643 syscall.finish();
31413644 if (max_attempts - attempt == 0) return error.SharingViolation;
31423645 _ = w.kernel32.SleepEx((@as(u32, 1) << attempt) >> 1, w.TRUE);
31433646 attempt += 1;
3647 syscall = try .start();
31443648 continue;
31453649 },
3146 .VIRUS_INFECTED, .VIRUS_DELETED => return error.AntivirusInterference,
3147 else => return w.unexpectedStatus(rc),
3650 .VIRUS_INFECTED, .VIRUS_DELETED => return syscall.fail(error.AntivirusInterference),
3651 else => |rc| return syscall.unexpectedNtstatus(rc),
31483652 }
31493653 };
31503654 errdefer w.CloseHandle(handle);
......@@ -3154,7 +3658,8 @@ pub fn dirOpenFileWtf16(
31543658 .shared => false,
31553659 .exclusive => true,
31563660 };
3157 const status = w.ntdll.NtLockFile(
3661 syscall = try .start();
3662 while (true) switch (w.ntdll.NtLockFile(
31583663 handle,
31593664 null,
31603665 null,
......@@ -3165,14 +3670,13 @@ pub fn dirOpenFileWtf16(
31653670 null,
31663671 @intFromBool(flags.lock_nonblocking),
31673672 @intFromBool(exclusive),
3168 );
3169 switch (status) {
3170 .SUCCESS => {},
3171 .INSUFFICIENT_RESOURCES => return error.SystemResources,
3172 .LOCK_NOT_GRANTED => return error.WouldBlock,
3173 .ACCESS_VIOLATION => |err| return windows.statusBug(err), // bad io_status_block pointer
3174 else => return windows.unexpectedStatus(status),
3175 }
3673 )) {
3674 .SUCCESS => break syscall.finish(),
3675 .INSUFFICIENT_RESOURCES => return syscall.fail(error.SystemResources),
3676 .LOCK_NOT_GRANTED => return syscall.fail(error.WouldBlock),
3677 .ACCESS_VIOLATION => |err| return syscall.ntstatusBug(err), // bad io_status_block pointer
3678 else => |status| return syscall.unexpectedNtstatus(status),
3679 };
31763680 return .{ .handle = handle };
31773681}
31783682
......@@ -3184,7 +3688,6 @@ fn dirOpenFileWasi(
31843688) File.OpenError!File {
31853689 if (builtin.link_libc) return dirOpenFilePosix(userdata, dir, sub_path, flags);
31863690 const t: *Threaded = @ptrCast(@alignCast(userdata));
3187 const current_thread = Thread.getCurrent(t);
31883691 const wasi = std.os.wasi;
31893692 var base: std.os.wasi.rights_t = .{};
31903693 // POLL_FD_READWRITE only grants extra rights if the corresponding FD_READ and/or FD_WRITE
......@@ -3214,19 +3717,19 @@ fn dirOpenFileWasi(
32143717 const inheriting: wasi.rights_t = .{};
32153718 const fdflags: wasi.fdflags_t = .{};
32163719 var fd: posix.fd_t = undefined;
3217 try current_thread.beginSyscall();
3720 const syscall: Syscall = try .start();
32183721 while (true) {
32193722 switch (wasi.path_open(dir.handle, lookup_flags, sub_path.ptr, sub_path.len, oflags, base, inheriting, fdflags, &fd)) {
32203723 .SUCCESS => {
3221 current_thread.endSyscall();
3724 syscall.finish();
32223725 break;
32233726 },
32243727 .INTR => {
3225 try current_thread.checkCancel();
3728 try syscall.checkCancel();
32263729 continue;
32273730 },
32283731 else => |e| {
3229 current_thread.endSyscall();
3732 syscall.finish();
32303733 switch (e) {
32313734 .FAULT => |err| return errnoBug(err),
32323735 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
......@@ -3283,14 +3786,13 @@ fn dirOpenDirPosix(
32833786 options: Dir.OpenOptions,
32843787) Dir.OpenError!Dir {
32853788 const t: *Threaded = @ptrCast(@alignCast(userdata));
3789 _ = t;
32863790
32873791 if (is_windows) {
32883792 const sub_path_w = try windows.sliceToPrefixedFileW(dir.handle, sub_path);
3289 return dirOpenDirWindows(t, dir, sub_path_w.span(), options);
3793 return dirOpenDirWindows(dir, sub_path_w.span(), options);
32903794 }
32913795
3292 const current_thread = Thread.getCurrent(t);
3293
32943796 var path_buffer: [posix.PATH_MAX]u8 = undefined;
32953797 const sub_path_posix = try pathToPosix(sub_path, &path_buffer);
32963798
......@@ -3311,20 +3813,20 @@ fn dirOpenDirPosix(
33113813 if (@hasField(posix.O, "PATH") and !options.iterate)
33123814 flags.PATH = true;
33133815
3314 try current_thread.beginSyscall();
3816 const syscall: Syscall = try .start();
33153817 while (true) {
33163818 const rc = openat_sym(dir.handle, sub_path_posix, flags, @as(usize, 0));
33173819 switch (posix.errno(rc)) {
33183820 .SUCCESS => {
3319 current_thread.endSyscall();
3821 syscall.finish();
33203822 return .{ .handle = @intCast(rc) };
33213823 },
33223824 .INTR => {
3323 try current_thread.checkCancel();
3825 try syscall.checkCancel();
33243826 continue;
33253827 },
33263828 else => |e| {
3327 current_thread.endSyscall();
3829 syscall.finish();
33283830 switch (e) {
33293831 .FAULT => |err| return errnoBug(err),
33303832 .INVAL => return error.BadPathName,
......@@ -3356,27 +3858,27 @@ fn dirOpenDirHaiku(
33563858 options: Dir.OpenOptions,
33573859) Dir.OpenError!Dir {
33583860 const t: *Threaded = @ptrCast(@alignCast(userdata));
3359 const current_thread = Thread.getCurrent(t);
3861 _ = t;
33603862
33613863 var path_buffer: [posix.PATH_MAX]u8 = undefined;
33623864 const sub_path_posix = try pathToPosix(sub_path, &path_buffer);
33633865
33643866 _ = options;
33653867
3366 try current_thread.beginSyscall();
3868 const syscall: Syscall = try .start();
33673869 while (true) {
33683870 const rc = posix.system._kern_open_dir(dir.handle, sub_path_posix);
33693871 if (rc >= 0) {
3370 current_thread.endSyscall();
3872 syscall.finish();
33713873 return .{ .handle = rc };
33723874 }
33733875 switch (@as(posix.E, @enumFromInt(rc))) {
33743876 .INTR => {
3375 try current_thread.checkCancel();
3877 try syscall.checkCancel();
33763878 continue;
33773879 },
33783880 else => |e| {
3379 current_thread.endSyscall();
3881 syscall.finish();
33803882 switch (e) {
33813883 .FAULT => |err| return errnoBug(err),
33823884 .INVAL => |err| return errnoBug(err),
......@@ -3400,12 +3902,10 @@ fn dirOpenDirHaiku(
34003902}
34013903
34023904pub fn dirOpenDirWindows(
3403 t: *Io.Threaded,
34043905 dir: Dir,
34053906 sub_path_w: [:0]const u16,
34063907 options: Dir.OpenOptions,
34073908) Dir.OpenError!Dir {
3408 const current_thread = Thread.getCurrent(t);
34093909 const w = windows;
34103910
34113911 const path_len_bytes: u16 = @intCast(sub_path_w.len * 2);
......@@ -3416,8 +3916,9 @@ pub fn dirOpenDirWindows(
34163916 };
34173917 var io_status_block: w.IO_STATUS_BLOCK = undefined;
34183918 var result: Dir = .{ .handle = undefined };
3419 try current_thread.checkCancel();
3420 const rc = w.ntdll.NtCreateFile(
3919
3920 const syscall: Syscall = try .start();
3921 while (true) switch (w.ntdll.NtCreateFile(
34213922 &result.handle,
34223923 // TODO remove some of these flags if options.access_sub_paths is false
34233924 .{
......@@ -3453,21 +3954,26 @@ pub fn dirOpenDirWindows(
34533954 },
34543955 null,
34553956 0,
3456 );
3457
3458 switch (rc) {
3459 .SUCCESS => return result,
3460 .OBJECT_NAME_INVALID => return error.BadPathName,
3461 .OBJECT_NAME_NOT_FOUND => return error.FileNotFound,
3957 )) {
3958 .SUCCESS => {
3959 syscall.finish();
3960 return result;
3961 },
3962 .CANCELLED => {
3963 try syscall.checkCancel();
3964 continue;
3965 },
3966 .OBJECT_NAME_INVALID => return syscall.fail(error.BadPathName),
3967 .OBJECT_NAME_NOT_FOUND => return syscall.fail(error.FileNotFound),
34623968 .OBJECT_NAME_COLLISION => |err| return w.statusBug(err),
3463 .OBJECT_PATH_NOT_FOUND => return error.FileNotFound,
3464 .NOT_A_DIRECTORY => return error.NotDir,
3969 .OBJECT_PATH_NOT_FOUND => return syscall.fail(error.FileNotFound),
3970 .NOT_A_DIRECTORY => return syscall.fail(error.NotDir),
34653971 // This can happen if the directory has 'List folder contents' permission set to 'Deny'
34663972 // and the directory is trying to be opened for iteration.
3467 .ACCESS_DENIED => return error.AccessDenied,
3468 .INVALID_PARAMETER => |err| return w.statusBug(err),
3469 else => return w.unexpectedStatus(rc),
3470 }
3973 .ACCESS_DENIED => return syscall.fail(error.AccessDenied),
3974 .INVALID_PARAMETER => |err| return syscall.ntstatusBug(err),
3975 else => |rc| return syscall.unexpectedNtstatus(rc),
3976 };
34713977}
34723978
34733979fn dirClose(userdata: ?*anyopaque, dirs: []const Dir) void {
......@@ -3490,7 +3996,7 @@ const dirRead = switch (native_os) {
34903996fn dirReadLinux(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) Dir.Reader.Error!usize {
34913997 const linux = std.os.linux;
34923998 const t: *Threaded = @ptrCast(@alignCast(userdata));
3493 const current_thread = Thread.getCurrent(t);
3999 _ = t;
34944000 var buffer_index: usize = 0;
34954001 while (buffer.len - buffer_index != 0) {
34964002 if (dr.end - dr.index == 0) {
......@@ -3498,26 +4004,26 @@ fn dirReadLinux(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) Dir
34984004 // buffered data.
34994005 if (buffer_index != 0) break;
35004006 if (dr.state == .reset) {
3501 posixSeekTo(current_thread, dr.dir.handle, 0) catch |err| switch (err) {
4007 posixSeekTo(dr.dir.handle, 0) catch |err| switch (err) {
35024008 error.Unseekable => return error.Unexpected,
35034009 else => |e| return e,
35044010 };
35054011 dr.state = .reading;
35064012 }
3507 try current_thread.beginSyscall();
4013 const syscall: Syscall = try .start();
35084014 const n = while (true) {
35094015 const rc = linux.getdents64(dr.dir.handle, dr.buffer.ptr, dr.buffer.len);
35104016 switch (linux.errno(rc)) {
35114017 .SUCCESS => {
3512 current_thread.endSyscall();
4018 syscall.finish();
35134019 break rc;
35144020 },
35154021 .INTR => {
3516 try current_thread.checkCancel();
4022 try syscall.checkCancel();
35174023 continue;
35184024 },
35194025 else => |e| {
3520 current_thread.endSyscall();
4026 syscall.finish();
35214027 switch (e) {
35224028 .BADF => |err| return errnoBug(err), // Dir is invalid or was opened without iteration ability.
35234029 .FAULT => |err| return errnoBug(err),
......@@ -3587,7 +4093,7 @@ fn dirReadLinux(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) Dir
35874093
35884094fn dirReadDarwin(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) Dir.Reader.Error!usize {
35894095 const t: *Threaded = @ptrCast(@alignCast(userdata));
3590 const current_thread = Thread.getCurrent(t);
4096 _ = t;
35914097 const Header = extern struct {
35924098 seek: i64,
35934099 };
......@@ -3606,27 +4112,27 @@ fn dirReadDarwin(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) Di
36064112 // buffered data.
36074113 if (buffer_index != 0) break;
36084114 if (dr.state == .reset) {
3609 posixSeekTo(current_thread, dr.dir.handle, 0) catch |err| switch (err) {
4115 posixSeekTo(dr.dir.handle, 0) catch |err| switch (err) {
36104116 error.Unseekable => return error.Unexpected,
36114117 else => |e| return e,
36124118 };
36134119 dr.state = .reading;
36144120 }
36154121 const dents_buffer = dr.buffer[header_end..];
3616 try current_thread.beginSyscall();
4122 const syscall: Syscall = try .start();
36174123 const n: usize = while (true) {
36184124 const rc = posix.system.getdirentries(dr.dir.handle, dents_buffer.ptr, dents_buffer.len, &header.seek);
36194125 switch (posix.errno(rc)) {
36204126 .SUCCESS => {
3621 current_thread.endSyscall();
4127 syscall.finish();
36224128 break @intCast(rc);
36234129 },
36244130 .INTR => {
3625 try current_thread.checkCancel();
4131 try syscall.checkCancel();
36264132 continue;
36274133 },
36284134 else => |e| {
3629 current_thread.endSyscall();
4135 syscall.finish();
36304136 switch (e) {
36314137 .BADF => |err| return errnoBug(err), // Dir is invalid or was opened without iteration ability.
36324138 .FAULT => |err| return errnoBug(err),
......@@ -3675,7 +4181,7 @@ fn dirReadDarwin(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) Di
36754181
36764182fn dirReadBsd(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) Dir.Reader.Error!usize {
36774183 const t: *Threaded = @ptrCast(@alignCast(userdata));
3678 const current_thread = Thread.getCurrent(t);
4184 _ = t;
36794185 var buffer_index: usize = 0;
36804186 while (buffer.len - buffer_index != 0) {
36814187 if (dr.end - dr.index == 0) {
......@@ -3683,26 +4189,26 @@ fn dirReadBsd(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) Dir.R
36834189 // buffered data.
36844190 if (buffer_index != 0) break;
36854191 if (dr.state == .reset) {
3686 posixSeekTo(current_thread, dr.dir.handle, 0) catch |err| switch (err) {
4192 posixSeekTo(dr.dir.handle, 0) catch |err| switch (err) {
36874193 error.Unseekable => return error.Unexpected,
36884194 else => |e| return e,
36894195 };
36904196 dr.state = .reading;
36914197 }
3692 try current_thread.beginSyscall();
4198 const syscall: Syscall = try .start();
36934199 const n: usize = while (true) {
36944200 const rc = posix.system.getdents(dr.dir.handle, dr.buffer.ptr, dr.buffer.len);
36954201 switch (posix.errno(rc)) {
36964202 .SUCCESS => {
3697 current_thread.endSyscall();
4203 syscall.finish();
36984204 break @intCast(rc);
36994205 },
37004206 .INTR => {
3701 try current_thread.checkCancel();
4207 try syscall.checkCancel();
37024208 continue;
37034209 },
37044210 else => |e| {
3705 current_thread.endSyscall();
4211 syscall.finish();
37064212 switch (e) {
37074213 .BADF => |err| return errnoBug(err), // Dir is invalid or was opened without iteration ability
37084214 .FAULT => |err| return errnoBug(err),
......@@ -3769,7 +4275,7 @@ fn dirReadBsd(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) Dir.R
37694275
37704276fn dirReadIllumos(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) Dir.Reader.Error!usize {
37714277 const t: *Threaded = @ptrCast(@alignCast(userdata));
3772 const current_thread = Thread.getCurrent(t);
4278 _ = t;
37734279 var buffer_index: usize = 0;
37744280 while (buffer.len - buffer_index != 0) {
37754281 if (dr.end - dr.index == 0) {
......@@ -3777,26 +4283,26 @@ fn dirReadIllumos(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) D
37774283 // buffered data.
37784284 if (buffer_index != 0) break;
37794285 if (dr.state == .reset) {
3780 posixSeekTo(current_thread, dr.dir.handle, 0) catch |err| switch (err) {
4286 posixSeekTo(dr.dir.handle, 0) catch |err| switch (err) {
37814287 error.Unseekable => return error.Unexpected,
37824288 else => |e| return e,
37834289 };
37844290 dr.state = .reading;
37854291 }
3786 try current_thread.beginSyscall();
4292 const syscall: Syscall = try .start();
37874293 const n: usize = while (true) {
37884294 const rc = posix.system.getdents(dr.dir.handle, dr.buffer.ptr, dr.buffer.len);
37894295 switch (posix.errno(rc)) {
37904296 .SUCCESS => {
3791 current_thread.endSyscall();
4297 syscall.finish();
37924298 break rc;
37934299 },
37944300 .INTR => {
3795 try current_thread.checkCancel();
4301 try syscall.checkCancel();
37964302 continue;
37974303 },
37984304 else => |e| {
3799 current_thread.endSyscall();
4305 syscall.finish();
38004306 switch (e) {
38014307 .BADF => |err| return errnoBug(err), // Dir is invalid or was opened without iteration ability
38024308 .FAULT => |err| return errnoBug(err),
......@@ -3822,7 +4328,7 @@ fn dirReadIllumos(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) D
38224328 if (std.mem.eql(u8, name, ".") or std.mem.eql(u8, name, "..")) continue;
38234329
38244330 // illumos dirent doesn't expose type, so we have to call stat to get it.
3825 const stat = try posixStatFile(current_thread, dr.dir.handle, name, posix.AT.SYMLINK_NOFOLLOW);
4331 const stat = try posixStatFile(dr.dir.handle, name, posix.AT.SYMLINK_NOFOLLOW);
38264332
38274333 buffer[buffer_index] = .{
38284334 .name = name,
......@@ -3843,7 +4349,7 @@ fn dirReadHaiku(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) Dir
38434349
38444350fn dirReadWindows(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) Dir.Reader.Error!usize {
38454351 const t: *Threaded = @ptrCast(@alignCast(userdata));
3846 const current_thread = Thread.getCurrent(t);
4352 _ = t;
38474353 const w = windows;
38484354
38494355 // 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
39074413 // buffered data.
39084414 if (buffer_index != 0) break;
39094415
3910 try current_thread.checkCancel();
39114416 var io_status_block: w.IO_STATUS_BLOCK = undefined;
3912 const rc = w.ntdll.NtQueryDirectoryFile(
4417 const syscall: Syscall = try .start();
4418 const rc = while (true) switch (w.ntdll.NtQueryDirectoryFile(
39134419 dr.dir.handle,
39144420 null,
39154421 null,
......@@ -3921,7 +4427,16 @@ fn dirReadWindows(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) D
39214427 w.FALSE,
39224428 null,
39234429 @intFromBool(dr.state == .reset),
3924 );
4430 )) {
4431 .CANCELLED => {
4432 try syscall.checkCancel();
4433 continue;
4434 },
4435 else => |rc| {
4436 syscall.finish();
4437 break rc;
4438 },
4439 };
39254440 dr.state = .reading;
39264441 if (io_status_block.Information == 0) {
39274442 dr.state = .finished;
......@@ -3993,7 +4508,7 @@ fn dirReadWasi(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) Dir.
39934508 // complexity here.
39944509 const wasi = std.os.wasi;
39954510 const t: *Threaded = @ptrCast(@alignCast(userdata));
3996 const current_thread = Thread.getCurrent(t);
4511 _ = t;
39974512 const Header = extern struct {
39984513 cookie: u64,
39994514 };
......@@ -4019,19 +4534,19 @@ fn dirReadWasi(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) Dir.
40194534 }
40204535 const dents_buffer = dr.buffer[header_end..];
40214536 var n: usize = undefined;
4022 try current_thread.beginSyscall();
4537 const syscall: Syscall = try .start();
40234538 while (true) {
40244539 switch (wasi.fd_readdir(dr.dir.handle, dents_buffer.ptr, dents_buffer.len, header.cookie, &n)) {
40254540 .SUCCESS => {
4026 current_thread.endSyscall();
4541 syscall.finish();
40274542 break;
40284543 },
40294544 .INTR => {
4030 try current_thread.checkCancel();
4545 try syscall.checkCancel();
40314546 continue;
40324547 },
40334548 else => |e| {
4034 current_thread.endSyscall();
4549 syscall.finish();
40354550 switch (e) {
40364551 .BADF => |err| return errnoBug(err), // Dir is invalid or was opened without iteration ability.
40374552 .FAULT => |err| return errnoBug(err),
......@@ -4107,34 +4622,42 @@ const dirRealPathFile = switch (native_os) {
41074622
41084623fn dirRealPathFileWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, out_buffer: []u8) Dir.RealPathFileError!usize {
41094624 const t: *Threaded = @ptrCast(@alignCast(userdata));
4110 const current_thread = Thread.getCurrent(t);
4111
4112 try current_thread.checkCancel();
4625 _ = t;
41134626
41144627 var path_name_w = try windows.sliceToPrefixedFileW(dir.handle, sub_path);
41154628
4116 const h_file = blk: {
4117 const res = windows.OpenFile(path_name_w.span(), .{
4118 .dir = dir.handle,
4119 .access_mask = .{
4120 .GENERIC = .{ .READ = true },
4121 .STANDARD = .{ .SYNCHRONIZE = true },
4122 },
4123 .creation = .OPEN,
4124 .filter = .any,
4125 }) catch |err| switch (err) {
4126 error.WouldBlock => unreachable,
4127 else => |e| return e,
4128 };
4129 break :blk res;
4629 const h_file = handle: {
4630 const syscall: Syscall = try .start();
4631 while (true) {
4632 if (windows.OpenFile(path_name_w.span(), .{
4633 .dir = dir.handle,
4634 .access_mask = .{
4635 .GENERIC = .{ .READ = true },
4636 .STANDARD = .{ .SYNCHRONIZE = true },
4637 },
4638 .creation = .OPEN,
4639 .filter = .any,
4640 })) |handle| {
4641 syscall.finish();
4642 break :handle handle;
4643 } else |err| switch (err) {
4644 error.WouldBlock => unreachable,
4645 error.OperationCanceled => {
4646 try syscall.checkCancel();
4647 continue;
4648 },
4649 else => |e| return syscall.fail(e),
4650 }
4651 }
41304652 };
41314653 defer windows.CloseHandle(h_file);
4132 return realPathWindows(current_thread, h_file, out_buffer);
4654 return realPathWindows(h_file, out_buffer);
41334655}
41344656
4135fn realPathWindows(current_thread: *Thread, h_file: windows.HANDLE, out_buffer: []u8) File.RealPathError!usize {
4136 _ = current_thread; // TODO move GetFinalPathNameByHandle logic into std.Io.Threaded and add cancel checks
4657fn realPathWindows(h_file: windows.HANDLE, out_buffer: []u8) File.RealPathError!usize {
41374658 var wide_buf: [windows.PATH_MAX_WIDE]u16 = undefined;
4659 // TODO move GetFinalPathNameByHandle logic into std.Io.Threaded and add cancel checks
4660 try Thread.checkCancel();
41384661 const wide_slice = try windows.GetFinalPathNameByHandle(h_file, .{}, &wide_buf);
41394662
41404663 const len = std.unicode.calcWtf8Len(wide_slice);
......@@ -4148,26 +4671,26 @@ fn dirRealPathFilePosix(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, o
41484671 if (native_os == .wasi) return error.OperationUnsupported;
41494672
41504673 const t: *Threaded = @ptrCast(@alignCast(userdata));
4151 const current_thread = Thread.getCurrent(t);
4674 _ = t;
41524675
41534676 var path_buffer: [posix.PATH_MAX]u8 = undefined;
41544677 const sub_path_posix = try pathToPosix(sub_path, &path_buffer);
41554678
41564679 if (builtin.link_libc and dir.handle == posix.AT.FDCWD) {
41574680 if (out_buffer.len < posix.PATH_MAX) return error.NameTooLong;
4158 try current_thread.beginSyscall();
4681 const syscall: Syscall = try .start();
41594682 while (true) {
41604683 if (std.c.realpath(sub_path_posix, out_buffer.ptr)) |redundant_pointer| {
4161 current_thread.endSyscall();
4684 syscall.finish();
41624685 assert(redundant_pointer == out_buffer.ptr);
41634686 return std.mem.indexOfScalar(u8, out_buffer, 0) orelse out_buffer.len;
41644687 }
41654688 const err: posix.E = @enumFromInt(std.c._errno().*);
41664689 if (err == .INTR) {
4167 try current_thread.checkCancel();
4690 try syscall.checkCancel();
41684691 continue;
41694692 }
4170 current_thread.endSyscall();
4693 syscall.finish();
41714694 switch (err) {
41724695 .INVAL => return errnoBug(err),
41734696 .BADF => return errnoBug(err),
......@@ -4191,20 +4714,20 @@ fn dirRealPathFilePosix(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, o
41914714
41924715 const mode: posix.mode_t = 0;
41934716
4194 try current_thread.beginSyscall();
4717 const syscall: Syscall = try .start();
41954718 const fd: posix.fd_t = while (true) {
41964719 const rc = openat_sym(dir.handle, sub_path_posix, flags, mode);
41974720 switch (posix.errno(rc)) {
41984721 .SUCCESS => {
4199 current_thread.endSyscall();
4722 syscall.finish();
42004723 break @intCast(rc);
42014724 },
42024725 .INTR => {
4203 try current_thread.checkCancel();
4726 try syscall.checkCancel();
42044727 continue;
42054728 },
42064729 else => |e| {
4207 current_thread.endSyscall();
4730 syscall.finish();
42084731 switch (e) {
42094732 .FAULT => |err| return errnoBug(err),
42104733 .INVAL => return error.BadPathName,
......@@ -4234,7 +4757,7 @@ fn dirRealPathFilePosix(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, o
42344757 }
42354758 };
42364759 defer posix.close(fd);
4237 return realPathPosix(current_thread, fd, out_buffer);
4760 return realPathPosix(fd, out_buffer);
42384761}
42394762
42404763const dirRealPath = switch (native_os) {
......@@ -4245,14 +4768,14 @@ const dirRealPath = switch (native_os) {
42454768fn dirRealPathPosix(userdata: ?*anyopaque, dir: Dir, out_buffer: []u8) Dir.RealPathError!usize {
42464769 if (native_os == .wasi) return error.OperationUnsupported;
42474770 const t: *Threaded = @ptrCast(@alignCast(userdata));
4248 const current_thread = Thread.getCurrent(t);
4249 return realPathPosix(current_thread, dir.handle, out_buffer);
4771 _ = t;
4772 return realPathPosix(dir.handle, out_buffer);
42504773}
42514774
42524775fn dirRealPathWindows(userdata: ?*anyopaque, dir: Dir, out_buffer: []u8) Dir.RealPathError!usize {
42534776 const t: *Threaded = @ptrCast(@alignCast(userdata));
4254 const current_thread = Thread.getCurrent(t);
4255 return realPathWindows(current_thread, dir.handle, out_buffer);
4777 _ = t;
4778 return realPathWindows(dir.handle, out_buffer);
42564779}
42574780
42584781const fileRealPath = switch (native_os) {
......@@ -4263,35 +4786,35 @@ const fileRealPath = switch (native_os) {
42634786fn fileRealPathWindows(userdata: ?*anyopaque, file: File, out_buffer: []u8) File.RealPathError!usize {
42644787 if (native_os == .wasi) return error.OperationUnsupported;
42654788 const t: *Threaded = @ptrCast(@alignCast(userdata));
4266 const current_thread = Thread.getCurrent(t);
4267 return realPathWindows(current_thread, file.handle, out_buffer);
4789 _ = t;
4790 return realPathWindows(file.handle, out_buffer);
42684791}
42694792
42704793fn fileRealPathPosix(userdata: ?*anyopaque, file: File, out_buffer: []u8) File.RealPathError!usize {
42714794 if (native_os == .wasi) return error.OperationUnsupported;
42724795 const t: *Threaded = @ptrCast(@alignCast(userdata));
4273 const current_thread = Thread.getCurrent(t);
4274 return realPathPosix(current_thread, file.handle, out_buffer);
4796 _ = t;
4797 return realPathPosix(file.handle, out_buffer);
42754798}
42764799
4277fn realPathPosix(current_thread: *Thread, fd: posix.fd_t, out_buffer: []u8) File.RealPathError!usize {
4800fn realPathPosix(fd: posix.fd_t, out_buffer: []u8) File.RealPathError!usize {
42784801 switch (native_os) {
42794802 .netbsd, .dragonfly, .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => {
42804803 var sufficient_buffer: [posix.PATH_MAX]u8 = undefined;
42814804 @memset(&sufficient_buffer, 0);
4282 try current_thread.beginSyscall();
4805 const syscall: Syscall = try .start();
42834806 while (true) {
42844807 switch (posix.errno(posix.system.fcntl(fd, posix.F.GETPATH, &sufficient_buffer))) {
42854808 .SUCCESS => {
4286 current_thread.endSyscall();
4809 syscall.finish();
42874810 break;
42884811 },
42894812 .INTR => {
4290 try current_thread.checkCancel();
4813 try syscall.checkCancel();
42914814 continue;
42924815 },
42934816 else => |e| {
4294 current_thread.endSyscall();
4817 syscall.finish();
42954818 switch (e) {
42964819 .ACCES => return error.AccessDenied,
42974820 .BADF => return error.FileNotFound,
......@@ -4313,21 +4836,21 @@ fn realPathPosix(current_thread: *Thread, fd: posix.fd_t, out_buffer: []u8) File
43134836 var procfs_buf: ["/proc/self/path/-2147483648\x00".len]u8 = undefined;
43144837 const template = if (native_os == .illumos) "/proc/self/path/{d}" else "/proc/self/fd/{d}";
43154838 const proc_path = std.fmt.bufPrintSentinel(&procfs_buf, template, .{fd}, 0) catch unreachable;
4316 try current_thread.beginSyscall();
4839 const syscall: Syscall = try .start();
43174840 while (true) {
43184841 const rc = posix.system.readlink(proc_path, out_buffer.ptr, out_buffer.len);
43194842 switch (posix.errno(rc)) {
43204843 .SUCCESS => {
4321 current_thread.endSyscall();
4844 syscall.finish();
43224845 const len: usize = @bitCast(rc);
43234846 return len;
43244847 },
43254848 .INTR => {
4326 try current_thread.checkCancel();
4849 try syscall.checkCancel();
43274850 continue;
43284851 },
43294852 else => |e| {
4330 current_thread.endSyscall();
4853 syscall.finish();
43314854 switch (e) {
43324855 .ACCES => return error.AccessDenied,
43334856 .FAULT => |err| return errnoBug(err),
......@@ -4347,23 +4870,23 @@ fn realPathPosix(current_thread: *Thread, fd: posix.fd_t, out_buffer: []u8) File
43474870 .freebsd => {
43484871 var k_file: std.c.kinfo_file = undefined;
43494872 k_file.structsize = std.c.KINFO_FILE_SIZE;
4350 try current_thread.beginSyscall();
4873 const syscall: Syscall = try .start();
43514874 while (true) {
43524875 switch (posix.errno(std.c.fcntl(fd, std.c.F.KINFO, @intFromPtr(&k_file)))) {
43534876 .SUCCESS => {
4354 current_thread.endSyscall();
4877 syscall.finish();
43554878 break;
43564879 },
43574880 .INTR => {
4358 try current_thread.checkCancel();
4881 try syscall.checkCancel();
43594882 continue;
43604883 },
43614884 .BADF => {
4362 current_thread.endSyscall();
4885 syscall.finish();
43634886 return error.FileNotFound;
43644887 },
43654888 else => |err| {
4366 current_thread.endSyscall();
4889 syscall.finish();
43674890 return posix.unexpectedErrno(err);
43684891 },
43694892 }
......@@ -4394,21 +4917,21 @@ fn dirDeleteFileWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8) D
43944917fn dirDeleteFileWasi(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8) Dir.DeleteFileError!void {
43954918 if (builtin.link_libc) return dirDeleteFilePosix(userdata, dir, sub_path);
43964919 const t: *Threaded = @ptrCast(@alignCast(userdata));
4397 const current_thread = Thread.getCurrent(t);
4398 try current_thread.beginSyscall();
4920 _ = t;
4921 const syscall: Syscall = try .start();
43994922 while (true) {
44004923 const res = std.os.wasi.path_unlink_file(dir.handle, sub_path.ptr, sub_path.len);
44014924 switch (res) {
44024925 .SUCCESS => {
4403 current_thread.endSyscall();
4926 syscall.finish();
44044927 return;
44054928 },
44064929 .INTR => {
4407 try current_thread.checkCancel();
4930 try syscall.checkCancel();
44084931 continue;
44094932 },
44104933 else => |e| {
4411 current_thread.endSyscall();
4934 syscall.finish();
44124935 switch (e) {
44134936 .ACCES => return error.AccessDenied,
44144937 .PERM => return error.PermissionDenied,
......@@ -4435,20 +4958,20 @@ fn dirDeleteFileWasi(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8) Dir.
44354958
44364959fn dirDeleteFilePosix(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8) Dir.DeleteFileError!void {
44374960 const t: *Threaded = @ptrCast(@alignCast(userdata));
4438 const current_thread = Thread.getCurrent(t);
4961 _ = t;
44394962
44404963 var path_buffer: [posix.PATH_MAX]u8 = undefined;
44414964 const sub_path_posix = try pathToPosix(sub_path, &path_buffer);
44424965
4443 try current_thread.beginSyscall();
4966 const syscall: Syscall = try .start();
44444967 while (true) {
44454968 switch (posix.errno(posix.system.unlinkat(dir.handle, sub_path_posix, 0))) {
44464969 .SUCCESS => {
4447 current_thread.endSyscall();
4970 syscall.finish();
44484971 return;
44494972 },
44504973 .INTR => {
4451 try current_thread.checkCancel();
4974 try syscall.checkCancel();
44524975 continue;
44534976 },
44544977 // 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
44604983 // Don't follow symlinks to match unlinkat (which acts on symlinks rather than follows them).
44614984 var st = std.mem.zeroes(posix.Stat);
44624985 while (true) {
4463 try current_thread.checkCancel();
4986 try syscall.checkCancel();
44644987 switch (posix.errno(fstatat_sym(dir.handle, sub_path_posix, &st, posix.AT.SYMLINK_NOFOLLOW))) {
44654988 .SUCCESS => {
4466 current_thread.endSyscall();
4989 syscall.finish();
44674990 break;
44684991 },
44694992 .INTR => continue,
44704993 else => {
4471 current_thread.endSyscall();
4994 syscall.finish();
44724995 return error.PermissionDenied;
44734996 },
44744997 }
......@@ -4480,12 +5003,12 @@ fn dirDeleteFilePosix(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8) Dir
44805003 return error.PermissionDenied;
44815004 },
44825005 else => {
4483 current_thread.endSyscall();
5006 syscall.finish();
44845007 return error.PermissionDenied;
44855008 },
44865009 },
44875010 else => |e| {
4488 current_thread.endSyscall();
5011 syscall.finish();
44895012 switch (e) {
44905013 .ACCES => return error.AccessDenied,
44915014 .BUSY => return error.FileBusy,
......@@ -4525,74 +5048,74 @@ fn dirDeleteDirWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8) Di
45255048
45265049fn dirDeleteWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, remove_dir: bool) (Dir.DeleteDirError || Dir.DeleteFileError)!void {
45275050 const t: *Threaded = @ptrCast(@alignCast(userdata));
4528 const current_thread = Thread.getCurrent(t);
5051 _ = t;
45295052 const w = windows;
45305053
4531 try current_thread.checkCancel();
4532
45335054 const sub_path_w_buf = try w.sliceToPrefixedFileW(dir.handle, sub_path);
45345055 const sub_path_w = sub_path_w_buf.span();
45355056
45365057 const path_len_bytes = @as(u16, @intCast(sub_path_w.len * 2));
45375058 var nt_name: w.UNICODE_STRING = .{
45385059 .Length = path_len_bytes,
4539 .MaximumLength = path_len_bytes,
4540 // The Windows API makes this mutable, but it will not mutate here.
4541 .Buffer = @constCast(sub_path_w.ptr),
4542 };
4543
4544 if (sub_path_w[0] == '.' and sub_path_w[1] == 0) {
4545 // Windows does not recognize this, but it does work with empty string.
4546 nt_name.Length = 0;
4547 }
4548 if (sub_path_w[0] == '.' and sub_path_w[1] == '.' and sub_path_w[2] == 0) {
4549 // Can't remove the parent directory with an open handle.
4550 return error.FileBusy;
4551 }
4552
4553 var io_status_block: w.IO_STATUS_BLOCK = undefined;
4554 var tmp_handle: w.HANDLE = undefined;
4555 var rc = w.ntdll.NtCreateFile(
4556 &tmp_handle,
4557 .{ .STANDARD = .{
4558 .RIGHTS = .{ .DELETE = true },
4559 .SYNCHRONIZE = true,
4560 } },
4561 &.{
4562 .Length = @sizeOf(w.OBJECT_ATTRIBUTES),
4563 .RootDirectory = if (std.fs.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle,
4564 .Attributes = .{},
4565 .ObjectName = &nt_name,
4566 .SecurityDescriptor = null,
4567 .SecurityQualityOfService = null,
4568 },
4569 &io_status_block,
4570 null,
4571 .{},
4572 .VALID_FLAGS,
4573 .OPEN,
4574 .{
4575 .DIRECTORY_FILE = remove_dir,
4576 .NON_DIRECTORY_FILE = !remove_dir,
4577 .OPEN_REPARSE_POINT = true, // would we ever want to delete the target instead?
4578 },
4579 null,
4580 0,
4581 );
4582 switch (rc) {
4583 .SUCCESS => {},
4584 .OBJECT_NAME_INVALID => |err| return w.statusBug(err),
4585 .OBJECT_NAME_NOT_FOUND => return error.FileNotFound,
4586 .OBJECT_PATH_NOT_FOUND => return error.FileNotFound,
4587 .BAD_NETWORK_PATH => return error.NetworkNotFound, // \\server was not found
4588 .BAD_NETWORK_NAME => return error.NetworkNotFound, // \\server was found but \\server\share wasn't
4589 .INVALID_PARAMETER => |err| return w.statusBug(err),
4590 .FILE_IS_A_DIRECTORY => return error.IsDir,
4591 .NOT_A_DIRECTORY => return error.NotDir,
4592 .SHARING_VIOLATION => return error.FileBusy,
4593 .ACCESS_DENIED => return error.AccessDenied,
4594 .DELETE_PENDING => return,
4595 else => return w.unexpectedStatus(rc),
5060 .MaximumLength = path_len_bytes,
5061 // The Windows API makes this mutable, but it will not mutate here.
5062 .Buffer = @constCast(sub_path_w.ptr),
5063 };
5064
5065 if (sub_path_w[0] == '.' and sub_path_w[1] == 0) {
5066 // Windows does not recognize this, but it does work with empty string.
5067 nt_name.Length = 0;
5068 }
5069 if (sub_path_w[0] == '.' and sub_path_w[1] == '.' and sub_path_w[2] == 0) {
5070 // Can't remove the parent directory with an open handle.
5071 return error.FileBusy;
5072 }
5073
5074 var io_status_block: w.IO_STATUS_BLOCK = undefined;
5075 var tmp_handle: w.HANDLE = undefined;
5076 {
5077 const syscall: Syscall = try .start();
5078 while (true) switch (w.ntdll.NtCreateFile(
5079 &tmp_handle,
5080 .{ .STANDARD = .{
5081 .RIGHTS = .{ .DELETE = true },
5082 .SYNCHRONIZE = true,
5083 } },
5084 &.{
5085 .Length = @sizeOf(w.OBJECT_ATTRIBUTES),
5086 .RootDirectory = if (std.fs.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle,
5087 .Attributes = .{},
5088 .ObjectName = &nt_name,
5089 .SecurityDescriptor = null,
5090 .SecurityQualityOfService = null,
5091 },
5092 &io_status_block,
5093 null,
5094 .{},
5095 .VALID_FLAGS,
5096 .OPEN,
5097 .{
5098 .DIRECTORY_FILE = remove_dir,
5099 .NON_DIRECTORY_FILE = !remove_dir,
5100 .OPEN_REPARSE_POINT = true, // would we ever want to delete the target instead?
5101 },
5102 null,
5103 0,
5104 )) {
5105 .SUCCESS => break syscall.finish(),
5106 .OBJECT_NAME_INVALID => |err| return syscall.ntstatusBug(err),
5107 .OBJECT_NAME_NOT_FOUND => return syscall.fail(error.FileNotFound),
5108 .OBJECT_PATH_NOT_FOUND => return syscall.fail(error.FileNotFound),
5109 .BAD_NETWORK_PATH => return syscall.fail(error.NetworkNotFound), // \\server was not found
5110 .BAD_NETWORK_NAME => return syscall.fail(error.NetworkNotFound), // \\server was found but \\server\share wasn't
5111 .INVALID_PARAMETER => |err| return syscall.ntstatusBug(err),
5112 .FILE_IS_A_DIRECTORY => return syscall.fail(error.IsDir),
5113 .NOT_A_DIRECTORY => return syscall.fail(error.NotDir),
5114 .SHARING_VIOLATION => return syscall.fail(error.FileBusy),
5115 .ACCESS_DENIED => return syscall.fail(error.AccessDenied),
5116 .DELETE_PENDING => return syscall.finish(),
5117 else => |rc| return syscall.unexpectedNtstatus(rc),
5118 };
45965119 }
45975120 defer w.CloseHandle(tmp_handle);
45985121
......@@ -4607,9 +5130,7 @@ fn dirDeleteWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, remov
46075130 //
46085131 // The strategy here is just to try using FileDispositionInformationEx and fall back to
46095132 // FileDispositionInformation if the return value lets us know that some aspect of it is not supported.
4610 const need_fallback = need_fallback: {
4611 try current_thread.checkCancel();
4612
5133 const rc = rc: {
46135134 // Deletion with posix semantics if the filesystem supports it.
46145135 const info: w.FILE.DISPOSITION.INFORMATION.EX = .{ .Flags = .{
46155136 .DELETE = true,
......@@ -4617,29 +5138,32 @@ fn dirDeleteWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, remov
46175138 .IGNORE_READONLY_ATTRIBUTE = true,
46185139 } };
46195140
4620 rc = w.ntdll.NtSetInformationFile(
5141 const syscall: Syscall = try .start();
5142 while (true) switch (w.ntdll.NtSetInformationFile(
46215143 tmp_handle,
46225144 &io_status_block,
46235145 &info,
46245146 @sizeOf(w.FILE.DISPOSITION.INFORMATION.EX),
46255147 .DispositionEx,
4626 );
4627 switch (rc) {
4628 .SUCCESS => return,
5148 )) {
5149 .CANCELLED => {
5150 try syscall.checkCancel();
5151 continue;
5152 },
46295153 // The filesystem does not support FileDispositionInformationEx
46305154 .INVALID_PARAMETER,
46315155 // The operating system does not support FileDispositionInformationEx
46325156 .INVALID_INFO_CLASS,
46335157 // The operating system does not support one of the flags
46345158 .NOT_SUPPORTED,
4635 => break :need_fallback true,
4636 // For all other statuses, fall down to the switch below to handle them.
4637 else => break :need_fallback false,
4638 }
4639 };
5159 => break, // use fallback path below; `syscall` still active
46405160
4641 if (need_fallback) {
4642 try current_thread.checkCancel();
5161 // For all other statuses, fall down to the switch below to handle them.
5162 else => |rc| {
5163 syscall.finish();
5164 break :rc rc;
5165 },
5166 };
46435167
46445168 // Deletion with file pending semantics, which requires waiting or moving
46455169 // files to get them removed (from here).
......@@ -4647,14 +5171,23 @@ fn dirDeleteWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, remov
46475171 .DeleteFile = w.TRUE,
46485172 };
46495173
4650 rc = w.ntdll.NtSetInformationFile(
5174 while (true) switch (w.ntdll.NtSetInformationFile(
46515175 tmp_handle,
46525176 &io_status_block,
46535177 &file_dispo,
46545178 @sizeOf(w.FILE.DISPOSITION.INFORMATION),
46555179 .Disposition,
4656 );
4657 }
5180 )) {
5181 .CANCELLED => {
5182 try syscall.checkCancel();
5183 continue;
5184 },
5185 else => |rc| {
5186 syscall.finish();
5187 break :rc rc;
5188 },
5189 };
5190 };
46585191 switch (rc) {
46595192 .SUCCESS => {},
46605193 .DIRECTORY_NOT_EMPTY => return error.DirNotEmpty,
......@@ -4670,22 +5203,22 @@ fn dirDeleteDirWasi(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8) Dir.D
46705203 if (builtin.link_libc) return dirDeleteDirPosix(userdata, dir, sub_path);
46715204
46725205 const t: *Threaded = @ptrCast(@alignCast(userdata));
4673 const current_thread = Thread.getCurrent(t);
5206 _ = t;
46745207
4675 try current_thread.beginSyscall();
5208 const syscall: Syscall = try .start();
46765209 while (true) {
46775210 const res = std.os.wasi.path_remove_directory(dir.handle, sub_path.ptr, sub_path.len);
46785211 switch (res) {
46795212 .SUCCESS => {
4680 current_thread.endSyscall();
5213 syscall.finish();
46815214 return;
46825215 },
46835216 .INTR => {
4684 try current_thread.checkCancel();
5217 try syscall.checkCancel();
46855218 continue;
46865219 },
46875220 else => |e| {
4688 current_thread.endSyscall();
5221 syscall.finish();
46895222 switch (e) {
46905223 .ACCES => return error.AccessDenied,
46915224 .PERM => return error.PermissionDenied,
......@@ -4712,24 +5245,24 @@ fn dirDeleteDirWasi(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8) Dir.D
47125245
47135246fn dirDeleteDirPosix(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8) Dir.DeleteDirError!void {
47145247 const t: *Threaded = @ptrCast(@alignCast(userdata));
4715 const current_thread = Thread.getCurrent(t);
5248 _ = t;
47165249
47175250 var path_buffer: [posix.PATH_MAX]u8 = undefined;
47185251 const sub_path_posix = try pathToPosix(sub_path, &path_buffer);
47195252
4720 try current_thread.beginSyscall();
5253 const syscall: Syscall = try .start();
47215254 while (true) {
47225255 switch (posix.errno(posix.system.unlinkat(dir.handle, sub_path_posix, posix.AT.REMOVEDIR))) {
47235256 .SUCCESS => {
4724 current_thread.endSyscall();
5257 syscall.finish();
47255258 return;
47265259 },
47275260 .INTR => {
4728 try current_thread.checkCancel();
5261 try syscall.checkCancel();
47295262 continue;
47305263 },
47315264 else => |e| {
4732 current_thread.endSyscall();
5265 syscall.finish();
47335266 switch (e) {
47345267 .ACCES => return error.AccessDenied,
47355268 .PERM => return error.PermissionDenied,
......@@ -4770,7 +5303,7 @@ fn dirRenameWindows(
47705303) Dir.RenameError!void {
47715304 const w = windows;
47725305 const t: *Threaded = @ptrCast(@alignCast(userdata));
4773 const current_thread = Thread.getCurrent(t);
5306 _ = t;
47745307
47755308 const old_path_w_buf = try windows.sliceToPrefixedFileW(old_dir.handle, old_sub_path);
47765309 const old_path_w = old_path_w_buf.span();
......@@ -4778,23 +5311,33 @@ fn dirRenameWindows(
47785311 const new_path_w = new_path_w_buf.span();
47795312 const replace_if_exists = true;
47805313
4781 try current_thread.checkCancel();
4782
4783 const src_fd = w.OpenFile(old_path_w, .{
4784 .dir = old_dir.handle,
4785 .access_mask = .{
4786 .GENERIC = .{ .WRITE = true },
4787 .STANDARD = .{
4788 .RIGHTS = .{ .DELETE = true },
4789 .SYNCHRONIZE = true,
4790 },
4791 },
4792 .creation = .OPEN,
4793 .filter = .any, // This function is supposed to rename both files and directories.
4794 .follow_symlinks = false,
4795 }) catch |err| switch (err) {
4796 error.WouldBlock => unreachable, // Not possible without `.share_access_nonblocking = true`.
4797 else => |e| return e,
5314 const src_fd = src_fd: {
5315 const syscall: Syscall = try .start();
5316 while (true) {
5317 if (w.OpenFile(old_path_w, .{
5318 .dir = old_dir.handle,
5319 .access_mask = .{
5320 .GENERIC = .{ .WRITE = true },
5321 .STANDARD = .{
5322 .RIGHTS = .{ .DELETE = true },
5323 .SYNCHRONIZE = true,
5324 },
5325 },
5326 .creation = .OPEN,
5327 .filter = .any, // This function is supposed to rename both files and directories.
5328 .follow_symlinks = false,
5329 })) |handle| {
5330 syscall.finish();
5331 break :src_fd handle;
5332 } else |err| switch (err) {
5333 error.WouldBlock => unreachable, // Not possible without `.share_access_nonblocking = true`.
5334 error.OperationCanceled => {
5335 try syscall.checkCancel();
5336 continue;
5337 },
5338 else => |e| return e,
5339 }
5340 }
47985341 };
47995342 defer w.CloseHandle(src_fd);
48005343
......@@ -4887,18 +5430,18 @@ fn dirRenameWasi(
48875430 if (builtin.link_libc) return dirRenamePosix(userdata, old_dir, old_sub_path, new_dir, new_sub_path);
48885431
48895432 const t: *Threaded = @ptrCast(@alignCast(userdata));
4890 const current_thread = Thread.getCurrent(t);
5433 _ = t;
48915434
4892 try current_thread.beginSyscall();
5435 const syscall: Syscall = try .start();
48935436 while (true) {
48945437 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)) {
4895 .SUCCESS => return current_thread.endSyscall(),
5438 .SUCCESS => return syscall.finish(),
48965439 .INTR => {
4897 try current_thread.checkCancel();
5440 try syscall.checkCancel();
48985441 continue;
48995442 },
49005443 else => |e| {
4901 current_thread.endSyscall();
5444 syscall.finish();
49025445 switch (e) {
49035446 .ACCES => return error.AccessDenied,
49045447 .PERM => return error.PermissionDenied,
......@@ -4935,7 +5478,7 @@ fn dirRenamePosix(
49355478 new_sub_path: []const u8,
49365479) Dir.RenameError!void {
49375480 const t: *Threaded = @ptrCast(@alignCast(userdata));
4938 const current_thread = Thread.getCurrent(t);
5481 _ = t;
49395482
49405483 var old_path_buffer: [posix.PATH_MAX]u8 = undefined;
49415484 var new_path_buffer: [posix.PATH_MAX]u8 = undefined;
......@@ -4943,16 +5486,16 @@ fn dirRenamePosix(
49435486 const old_sub_path_posix = try pathToPosix(old_sub_path, &old_path_buffer);
49445487 const new_sub_path_posix = try pathToPosix(new_sub_path, &new_path_buffer);
49455488
4946 try current_thread.beginSyscall();
5489 const syscall: Syscall = try .start();
49475490 while (true) {
49485491 switch (posix.errno(posix.system.renameat(old_dir.handle, old_sub_path_posix, new_dir.handle, new_sub_path_posix))) {
4949 .SUCCESS => return current_thread.endSyscall(),
5492 .SUCCESS => return syscall.finish(),
49505493 .INTR => {
4951 try current_thread.checkCancel();
5494 try syscall.checkCancel();
49525495 continue;
49535496 },
49545497 else => |e| {
4955 current_thread.endSyscall();
5498 syscall.finish();
49565499 switch (e) {
49575500 .ACCES => return error.AccessDenied,
49585501 .PERM => return error.PermissionDenied,
......@@ -4994,11 +5537,9 @@ fn dirSymLinkWindows(
49945537 flags: Dir.SymLinkFlags,
49955538) Dir.SymLinkError!void {
49965539 const t: *Threaded = @ptrCast(@alignCast(userdata));
4997 const current_thread = Thread.getCurrent(t);
5540 _ = t;
49985541 const w = windows;
49995542
5000 try current_thread.checkCancel();
5001
50025543 // Target path does not use sliceToPrefixedFileW because certain paths
50035544 // are handled differently when creating a symlink than they would be
50045545 // when converting to an NT namespaced path. CreateSymbolicLink in
......@@ -5028,22 +5569,34 @@ fn dirSymLinkWindows(
50285569 Flags: w.ULONG,
50295570 };
50305571
5031 const symlink_handle = w.OpenFile(sym_link_path_w.span(), .{
5032 .access_mask = .{
5033 .GENERIC = .{ .READ = true, .WRITE = true },
5034 .STANDARD = .{ .SYNCHRONIZE = true },
5035 },
5036 .dir = dir.handle,
5037 .creation = .CREATE,
5038 .filter = if (flags.is_directory) .dir_only else .non_directory_only,
5039 }) catch |err| switch (err) {
5040 error.IsDir => return error.PathAlreadyExists,
5041 error.NotDir => return error.Unexpected,
5042 error.WouldBlock => return error.Unexpected,
5043 error.PipeBusy => return error.Unexpected,
5044 error.NoDevice => return error.Unexpected,
5045 error.AntivirusInterference => return error.Unexpected,
5046 else => |e| return e,
5572 const symlink_handle = handle: {
5573 const syscall: Syscall = try .start();
5574 while (true) {
5575 if (w.OpenFile(sym_link_path_w.span(), .{
5576 .access_mask = .{
5577 .GENERIC = .{ .READ = true, .WRITE = true },
5578 .STANDARD = .{ .SYNCHRONIZE = true },
5579 },
5580 .dir = dir.handle,
5581 .creation = .CREATE,
5582 .filter = if (flags.is_directory) .dir_only else .non_directory_only,
5583 })) |handle| {
5584 syscall.finish();
5585 break :handle handle;
5586 } else |err| switch (err) {
5587 error.IsDir => return syscall.fail(error.PathAlreadyExists),
5588 error.NotDir => return syscall.fail(error.Unexpected),
5589 error.WouldBlock => return syscall.fail(error.Unexpected),
5590 error.PipeBusy => return syscall.fail(error.Unexpected),
5591 error.NoDevice => return syscall.fail(error.Unexpected),
5592 error.AntivirusInterference => return syscall.fail(error.Unexpected),
5593 error.OperationCanceled => {
5594 try syscall.checkCancel();
5595 continue;
5596 },
5597 else => |e| return e,
5598 }
5599 }
50475600 };
50485601 defer w.CloseHandle(symlink_handle);
50495602
......@@ -5121,18 +5674,18 @@ fn dirSymLinkWasi(
51215674 if (builtin.link_libc) return dirSymLinkPosix(userdata, dir, target_path, sym_link_path, flags);
51225675
51235676 const t: *Threaded = @ptrCast(@alignCast(userdata));
5124 const current_thread = Thread.getCurrent(t);
5677 _ = t;
51255678
5126 try current_thread.beginSyscall();
5679 const syscall: Syscall = try .start();
51275680 while (true) {
51285681 switch (std.os.wasi.path_symlink(target_path.ptr, target_path.len, dir.handle, sym_link_path.ptr, sym_link_path.len)) {
5129 .SUCCESS => return current_thread.endSyscall(),
5682 .SUCCESS => return syscall.finish(),
51305683 .INTR => {
5131 try current_thread.checkCancel();
5684 try syscall.checkCancel();
51325685 continue;
51335686 },
51345687 else => |e| {
5135 current_thread.endSyscall();
5688 syscall.finish();
51365689 switch (e) {
51375690 .FAULT => |err| return errnoBug(err),
51385691 .INVAL => |err| return errnoBug(err),
......@@ -5167,7 +5720,7 @@ fn dirSymLinkPosix(
51675720) Dir.SymLinkError!void {
51685721 _ = flags;
51695722 const t: *Threaded = @ptrCast(@alignCast(userdata));
5170 const current_thread = Thread.getCurrent(t);
5723 _ = t;
51715724
51725725 var target_path_buffer: [posix.PATH_MAX]u8 = undefined;
51735726 var sym_link_path_buffer: [posix.PATH_MAX]u8 = undefined;
......@@ -5175,16 +5728,16 @@ fn dirSymLinkPosix(
51755728 const target_path_posix = try pathToPosix(target_path, &target_path_buffer);
51765729 const sym_link_path_posix = try pathToPosix(sym_link_path, &sym_link_path_buffer);
51775730
5178 try current_thread.beginSyscall();
5731 const syscall: Syscall = try .start();
51795732 while (true) {
51805733 switch (posix.errno(posix.system.symlinkat(target_path_posix, dir.handle, sym_link_path_posix))) {
5181 .SUCCESS => return current_thread.endSyscall(),
5734 .SUCCESS => return syscall.finish(),
51825735 .INTR => {
5183 try current_thread.checkCancel();
5736 try syscall.checkCancel();
51845737 continue;
51855738 },
51865739 else => |e| {
5187 current_thread.endSyscall();
5740 syscall.finish();
51885741 switch (e) {
51895742 .FAULT => |err| return errnoBug(err),
51905743 .INVAL => |err| return errnoBug(err),
......@@ -5216,14 +5769,24 @@ const dirReadLink = switch (native_os) {
52165769
52175770fn dirReadLinkWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, buffer: []u8) Dir.ReadLinkError!usize {
52185771 const t: *Threaded = @ptrCast(@alignCast(userdata));
5219 const current_thread = Thread.getCurrent(t);
5772 _ = t;
52205773 const w = windows;
52215774
5222 try current_thread.checkCancel();
5223
52245775 var sub_path_w_buf = try windows.sliceToPrefixedFileW(dir.handle, sub_path);
52255776
5226 const result_w = try w.ReadLink(dir.handle, sub_path_w_buf.span(), &sub_path_w_buf.data);
5777 const syscall: Syscall = try .start();
5778 const result_w = while (true) {
5779 if (w.ReadLink(dir.handle, sub_path_w_buf.span(), &sub_path_w_buf.data)) |res| {
5780 syscall.finish();
5781 break res;
5782 } else |err| switch (err) {
5783 error.OperationCanceled => {
5784 try syscall.checkCancel();
5785 continue;
5786 },
5787 else => |e| return syscall.fail(e),
5788 }
5789 };
52275790
52285791 const len = std.unicode.calcWtf8Len(result_w);
52295792 if (len > buffer.len) return error.NameTooLong;
......@@ -5235,22 +5798,22 @@ fn dirReadLinkWasi(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, buffer
52355798 if (builtin.link_libc) return dirReadLinkPosix(userdata, dir, sub_path, buffer);
52365799
52375800 const t: *Threaded = @ptrCast(@alignCast(userdata));
5238 const current_thread = Thread.getCurrent(t);
5801 _ = t;
52395802
52405803 var n: usize = undefined;
5241 try current_thread.beginSyscall();
5804 const syscall: Syscall = try .start();
52425805 while (true) {
52435806 switch (std.os.wasi.path_readlink(dir.handle, sub_path.ptr, sub_path.len, buffer.ptr, buffer.len, &n)) {
52445807 .SUCCESS => {
5245 current_thread.endSyscall();
5808 syscall.finish();
52465809 return n;
52475810 },
52485811 .INTR => {
5249 try current_thread.checkCancel();
5812 try syscall.checkCancel();
52505813 continue;
52515814 },
52525815 else => |e| {
5253 current_thread.endSyscall();
5816 syscall.finish();
52545817 switch (e) {
52555818 .ACCES => return error.AccessDenied,
52565819 .FAULT => |err| return errnoBug(err),
......@@ -5272,26 +5835,26 @@ fn dirReadLinkWasi(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, buffer
52725835
52735836fn dirReadLinkPosix(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, buffer: []u8) Dir.ReadLinkError!usize {
52745837 const t: *Threaded = @ptrCast(@alignCast(userdata));
5275 const current_thread = Thread.getCurrent(t);
5838 _ = t;
52765839
52775840 var sub_path_buffer: [posix.PATH_MAX]u8 = undefined;
52785841 const sub_path_posix = try pathToPosix(sub_path, &sub_path_buffer);
52795842
5280 try current_thread.beginSyscall();
5843 const syscall: Syscall = try .start();
52815844 while (true) {
52825845 const rc = posix.system.readlinkat(dir.handle, sub_path_posix, buffer.ptr, buffer.len);
52835846 switch (posix.errno(rc)) {
52845847 .SUCCESS => {
5285 current_thread.endSyscall();
5848 syscall.finish();
52865849 const len: usize = @bitCast(rc);
52875850 return len;
52885851 },
52895852 .INTR => {
5290 try current_thread.checkCancel();
5853 try syscall.checkCancel();
52915854 continue;
52925855 },
52935856 else => |e| {
5294 current_thread.endSyscall();
5857 syscall.finish();
52955858 switch (e) {
52965859 .ACCES => return error.AccessDenied,
52975860 .FAULT => |err| return errnoBug(err),
......@@ -5326,8 +5889,8 @@ fn dirSetPermissionsWindows(userdata: ?*anyopaque, dir: Dir, permissions: Dir.Pe
53265889fn dirSetPermissionsPosix(userdata: ?*anyopaque, dir: Dir, permissions: Dir.Permissions) Dir.SetPermissionsError!void {
53275890 if (@sizeOf(Dir.Permissions) == 0) return;
53285891 const t: *Threaded = @ptrCast(@alignCast(userdata));
5329 const current_thread = Thread.getCurrent(t);
5330 return setPermissionsPosix(current_thread, dir.handle, permissions.toMode());
5892 _ = t;
5893 return setPermissionsPosix(dir.handle, permissions.toMode());
53315894}
53325895
53335896fn dirSetFilePermissions(
......@@ -5340,7 +5903,6 @@ fn dirSetFilePermissions(
53405903 if (@sizeOf(Dir.Permissions) == 0) return;
53415904 if (is_windows) @panic("TODO implement dirSetFilePermissions windows");
53425905 const t: *Threaded = @ptrCast(@alignCast(userdata));
5343 const current_thread = Thread.getCurrent(t);
53445906
53455907 var path_buffer: [posix.PATH_MAX]u8 = undefined;
53465908 const sub_path_posix = try pathToPosix(sub_path, &path_buffer);
......@@ -5348,12 +5910,11 @@ fn dirSetFilePermissions(
53485910 const mode = permissions.toMode();
53495911 const flags: u32 = if (!options.follow_symlinks) posix.AT.SYMLINK_NOFOLLOW else 0;
53505912
5351 return posixFchmodat(t, current_thread, dir.handle, sub_path_posix, mode, flags);
5913 return posixFchmodat(t, dir.handle, sub_path_posix, mode, flags);
53525914}
53535915
53545916fn posixFchmodat(
53555917 t: *Threaded,
5356 current_thread: *Thread,
53575918 dir_fd: posix.fd_t,
53585919 path: [*:0]const u8,
53595920 mode: posix.mode_t,
......@@ -5362,20 +5923,20 @@ fn posixFchmodat(
53625923 // No special handling for linux is needed if we can use the libc fallback
53635924 // or `flags` is empty. Glibc only added the fallback in 2.32.
53645925 if (have_fchmodat_flags or flags == 0) {
5365 try current_thread.beginSyscall();
5926 const syscall: Syscall = try .start();
53665927 while (true) {
53675928 const rc = if (have_fchmodat_flags or builtin.link_libc)
53685929 posix.system.fchmodat(dir_fd, path, mode, flags)
53695930 else
53705931 posix.system.fchmodat(dir_fd, path, mode);
53715932 switch (posix.errno(rc)) {
5372 .SUCCESS => return current_thread.endSyscall(),
5933 .SUCCESS => return syscall.finish(),
53735934 .INTR => {
5374 try current_thread.checkCancel();
5935 try syscall.checkCancel();
53755936 continue;
53765937 },
53775938 else => |e| {
5378 current_thread.endSyscall();
5939 syscall.finish();
53795940 switch (e) {
53805941 .BADF => |err| return errnoBug(err),
53815942 .FAULT => |err| return errnoBug(err),
......@@ -5400,20 +5961,20 @@ fn posixFchmodat(
54005961 }
54015962
54025963 if (@atomicLoad(UseFchmodat2, &t.use_fchmodat2, .monotonic) == .disabled)
5403 return fchmodatFallback(current_thread, dir_fd, path, mode);
5964 return fchmodatFallback(dir_fd, path, mode);
54045965
54055966 comptime assert(native_os == .linux);
54065967
5407 try current_thread.beginSyscall();
5968 const syscall: Syscall = try .start();
54085969 while (true) {
54095970 switch (std.os.linux.errno(std.os.linux.fchmodat2(dir_fd, path, mode, flags))) {
5410 .SUCCESS => return current_thread.endSyscall(),
5971 .SUCCESS => return syscall.finish(),
54115972 .INTR => {
5412 try current_thread.checkCancel();
5973 try syscall.checkCancel();
54135974 continue;
54145975 },
54155976 else => |e| {
5416 current_thread.endSyscall();
5977 syscall.finish();
54175978 switch (e) {
54185979 .BADF => |err| return errnoBug(err),
54195980 .FAULT => |err| return errnoBug(err),
......@@ -5429,7 +5990,7 @@ fn posixFchmodat(
54295990 .ROFS => return error.ReadOnlyFileSystem,
54305991 .NOSYS => {
54315992 @atomicStore(UseFchmodat2, &t.use_fchmodat2, .disabled, .monotonic);
5432 return fchmodatFallback(current_thread, dir_fd, path, mode);
5993 return fchmodatFallback(dir_fd, path, mode);
54335994 },
54345995 else => |err| return posix.unexpectedErrno(err),
54355996 }
......@@ -5439,7 +6000,6 @@ fn posixFchmodat(
54396000}
54406001
54416002fn fchmodatFallback(
5442 current_thread: *Thread,
54436003 dir_fd: posix.fd_t,
54446004 path: [*:0]const u8,
54456005 mode: posix.mode_t,
......@@ -5457,64 +6017,68 @@ fn fchmodatFallback(
54576017 // 2. Stat the fd and check if it isn't a symbolic link.
54586018 // 3. Generate the procfs reference to the fd via `/proc/self/fd/{fd}`.
54596019 // 4. Pass the procfs path to `chmod` with the `mode`.
5460 try current_thread.beginSyscall();
5461 const path_fd: posix.fd_t = while (true) {
5462 const rc = posix.system.openat(dir_fd, path, .{
5463 .PATH = true,
5464 .NOFOLLOW = true,
5465 .CLOEXEC = true,
5466 }, @as(posix.mode_t, 0));
5467 switch (posix.errno(rc)) {
5468 .SUCCESS => {
5469 current_thread.endSyscall();
5470 break @intCast(rc);
5471 },
5472 .INTR => {
5473 try current_thread.checkCancel();
5474 continue;
5475 },
5476 else => |e| {
5477 current_thread.endSyscall();
5478 switch (e) {
5479 .FAULT => |err| return errnoBug(err),
5480 .INVAL => |err| return errnoBug(err),
5481 .ACCES => return error.AccessDenied,
5482 .PERM => return error.PermissionDenied,
5483 .LOOP => return error.SymLinkLoop,
5484 .MFILE => return error.ProcessFdQuotaExceeded,
5485 .NAMETOOLONG => return error.NameTooLong,
5486 .NFILE => return error.SystemFdQuotaExceeded,
5487 .NOENT => return error.FileNotFound,
5488 .NOMEM => return error.SystemResources,
5489 else => |err| return posix.unexpectedErrno(err),
5490 }
5491 },
6020 const path_fd: posix.fd_t = fd: {
6021 const syscall: Syscall = try .start();
6022 while (true) {
6023 const rc = posix.system.openat(dir_fd, path, .{
6024 .PATH = true,
6025 .NOFOLLOW = true,
6026 .CLOEXEC = true,
6027 }, @as(posix.mode_t, 0));
6028 switch (posix.errno(rc)) {
6029 .SUCCESS => {
6030 syscall.finish();
6031 break :fd @intCast(rc);
6032 },
6033 .INTR => {
6034 try syscall.checkCancel();
6035 continue;
6036 },
6037 else => |e| {
6038 syscall.finish();
6039 switch (e) {
6040 .FAULT => |err| return errnoBug(err),
6041 .INVAL => |err| return errnoBug(err),
6042 .ACCES => return error.AccessDenied,
6043 .PERM => return error.PermissionDenied,
6044 .LOOP => return error.SymLinkLoop,
6045 .MFILE => return error.ProcessFdQuotaExceeded,
6046 .NAMETOOLONG => return error.NameTooLong,
6047 .NFILE => return error.SystemFdQuotaExceeded,
6048 .NOENT => return error.FileNotFound,
6049 .NOMEM => return error.SystemResources,
6050 else => |err| return posix.unexpectedErrno(err),
6051 }
6052 },
6053 }
54926054 }
54936055 };
54946056 defer posix.close(path_fd);
54956057
5496 try current_thread.beginSyscall();
5497 const path_mode = while (true) {
5498 var statx = std.mem.zeroes(std.os.linux.Statx);
5499 switch (sys.errno(sys.statx(path_fd, "", posix.AT.EMPTY_PATH, .{ .TYPE = true }, &statx))) {
5500 .SUCCESS => {
5501 current_thread.endSyscall();
5502 if (!statx.mask.TYPE) return error.Unexpected;
5503 break statx.mode;
5504 },
5505 .INTR => {
5506 try current_thread.checkCancel();
5507 continue;
5508 },
5509 else => |e| {
5510 current_thread.endSyscall();
5511 switch (e) {
5512 .ACCES => return error.AccessDenied,
5513 .LOOP => return error.SymLinkLoop,
5514 .NOMEM => return error.SystemResources,
5515 else => |err| return posix.unexpectedErrno(err),
5516 }
5517 },
6058 const path_mode = mode: {
6059 const syscall: Syscall = try .start();
6060 while (true) {
6061 var statx = std.mem.zeroes(std.os.linux.Statx);
6062 switch (sys.errno(sys.statx(path_fd, "", posix.AT.EMPTY_PATH, .{ .TYPE = true }, &statx))) {
6063 .SUCCESS => {
6064 syscall.finish();
6065 if (!statx.mask.TYPE) return error.Unexpected;
6066 break :mode statx.mode;
6067 },
6068 .INTR => {
6069 try syscall.checkCancel();
6070 continue;
6071 },
6072 else => |e| {
6073 syscall.finish();
6074 switch (e) {
6075 .ACCES => return error.AccessDenied,
6076 .LOOP => return error.SymLinkLoop,
6077 .NOMEM => return error.SystemResources,
6078 else => |err| return posix.unexpectedErrno(err),
6079 }
6080 },
6081 }
55186082 }
55196083 };
55206084
......@@ -5524,16 +6088,16 @@ fn fchmodatFallback(
55246088
55256089 var procfs_buf: ["/proc/self/fd/-2147483648\x00".len]u8 = undefined;
55266090 const proc_path = std.fmt.bufPrintSentinel(&procfs_buf, "/proc/self/fd/{d}", .{path_fd}, 0) catch unreachable;
5527 try current_thread.beginSyscall();
6091 const syscall: Syscall = try .start();
55286092 while (true) {
55296093 switch (posix.errno(posix.system.chmod(proc_path, mode))) {
5530 .SUCCESS => return current_thread.endSyscall(),
6094 .SUCCESS => return syscall.finish(),
55316095 .INTR => {
5532 try current_thread.checkCancel();
6096 try syscall.checkCancel();
55336097 continue;
55346098 },
55356099 else => |e| {
5536 current_thread.endSyscall();
6100 syscall.finish();
55376101 switch (e) {
55386102 .NOENT => return error.OperationUnsupported, // procfs not mounted.
55396103 .BADF => |err| return errnoBug(err),
......@@ -5569,24 +6133,24 @@ fn dirSetOwnerUnsupported(userdata: ?*anyopaque, dir: Dir, owner: ?File.Uid, gro
55696133fn dirSetOwnerPosix(userdata: ?*anyopaque, dir: Dir, owner: ?File.Uid, group: ?File.Gid) Dir.SetOwnerError!void {
55706134 if (!have_fchown) return error.Unexpected; // Unsupported OS, don't call this function.
55716135 const t: *Threaded = @ptrCast(@alignCast(userdata));
5572 const current_thread = Thread.getCurrent(t);
6136 _ = t;
55736137 const uid = owner orelse ~@as(posix.uid_t, 0);
55746138 const gid = group orelse ~@as(posix.gid_t, 0);
5575 return posixFchown(current_thread, dir.handle, uid, gid);
6139 return posixFchown(dir.handle, uid, gid);
55766140}
55776141
5578fn posixFchown(current_thread: *Thread, fd: posix.fd_t, uid: posix.uid_t, gid: posix.gid_t) File.SetOwnerError!void {
6142fn posixFchown(fd: posix.fd_t, uid: posix.uid_t, gid: posix.gid_t) File.SetOwnerError!void {
55796143 comptime assert(have_fchown);
5580 try current_thread.beginSyscall();
6144 const syscall: Syscall = try .start();
55816145 while (true) {
55826146 switch (posix.errno(posix.system.fchown(fd, uid, gid))) {
5583 .SUCCESS => return current_thread.endSyscall(),
6147 .SUCCESS => return syscall.finish(),
55846148 .INTR => {
5585 try current_thread.checkCancel();
6149 try syscall.checkCancel();
55866150 continue;
55876151 },
55886152 else => |e| {
5589 current_thread.endSyscall();
6153 syscall.finish();
55906154 switch (e) {
55916155 .BADF => |err| return errnoBug(err), // likely fd refers to directory opened without `Dir.OpenOptions.iterate`
55926156 .FAULT => |err| return errnoBug(err),
......@@ -5616,12 +6180,11 @@ fn dirSetFileOwner(
56166180) Dir.SetFileOwnerError!void {
56176181 if (!have_fchown) return error.Unexpected; // Unsupported OS, don't call this function.
56186182 const t: *Threaded = @ptrCast(@alignCast(userdata));
5619 const current_thread = Thread.getCurrent(t);
6183 _ = t;
56206184
56216185 var path_buffer: [posix.PATH_MAX]u8 = undefined;
56226186 const sub_path_posix = try pathToPosix(sub_path, &path_buffer);
56236187
5624 _ = current_thread;
56256188 _ = dir;
56266189 _ = sub_path_posix;
56276190 _ = owner;
......@@ -5638,35 +6201,43 @@ const fileSync = switch (native_os) {
56386201
56396202fn fileSyncWindows(userdata: ?*anyopaque, file: File) File.SyncError!void {
56406203 const t: *Threaded = @ptrCast(@alignCast(userdata));
5641 const current_thread = Thread.getCurrent(t);
5642
5643 try current_thread.checkCancel();
5644
5645 if (windows.kernel32.FlushFileBuffers(file.handle) != 0)
5646 return;
6204 _ = t;
56476205
5648 switch (windows.GetLastError()) {
5649 .SUCCESS => return,
5650 .INVALID_HANDLE => unreachable,
5651 .ACCESS_DENIED => return error.AccessDenied, // a sync was performed but the system couldn't update the access time
5652 .UNEXP_NET_ERR => return error.InputOutput,
5653 else => |err| return windows.unexpectedError(err),
6206 const syscall: Syscall = try .start();
6207 while (true) {
6208 if (windows.kernel32.FlushFileBuffers(file.handle) != 0) {
6209 return syscall.finish();
6210 }
6211 switch (windows.GetLastError()) {
6212 .SUCCESS => unreachable, // `FlushFileBuffers` returned nonzero
6213 .INVALID_HANDLE => unreachable,
6214 .ACCESS_DENIED => return syscall.fail(error.AccessDenied), // a sync was performed but the system couldn't update the access time
6215 .UNEXP_NET_ERR => return syscall.fail(error.InputOutput),
6216 .OPERATION_ABORTED => {
6217 try syscall.checkCancel();
6218 continue;
6219 },
6220 else => |err| {
6221 syscall.finish();
6222 return windows.unexpectedError(err);
6223 },
6224 }
56546225 }
56556226}
56566227
56576228fn fileSyncPosix(userdata: ?*anyopaque, file: File) File.SyncError!void {
56586229 const t: *Threaded = @ptrCast(@alignCast(userdata));
5659 const current_thread = Thread.getCurrent(t);
5660 try current_thread.beginSyscall();
6230 _ = t;
6231 const syscall: Syscall = try .start();
56616232 while (true) {
56626233 switch (posix.errno(posix.system.fsync(file.handle))) {
5663 .SUCCESS => return current_thread.endSyscall(),
6234 .SUCCESS => return syscall.finish(),
56646235 .INTR => {
5665 try current_thread.checkCancel();
6236 try syscall.checkCancel();
56666237 continue;
56676238 },
56686239 else => |e| {
5669 current_thread.endSyscall();
6240 syscall.finish();
56706241 switch (e) {
56716242 .BADF => |err| return errnoBug(err),
56726243 .INVAL => |err| return errnoBug(err),
......@@ -5683,17 +6254,17 @@ fn fileSyncPosix(userdata: ?*anyopaque, file: File) File.SyncError!void {
56836254
56846255fn fileSyncWasi(userdata: ?*anyopaque, file: File) File.SyncError!void {
56856256 const t: *Threaded = @ptrCast(@alignCast(userdata));
5686 const current_thread = Thread.getCurrent(t);
5687 try current_thread.beginSyscall();
6257 _ = t;
6258 const syscall: Syscall = try .start();
56886259 while (true) {
56896260 switch (std.os.wasi.fd_sync(file.handle)) {
5690 .SUCCESS => return current_thread.endSyscall(),
6261 .SUCCESS => return syscall.finish(),
56916262 .INTR => {
5692 try current_thread.checkCancel();
6263 try syscall.checkCancel();
56936264 continue;
56946265 },
56956266 else => |e| {
5696 current_thread.endSyscall();
6267 syscall.finish();
56976268 switch (e) {
56986269 .BADF => |err| return errnoBug(err),
56996270 .INVAL => |err| return errnoBug(err),
......@@ -5710,33 +6281,46 @@ fn fileSyncWasi(userdata: ?*anyopaque, file: File) File.SyncError!void {
57106281
57116282fn fileIsTty(userdata: ?*anyopaque, file: File) Io.Cancelable!bool {
57126283 const t: *Threaded = @ptrCast(@alignCast(userdata));
5713 const current_thread = Thread.getCurrent(t);
5714 return isTty(current_thread, file);
6284 _ = t;
6285 return isTty(file);
57156286}
57166287
5717fn isTty(current_thread: *Thread, file: File) Io.Cancelable!bool {
6288fn isTty(file: File) Io.Cancelable!bool {
57186289 if (is_windows) {
5719 if (try isCygwinPty(current_thread, file)) return true;
5720 try current_thread.checkCancel();
6290 if (try isCygwinPty(file)) return true;
57216291 var out: windows.DWORD = undefined;
5722 return windows.kernel32.GetConsoleMode(file.handle, &out) != 0;
6292 const syscall: Syscall = try .start();
6293 while (windows.kernel32.GetConsoleMode(file.handle, &out) == 0) {
6294 switch (windows.GetLastError()) {
6295 .OPERATION_ABORTED => {
6296 try syscall.checkCancel();
6297 continue;
6298 },
6299 else => {
6300 syscall.finish();
6301 return false;
6302 },
6303 }
6304 }
6305 syscall.finish();
6306 return true;
57236307 }
57246308
57256309 if (builtin.link_libc) {
5726 try current_thread.beginSyscall();
6310 const syscall: Syscall = try .start();
57276311 while (true) {
57286312 const rc = posix.system.isatty(file.handle);
57296313 switch (posix.errno(rc - 1)) {
57306314 .SUCCESS => {
5731 current_thread.endSyscall();
6315 syscall.finish();
57326316 return true;
57336317 },
57346318 .INTR => {
5735 try current_thread.checkCancel();
6319 try syscall.checkCancel();
57366320 continue;
57376321 },
57386322 else => {
5739 current_thread.endSyscall();
6323 syscall.finish();
57406324 return false;
57416325 },
57426326 }
......@@ -5760,22 +6344,22 @@ fn isTty(current_thread: *Thread, file: File) Io.Cancelable!bool {
57606344
57616345 if (native_os == .linux) {
57626346 const linux = std.os.linux;
5763 try current_thread.beginSyscall();
6347 const syscall: Syscall = try .start();
57646348 while (true) {
57656349 var wsz: posix.winsize = undefined;
57666350 const fd: usize = @bitCast(@as(isize, file.handle));
57676351 const rc = linux.syscall3(.ioctl, fd, linux.T.IOCGWINSZ, @intFromPtr(&wsz));
57686352 switch (linux.errno(rc)) {
57696353 .SUCCESS => {
5770 current_thread.endSyscall();
6354 syscall.finish();
57716355 return true;
57726356 },
57736357 .INTR => {
5774 try current_thread.checkCancel();
6358 try syscall.checkCancel();
57756359 continue;
57766360 },
57776361 else => {
5778 current_thread.endSyscall();
6362 syscall.finish();
57796363 return false;
57806364 },
57816365 }
......@@ -5787,53 +6371,99 @@ fn isTty(current_thread: *Thread, file: File) Io.Cancelable!bool {
57876371
57886372fn fileEnableAnsiEscapeCodes(userdata: ?*anyopaque, file: File) File.EnableAnsiEscapeCodesError!void {
57896373 const t: *Threaded = @ptrCast(@alignCast(userdata));
5790 const current_thread = Thread.getCurrent(t);
6374 _ = t;
57916375
5792 if (is_windows) {
5793 try current_thread.checkCancel();
6376 if (!is_windows) {
6377 if (try supportsAnsiEscapeCodes(file)) return;
6378 return error.NotTerminalDevice;
6379 }
57946380
5795 // For Windows Terminal, VT Sequences processing is enabled by default.
5796 var original_console_mode: windows.DWORD = 0;
5797 if (windows.kernel32.GetConsoleMode(file.handle, &original_console_mode) != 0) {
5798 if (original_console_mode & windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING != 0) return;
6381 // For Windows Terminal, VT Sequences processing is enabled by default.
6382 var original_console_mode: windows.DWORD = 0;
57996383
5800 // For Windows Console, VT Sequences processing support was added in Windows 10 build 14361, but disabled by default.
5801 // https://devblogs.microsoft.com/commandline/tmux-support-arrives-for-bash-on-ubuntu-on-windows/
5802 //
5803 // Note: In Microsoft's example for enabling virtual terminal processing, it
5804 // shows attempting to enable `DISABLE_NEWLINE_AUTO_RETURN` as well:
5805 // https://learn.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences#example-of-enabling-virtual-terminal-processing
5806 // This is avoided because in the old Windows Console, that flag causes \n (as opposed to \r\n)
5807 // to behave unexpectedly (the cursor moves down 1 row but remains on the same column).
5808 // Additionally, the default console mode in Windows Terminal does not have
5809 // `DISABLE_NEWLINE_AUTO_RETURN` set, so by only enabling `ENABLE_VIRTUAL_TERMINAL_PROCESSING`
5810 // we end up matching the mode of Windows Terminal.
5811 const requested_console_modes = windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING;
5812 const console_mode = original_console_mode | requested_console_modes;
5813 try current_thread.checkCancel();
5814 if (windows.kernel32.SetConsoleMode(file.handle, console_mode) != 0) return;
5815 }
5816 if (try isCygwinPty(current_thread, file)) return;
5817 } else {
5818 if (try supportsAnsiEscapeCodes(current_thread, file)) return;
6384 {
6385 const syscall: Syscall = try .start();
6386 while (windows.kernel32.GetConsoleMode(file.handle, &original_console_mode) == 0) {
6387 switch (windows.GetLastError()) {
6388 .OPERATION_ABORTED => {
6389 try syscall.checkCancel();
6390 continue;
6391 },
6392 else => {
6393 syscall.finish();
6394 if (try isCygwinPty(file)) return;
6395 return error.NotTerminalDevice;
6396 },
6397 }
6398 }
6399 syscall.finish();
6400 }
6401
6402 if (original_console_mode & windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING != 0) return;
6403
6404 // For Windows Console, VT Sequences processing support was added in Windows 10 build 14361, but disabled by default.
6405 // https://devblogs.microsoft.com/commandline/tmux-support-arrives-for-bash-on-ubuntu-on-windows/
6406 //
6407 // Note: In Microsoft's example for enabling virtual terminal processing, it
6408 // shows attempting to enable `DISABLE_NEWLINE_AUTO_RETURN` as well:
6409 // https://learn.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences#example-of-enabling-virtual-terminal-processing
6410 // This is avoided because in the old Windows Console, that flag causes \n (as opposed to \r\n)
6411 // to behave unexpectedly (the cursor moves down 1 row but remains on the same column).
6412 // Additionally, the default console mode in Windows Terminal does not have
6413 // `DISABLE_NEWLINE_AUTO_RETURN` set, so by only enabling `ENABLE_VIRTUAL_TERMINAL_PROCESSING`
6414 // we end up matching the mode of Windows Terminal.
6415 const requested_console_modes = windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING;
6416 const console_mode = original_console_mode | requested_console_modes;
6417
6418 {
6419 const syscall: Syscall = try .start();
6420 while (windows.kernel32.SetConsoleMode(file.handle, console_mode) == 0) {
6421 switch (windows.GetLastError()) {
6422 .OPERATION_ABORTED => {
6423 try syscall.checkCancel();
6424 continue;
6425 },
6426 else => {
6427 syscall.finish();
6428 if (try isCygwinPty(file)) return;
6429 return error.NotTerminalDevice;
6430 },
6431 }
6432 }
6433 syscall.finish();
58196434 }
5820 return error.NotTerminalDevice;
58216435}
58226436
58236437fn fileSupportsAnsiEscapeCodes(userdata: ?*anyopaque, file: File) Io.Cancelable!bool {
58246438 const t: *Threaded = @ptrCast(@alignCast(userdata));
5825 const current_thread = Thread.getCurrent(t);
5826 return supportsAnsiEscapeCodes(current_thread, file);
6439 _ = t;
6440 return supportsAnsiEscapeCodes(file);
58276441}
58286442
5829fn supportsAnsiEscapeCodes(current_thread: *Thread, file: File) Io.Cancelable!bool {
6443fn supportsAnsiEscapeCodes(file: File) Io.Cancelable!bool {
58306444 if (is_windows) {
5831 try current_thread.checkCancel();
58326445 var console_mode: windows.DWORD = 0;
5833 if (windows.kernel32.GetConsoleMode(file.handle, &console_mode) != 0) {
5834 if (console_mode & windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING != 0) return true;
6446
6447 const syscall: Syscall = try .start();
6448 while (windows.kernel32.GetConsoleMode(file.handle, &console_mode) == 0) {
6449 switch (windows.GetLastError()) {
6450 .OPERATION_ABORTED => {
6451 try syscall.checkCancel();
6452 continue;
6453 },
6454 else => {
6455 syscall.finish();
6456 break;
6457 },
6458 }
6459 } else {
6460 syscall.finish();
6461 if (console_mode & windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING != 0) {
6462 return true;
6463 }
58356464 }
5836 return isCygwinPty(current_thread, file);
6465
6466 return isCygwinPty(file);
58376467 }
58386468
58396469 if (native_os == .wasi) {
......@@ -5843,12 +6473,12 @@ fn supportsAnsiEscapeCodes(current_thread: *Thread, file: File) Io.Cancelable!bo
58436473 return false;
58446474 }
58456475
5846 if (try isTty(current_thread, file)) return true;
6476 if (try isTty(file)) return true;
58476477
58486478 return false;
58496479}
58506480
5851fn isCygwinPty(current_thread: *Thread, file: File) Io.Cancelable!bool {
6481fn isCygwinPty(file: File) Io.Cancelable!bool {
58526482 if (!is_windows) return false;
58536483
58546484 const handle = file.handle;
......@@ -5863,20 +6493,26 @@ fn isCygwinPty(current_thread: *Thread, file: File) Io.Cancelable!bool {
58636493 // This allows us to avoid the more costly NtQueryInformationFile call
58646494 // for handles that aren't named pipes.
58656495 {
5866 try current_thread.checkCancel();
58676496 var io_status: windows.IO_STATUS_BLOCK = undefined;
58686497 var device_info: windows.FILE.FS_DEVICE_INFORMATION = undefined;
5869 const rc = windows.ntdll.NtQueryVolumeInformationFile(
6498 const syscall: Syscall = try .start();
6499 while (true) switch (windows.ntdll.NtQueryVolumeInformationFile(
58706500 handle,
5871 &io_status,
5872 &device_info,
5873 @sizeOf(windows.FILE.FS_DEVICE_INFORMATION),
5874 .Device,
5875 );
5876 switch (rc) {
5877 .SUCCESS => {},
5878 else => return false,
5879 }
6501 &io_status,
6502 &device_info,
6503 @sizeOf(windows.FILE.FS_DEVICE_INFORMATION),
6504 .Device,
6505 )) {
6506 .SUCCESS => break syscall.finish(),
6507 .CANCELLED => {
6508 try syscall.checkCancel();
6509 continue;
6510 },
6511 else => {
6512 syscall.finish();
6513 return false;
6514 },
6515 };
58806516 if (device_info.DeviceType.FileDevice != .NAMED_PIPE) return false;
58816517 }
58826518
......@@ -5891,19 +6527,25 @@ fn isCygwinPty(current_thread: *Thread, file: File) Io.Cancelable!bool {
58916527 var name_info_bytes align(@alignOf(windows.FILE.NAME_INFORMATION)) = [_]u8{0} ** (name_bytes_offset + num_name_bytes);
58926528
58936529 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
5894 try current_thread.checkCancel();
5895 const rc = windows.ntdll.NtQueryInformationFile(
6530 const syscall: Syscall = try .start();
6531 while (true) switch (windows.ntdll.NtQueryInformationFile(
58966532 handle,
58976533 &io_status_block,
58986534 &name_info_bytes,
58996535 @intCast(name_info_bytes.len),
59006536 .Name,
5901 );
5902 switch (rc) {
5903 .SUCCESS => {},
6537 )) {
6538 .SUCCESS => break syscall.finish(),
6539 .CANCELLED => {
6540 try syscall.checkCancel();
6541 continue;
6542 },
59046543 .INVALID_PARAMETER => unreachable,
5905 else => return false,
5906 }
6544 else => {
6545 syscall.finish();
6546 return false;
6547 },
6548 };
59076549
59086550 const name_info: *const windows.FILE_NAME_INFO = @ptrCast(&name_info_bytes);
59096551 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 {
59166558
59176559fn fileSetLength(userdata: ?*anyopaque, file: File, length: u64) File.SetLengthError!void {
59186560 const t: *Threaded = @ptrCast(@alignCast(userdata));
5919 const current_thread = Thread.getCurrent(t);
6561 _ = t;
59206562
59216563 const signed_len: i64 = @bitCast(length);
59226564 if (signed_len < 0) return error.FileTooBig; // Avoid ambiguous EINVAL errors.
59236565
59246566 if (is_windows) {
5925 try current_thread.checkCancel();
5926
59276567 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
59286568 const eof_info: windows.FILE.END_OF_FILE_INFORMATION = .{
59296569 .EndOfFile = signed_len,
59306570 };
59316571
5932 const status = windows.ntdll.NtSetInformationFile(
6572 const syscall: Syscall = try .start();
6573 while (true) switch (windows.ntdll.NtSetInformationFile(
59336574 file.handle,
59346575 &io_status_block,
59356576 &eof_info,
59366577 @sizeOf(windows.FILE.END_OF_FILE_INFORMATION),
59376578 .EndOfFile,
5938 );
5939 switch (status) {
5940 .SUCCESS => return,
5941 .INVALID_HANDLE => |err| return windows.statusBug(err), // Handle not open for writing.
5942 .ACCESS_DENIED => return error.AccessDenied,
5943 .USER_MAPPED_FILE => return error.AccessDenied,
5944 .INVALID_PARAMETER => return error.FileTooBig,
5945 else => return windows.unexpectedStatus(status),
5946 }
6579 )) {
6580 .SUCCESS => return syscall.finish(),
6581 .CANCELLED => {
6582 try syscall.checkCancel();
6583 continue;
6584 },
6585 .INVALID_HANDLE => |err| return syscall.ntstatusBug(err), // Handle not open for writing.
6586 .ACCESS_DENIED => return syscall.fail(error.AccessDenied),
6587 .USER_MAPPED_FILE => return syscall.fail(error.AccessDenied),
6588 .INVALID_PARAMETER => return syscall.fail(error.FileTooBig),
6589 else => |status| return syscall.unexpectedNtstatus(status),
6590 };
59476591 }
59486592
59496593 if (native_os == .wasi and !builtin.link_libc) {
5950 try current_thread.beginSyscall();
6594 const syscall: Syscall = try .start();
59516595 while (true) {
59526596 switch (std.os.wasi.fd_filestat_set_size(file.handle, length)) {
5953 .SUCCESS => return current_thread.endSyscall(),
6597 .SUCCESS => return syscall.finish(),
59546598 .INTR => {
5955 try current_thread.checkCancel();
6599 try syscall.checkCancel();
59566600 continue;
59576601 },
59586602 else => |e| {
5959 current_thread.endSyscall();
6603 syscall.finish();
59606604 switch (e) {
59616605 .FBIG => return error.FileTooBig,
59626606 .IO => return error.InputOutput,
......@@ -5972,16 +6616,16 @@ fn fileSetLength(userdata: ?*anyopaque, file: File, length: u64) File.SetLengthE
59726616 }
59736617 }
59746618
5975 try current_thread.beginSyscall();
6619 const syscall: Syscall = try .start();
59766620 while (true) {
59776621 switch (posix.errno(ftruncate_sym(file.handle, signed_len))) {
5978 .SUCCESS => return current_thread.endSyscall(),
6622 .SUCCESS => return syscall.finish(),
59796623 .INTR => {
5980 try current_thread.checkCancel();
6624 try syscall.checkCancel();
59816625 continue;
59826626 },
59836627 else => |e| {
5984 current_thread.endSyscall();
6628 syscall.finish();
59856629 switch (e) {
59866630 .FBIG => return error.FileTooBig,
59876631 .IO => return error.InputOutput,
......@@ -5999,19 +6643,18 @@ fn fileSetLength(userdata: ?*anyopaque, file: File, length: u64) File.SetLengthE
59996643fn fileSetOwner(userdata: ?*anyopaque, file: File, owner: ?File.Uid, group: ?File.Gid) File.SetOwnerError!void {
60006644 if (!have_fchown) return error.Unexpected; // Unsupported OS, don't call this function.
60016645 const t: *Threaded = @ptrCast(@alignCast(userdata));
6002 const current_thread = Thread.getCurrent(t);
6646 _ = t;
60036647 const uid = owner orelse ~@as(posix.uid_t, 0);
60046648 const gid = group orelse ~@as(posix.gid_t, 0);
6005 return posixFchown(current_thread, file.handle, uid, gid);
6649 return posixFchown(file.handle, uid, gid);
60066650}
60076651
60086652fn fileSetPermissions(userdata: ?*anyopaque, file: File, permissions: File.Permissions) File.SetPermissionsError!void {
60096653 if (@sizeOf(File.Permissions) == 0) return;
60106654 const t: *Threaded = @ptrCast(@alignCast(userdata));
6011 const current_thread = Thread.getCurrent(t);
6655 _ = t;
60126656 switch (native_os) {
60136657 .windows => {
6014 try current_thread.checkCancel();
60156658 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
60166659 const info: windows.FILE.BASIC_INFORMATION = .{
60176660 .CreationTime = 0,
......@@ -6020,37 +6663,41 @@ fn fileSetPermissions(userdata: ?*anyopaque, file: File, permissions: File.Permi
60206663 .ChangeTime = 0,
60216664 .FileAttributes = permissions.toAttributes(),
60226665 };
6023 const status = windows.ntdll.NtSetInformationFile(
6666 const syscall: Syscall = try .start();
6667 while (true) switch (windows.ntdll.NtSetInformationFile(
60246668 file.handle,
60256669 &io_status_block,
60266670 &info,
60276671 @sizeOf(windows.FILE.BASIC_INFORMATION),
60286672 .Basic,
6029 );
6030 switch (status) {
6031 .SUCCESS => return,
6032 .INVALID_HANDLE => |err| return windows.statusBug(err),
6033 .ACCESS_DENIED => return error.AccessDenied,
6034 else => return windows.unexpectedStatus(status),
6035 }
6673 )) {
6674 .SUCCESS => return syscall.finish(),
6675 .CANCELLED => {
6676 try syscall.checkCancel();
6677 continue;
6678 },
6679 .INVALID_HANDLE => |err| return syscall.ntstatusBug(err),
6680 .ACCESS_DENIED => return syscall.fail(error.AccessDenied),
6681 else => |status| return syscall.unexpectedNtstatus(status),
6682 };
60366683 },
60376684 .wasi => return error.Unexpected, // Unsupported OS.
6038 else => return setPermissionsPosix(current_thread, file.handle, permissions.toMode()),
6685 else => return setPermissionsPosix(file.handle, permissions.toMode()),
60396686 }
60406687}
60416688
6042fn setPermissionsPosix(current_thread: *Thread, fd: posix.fd_t, mode: posix.mode_t) File.SetPermissionsError!void {
6689fn setPermissionsPosix(fd: posix.fd_t, mode: posix.mode_t) File.SetPermissionsError!void {
60436690 comptime assert(have_fchmod);
6044 try current_thread.beginSyscall();
6691 const syscall: Syscall = try .start();
60456692 while (true) {
60466693 switch (posix.errno(posix.system.fchmod(fd, mode))) {
6047 .SUCCESS => return current_thread.endSyscall(),
6694 .SUCCESS => return syscall.finish(),
60486695 .INTR => {
6049 try current_thread.checkCancel();
6696 try syscall.checkCancel();
60506697 continue;
60516698 },
60526699 else => |e| {
6053 current_thread.endSyscall();
6700 syscall.finish();
60546701 switch (e) {
60556702 .BADF => |err| return errnoBug(err),
60566703 .FAULT => |err| return errnoBug(err),
......@@ -6077,7 +6724,7 @@ fn dirSetTimestamps(
60776724 options: Dir.SetTimestampsOptions,
60786725) Dir.SetTimestampsError!void {
60796726 const t: *Threaded = @ptrCast(@alignCast(userdata));
6080 const current_thread = Thread.getCurrent(t);
6727 _ = t;
60816728
60826729 if (is_windows) {
60836730 @panic("TODO implement dirSetTimestamps windows");
......@@ -6101,20 +6748,20 @@ fn dirSetTimestamps(
61016748 var path_buffer: [posix.PATH_MAX]u8 = undefined;
61026749 const sub_path_posix = try pathToPosix(sub_path, &path_buffer);
61036750
6104 try current_thread.beginSyscall();
6751 const syscall: Syscall = try .start();
61056752 while (true) switch (posix.errno(posix.system.utimensat(dir.handle, sub_path_posix, times, flags))) {
6106 .SUCCESS => return current_thread.endSyscall(),
6753 .SUCCESS => return syscall.finish(),
61076754 .INTR => {
6108 try current_thread.checkCancel();
6755 try syscall.checkCancel();
61096756 continue;
61106757 },
6111 .BADF => |err| return current_thread.endSyscallErrnoBug(err), // always a race condition
6112 .FAULT => |err| return current_thread.endSyscallErrnoBug(err),
6113 .INVAL => |err| return current_thread.endSyscallErrnoBug(err),
6114 .ACCES => return current_thread.endSyscallError(error.AccessDenied),
6115 .PERM => return current_thread.endSyscallError(error.PermissionDenied),
6116 .ROFS => return current_thread.endSyscallError(error.ReadOnlyFileSystem),
6117 else => |err| return current_thread.endSyscallUnexpectedErrno(err),
6758 .BADF => |err| return syscall.errnoBug(err), // always a race condition
6759 .FAULT => |err| return syscall.errnoBug(err),
6760 .INVAL => |err| return syscall.errnoBug(err),
6761 .ACCES => return syscall.fail(error.AccessDenied),
6762 .PERM => return syscall.fail(error.PermissionDenied),
6763 .ROFS => return syscall.fail(error.ReadOnlyFileSystem),
6764 else => |err| return syscall.unexpectedErrno(err),
61186765 };
61196766}
61206767
......@@ -6124,11 +6771,9 @@ fn fileSetTimestamps(
61246771 options: File.SetTimestampsOptions,
61256772) File.SetTimestampsError!void {
61266773 const t: *Threaded = @ptrCast(@alignCast(userdata));
6127 const current_thread = Thread.getCurrent(t);
6774 _ = t;
61286775
61296776 if (is_windows) {
6130 try current_thread.checkCancel();
6131
61326777 var access_time_buffer: windows.FILETIME = undefined;
61336778 var modify_time_buffer: windows.FILETIME = undefined;
61346779 var system_time_buffer: windows.LARGE_INTEGER = undefined;
......@@ -6156,13 +6801,22 @@ fn fileSetTimestamps(
61566801 };
61576802
61586803 // https://github.com/ziglang/zig/issues/1840
6159 const rc = windows.kernel32.SetFileTime(file.handle, null, access_ptr, modify_ptr);
6160 if (rc == 0) {
6161 switch (windows.GetLastError()) {
6162 else => |err| return windows.unexpectedError(err),
6804 const syscall: Syscall = try .start();
6805 while (true) {
6806 switch (windows.kernel32.SetFileTime(file.handle, null, access_ptr, modify_ptr)) {
6807 0 => switch (windows.GetLastError()) {
6808 .OPERATION_ABORTED => {
6809 try syscall.checkCancel();
6810 continue;
6811 },
6812 else => |err| {
6813 syscall.finish();
6814 return windows.unexpectedError(err);
6815 },
6816 },
6817 else => return syscall.finish(),
61636818 }
61646819 }
6165 return;
61666820 }
61676821
61686822 if (native_os == .wasi and !builtin.link_libc) {
......@@ -6188,20 +6842,20 @@ fn fileSetTimestamps(
61886842 },
61896843 }
61906844
6191 try current_thread.beginSyscall();
6845 const syscall: Syscall = try .start();
61926846 while (true) switch (std.os.wasi.fd_filestat_set_times(file.handle, atime, mtime, flags)) {
6193 .SUCCESS => return current_thread.endSyscall(),
6847 .SUCCESS => return syscall.finish(),
61946848 .INTR => {
6195 try current_thread.checkCancel();
6849 try syscall.checkCancel();
61966850 continue;
61976851 },
6198 .BADF => |err| return current_thread.endSyscallErrnoBug(err), // File descriptor use-after-free.
6199 .FAULT => |err| return current_thread.endSyscallErrnoBug(err),
6200 .INVAL => |err| return current_thread.endSyscallErrnoBug(err),
6201 .ACCES => return current_thread.endSyscallError(error.AccessDenied),
6202 .PERM => return current_thread.endSyscallError(error.PermissionDenied),
6203 .ROFS => return current_thread.endSyscallError(error.ReadOnlyFileSystem),
6204 else => |err| return current_thread.endSyscallUnexpectedErrno(err),
6852 .BADF => |err| return syscall.errnoBug(err), // File descriptor use-after-free.
6853 .FAULT => |err| return syscall.errnoBug(err),
6854 .INVAL => |err| return syscall.errnoBug(err),
6855 .ACCES => return syscall.fail(error.AccessDenied),
6856 .PERM => return syscall.fail(error.PermissionDenied),
6857 .ROFS => return syscall.fail(error.ReadOnlyFileSystem),
6858 else => |err| return syscall.unexpectedErrno(err),
62056859 };
62066860 }
62076861
......@@ -6214,20 +6868,20 @@ fn fileSetTimestamps(
62146868 break :p &times_buffer;
62156869 };
62166870
6217 try current_thread.beginSyscall();
6871 const syscall: Syscall = try .start();
62186872 while (true) switch (posix.errno(posix.system.futimens(file.handle, times))) {
6219 .SUCCESS => return current_thread.endSyscall(),
6873 .SUCCESS => return syscall.finish(),
62206874 .INTR => {
6221 try current_thread.checkCancel();
6875 try syscall.checkCancel();
62226876 continue;
62236877 },
6224 .BADF => |err| return current_thread.endSyscallErrnoBug(err), // always a race condition
6225 .FAULT => |err| return current_thread.endSyscallErrnoBug(err),
6226 .INVAL => |err| return current_thread.endSyscallErrnoBug(err),
6227 .ACCES => return current_thread.endSyscallError(error.AccessDenied),
6228 .PERM => return current_thread.endSyscallError(error.PermissionDenied),
6229 .ROFS => return current_thread.endSyscallError(error.ReadOnlyFileSystem),
6230 else => |err| return current_thread.endSyscallUnexpectedErrno(err),
6878 .BADF => |err| return syscall.errnoBug(err), // always a race condition
6879 .FAULT => |err| return syscall.errnoBug(err),
6880 .INVAL => |err| return syscall.errnoBug(err),
6881 .ACCES => return syscall.fail(error.AccessDenied),
6882 .PERM => return syscall.fail(error.PermissionDenied),
6883 .ROFS => return syscall.fail(error.ReadOnlyFileSystem),
6884 else => |err| return syscall.unexpectedErrno(err),
62316885 };
62326886}
62336887
......@@ -6237,34 +6891,33 @@ const windows_lock_range_len: windows.LARGE_INTEGER = 1;
62376891fn fileLock(userdata: ?*anyopaque, file: File, lock: File.Lock) File.LockError!void {
62386892 if (native_os == .wasi) return error.FileLocksUnsupported;
62396893 const t: *Threaded = @ptrCast(@alignCast(userdata));
6240 const current_thread = Thread.getCurrent(t);
6894 _ = t;
62416895
62426896 if (is_windows) {
62436897 const exclusive = switch (lock) {
62446898 .none => {
62456899 // To match the non-Windows behavior, unlock
62466900 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
6247 const status = windows.ntdll.NtUnlockFile(
6901 while (true) switch (windows.ntdll.NtUnlockFile(
62486902 file.handle,
62496903 &io_status_block,
62506904 &windows_lock_range_off,
62516905 &windows_lock_range_len,
62526906 0,
6253 );
6254 switch (status) {
6255 .SUCCESS => {},
6256 .RANGE_NOT_LOCKED => {},
6907 )) {
6908 .SUCCESS => return,
6909 .CANCELLED => continue,
6910 .RANGE_NOT_LOCKED => return,
62576911 .ACCESS_VIOLATION => |err| return windows.statusBug(err), // bad io_status_block pointer
6258 else => return windows.unexpectedStatus(status),
6259 }
6260 return;
6912 else => |status| return windows.unexpectedStatus(status),
6913 };
62616914 },
62626915 .shared => false,
62636916 .exclusive => true,
62646917 };
6265 try current_thread.checkCancel();
62666918 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
6267 const status = windows.ntdll.NtLockFile(
6919 const syscall: Syscall = try .start();
6920 while (true) switch (windows.ntdll.NtLockFile(
62686921 file.handle,
62696922 null,
62706923 null,
......@@ -6275,14 +6928,17 @@ fn fileLock(userdata: ?*anyopaque, file: File, lock: File.Lock) File.LockError!v
62756928 null,
62766929 windows.FALSE,
62776930 @intFromBool(exclusive),
6278 );
6279 switch (status) {
6280 .SUCCESS => return,
6281 .INSUFFICIENT_RESOURCES => return error.SystemResources,
6282 .LOCK_NOT_GRANTED => |err| return windows.statusBug(err), // passed FailImmediately=false
6283 .ACCESS_VIOLATION => |err| return windows.statusBug(err), // bad io_status_block pointer
6284 else => return windows.unexpectedStatus(status),
6285 }
6931 )) {
6932 .SUCCESS => return syscall.finish(),
6933 .CANCELLED => {
6934 try syscall.checkCancel();
6935 continue;
6936 },
6937 .INSUFFICIENT_RESOURCES => return syscall.fail(error.SystemResources),
6938 .LOCK_NOT_GRANTED => |err| return syscall.ntstatusBug(err), // passed FailImmediately=false
6939 .ACCESS_VIOLATION => |err| return syscall.ntstatusBug(err), // bad io_status_block pointer
6940 else => |status| return syscall.unexpectedNtstatus(status),
6941 };
62866942 }
62876943
62886944 const operation: i32 = switch (lock) {
......@@ -6290,16 +6946,16 @@ fn fileLock(userdata: ?*anyopaque, file: File, lock: File.Lock) File.LockError!v
62906946 .shared => posix.LOCK.SH,
62916947 .exclusive => posix.LOCK.EX,
62926948 };
6293 try current_thread.beginSyscall();
6949 const syscall: Syscall = try .start();
62946950 while (true) {
62956951 switch (posix.errno(posix.system.flock(file.handle, operation))) {
6296 .SUCCESS => return current_thread.endSyscall(),
6952 .SUCCESS => return syscall.finish(),
62976953 .INTR => {
6298 try current_thread.checkCancel();
6954 try syscall.checkCancel();
62996955 continue;
63006956 },
63016957 else => |e| {
6302 current_thread.endSyscall();
6958 syscall.finish();
63036959 switch (e) {
63046960 .BADF => |err| return errnoBug(err),
63056961 .INVAL => |err| return errnoBug(err), // invalid parameters
......@@ -6316,33 +6972,33 @@ fn fileLock(userdata: ?*anyopaque, file: File, lock: File.Lock) File.LockError!v
63166972fn fileTryLock(userdata: ?*anyopaque, file: File, lock: File.Lock) File.LockError!bool {
63176973 if (native_os == .wasi) return error.FileLocksUnsupported;
63186974 const t: *Threaded = @ptrCast(@alignCast(userdata));
6319 const current_thread = Thread.getCurrent(t);
6975 _ = t;
63206976
63216977 if (is_windows) {
63226978 const exclusive = switch (lock) {
63236979 .none => {
63246980 // To match the non-Windows behavior, unlock
63256981 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
6326 const status = windows.ntdll.NtUnlockFile(
6982 while (true) switch (windows.ntdll.NtUnlockFile(
63276983 file.handle,
63286984 &io_status_block,
63296985 &windows_lock_range_off,
63306986 &windows_lock_range_len,
63316987 0,
6332 );
6333 switch (status) {
6988 )) {
63346989 .SUCCESS => return true,
6990 .CANCELLED => continue,
63356991 .RANGE_NOT_LOCKED => return false,
63366992 .ACCESS_VIOLATION => |err| return windows.statusBug(err), // bad io_status_block pointer
6337 else => return windows.unexpectedStatus(status),
6338 }
6993 else => |status| return windows.unexpectedStatus(status),
6994 };
63396995 },
63406996 .shared => false,
63416997 .exclusive => true,
63426998 };
6343 try current_thread.checkCancel();
63446999 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
6345 const status = windows.ntdll.NtLockFile(
7000 const syscall: Syscall = try .start();
7001 while (true) switch (windows.ntdll.NtLockFile(
63467002 file.handle,
63477003 null,
63487004 null,
......@@ -6353,14 +7009,23 @@ fn fileTryLock(userdata: ?*anyopaque, file: File, lock: File.Lock) File.LockErro
63537009 null,
63547010 windows.TRUE,
63557011 @intFromBool(exclusive),
6356 );
6357 switch (status) {
6358 .SUCCESS => return true,
6359 .INSUFFICIENT_RESOURCES => return error.SystemResources,
6360 .LOCK_NOT_GRANTED => return false,
6361 .ACCESS_VIOLATION => |err| return windows.statusBug(err), // bad io_status_block pointer
6362 else => return windows.unexpectedStatus(status),
6363 }
7012 )) {
7013 .SUCCESS => {
7014 syscall.finish();
7015 return true;
7016 },
7017 .LOCK_NOT_GRANTED => {
7018 syscall.finish();
7019 return false;
7020 },
7021 .CANCELLED => {
7022 try syscall.checkCancel();
7023 continue;
7024 },
7025 .INSUFFICIENT_RESOURCES => return syscall.fail(error.SystemResources),
7026 .ACCESS_VIOLATION => |err| return syscall.ntstatusBug(err), // bad io_status_block pointer
7027 else => |status| return syscall.unexpectedNtstatus(status),
7028 };
63647029 }
63657030
63667031 const operation: i32 = switch (lock) {
......@@ -6368,23 +7033,23 @@ fn fileTryLock(userdata: ?*anyopaque, file: File, lock: File.Lock) File.LockErro
63687033 .shared => posix.LOCK.SH | posix.LOCK.NB,
63697034 .exclusive => posix.LOCK.EX | posix.LOCK.NB,
63707035 };
6371 try current_thread.beginSyscall();
7036 const syscall: Syscall = try .start();
63727037 while (true) {
63737038 switch (posix.errno(posix.system.flock(file.handle, operation))) {
63747039 .SUCCESS => {
6375 current_thread.endSyscall();
7040 syscall.finish();
63767041 return true;
63777042 },
63787043 .INTR => {
6379 try current_thread.checkCancel();
7044 try syscall.checkCancel();
63807045 continue;
63817046 },
63827047 .AGAIN => {
6383 current_thread.endSyscall();
7048 syscall.finish();
63847049 return false;
63857050 },
63867051 else => |e| {
6387 current_thread.endSyscall();
7052 syscall.finish();
63887053 switch (e) {
63897054 .BADF => |err| return errnoBug(err),
63907055 .INVAL => |err| return errnoBug(err), // invalid parameters
......@@ -6404,20 +7069,19 @@ fn fileUnlock(userdata: ?*anyopaque, file: File) void {
64047069
64057070 if (is_windows) {
64067071 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
6407 const status = windows.ntdll.NtUnlockFile(
7072 while (true) switch (windows.ntdll.NtUnlockFile(
64087073 file.handle,
64097074 &io_status_block,
64107075 &windows_lock_range_off,
64117076 &windows_lock_range_len,
64127077 0,
6413 );
6414 if (is_debug) switch (status) {
6415 .SUCCESS => {},
6416 .RANGE_NOT_LOCKED => unreachable, // Function asserts unlocked.
6417 .ACCESS_VIOLATION => unreachable, // bad io_status_block pointer
6418 else => unreachable, // Resource deallocation must succeed.
7078 )) {
7079 .SUCCESS => return,
7080 .CANCELLED => continue,
7081 .RANGE_NOT_LOCKED => if (is_debug) unreachable else return, // Function asserts unlocked.
7082 .ACCESS_VIOLATION => if (is_debug) unreachable else return, // bad io_status_block pointer
7083 else => if (is_debug) unreachable else return, // Resource deallocation must succeed.
64197084 };
6420 return;
64217085 }
64227086
64237087 while (true) {
......@@ -6437,17 +7101,17 @@ fn fileUnlock(userdata: ?*anyopaque, file: File) void {
64377101fn fileDowngradeLock(userdata: ?*anyopaque, file: File) File.DowngradeLockError!void {
64387102 if (native_os == .wasi) return;
64397103 const t: *Threaded = @ptrCast(@alignCast(userdata));
6440 const current_thread = Thread.getCurrent(t);
7104 _ = t;
64417105
64427106 if (is_windows) {
6443 try current_thread.checkCancel();
64447107 // On Windows it works like a semaphore + exclusivity flag. To
64457108 // implement this function, we first obtain another lock in shared
64467109 // mode. This changes the exclusivity flag, but increments the
64477110 // semaphore to 2. So we follow up with an NtUnlockFile which
64487111 // decrements the semaphore but does not modify the exclusivity flag.
64497112 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
6450 switch (windows.ntdll.NtLockFile(
7113 const syscall: Syscall = try .start();
7114 while (true) switch (windows.ntdll.NtLockFile(
64517115 file.handle,
64527116 null,
64537117 null,
......@@ -6459,43 +7123,46 @@ fn fileDowngradeLock(userdata: ?*anyopaque, file: File) File.DowngradeLockError!
64597123 windows.TRUE,
64607124 windows.FALSE,
64617125 )) {
6462 .SUCCESS => {},
6463 .INSUFFICIENT_RESOURCES => |err| return windows.statusBug(err),
6464 .LOCK_NOT_GRANTED => |err| return windows.statusBug(err), // File was not locked in exclusive mode.
6465 .ACCESS_VIOLATION => |err| return windows.statusBug(err), // bad io_status_block pointer
6466 else => |status| return windows.unexpectedStatus(status),
6467 }
6468 const status = windows.ntdll.NtUnlockFile(
7126 .SUCCESS => break syscall.finish(),
7127 .CANCELLED => {
7128 try syscall.checkCancel();
7129 continue;
7130 },
7131 .INSUFFICIENT_RESOURCES => |err| return syscall.ntstatusBug(err),
7132 .LOCK_NOT_GRANTED => |err| return syscall.ntstatusBug(err), // File was not locked in exclusive mode.
7133 .ACCESS_VIOLATION => |err| return syscall.ntstatusBug(err), // bad io_status_block pointer
7134 else => |status| return syscall.unexpectedNtstatus(status),
7135 };
7136 while (true) switch (windows.ntdll.NtUnlockFile(
64697137 file.handle,
64707138 &io_status_block,
64717139 &windows_lock_range_off,
64727140 &windows_lock_range_len,
64737141 0,
6474 );
6475 if (is_debug) switch (status) {
6476 .SUCCESS => {},
6477 .RANGE_NOT_LOCKED => unreachable, // File was not locked.
6478 .ACCESS_VIOLATION => unreachable, // bad io_status_block pointer
6479 else => unreachable, // Resource deallocation must succeed.
7142 )) {
7143 .SUCCESS => return,
7144 .CANCELLED => continue,
7145 .RANGE_NOT_LOCKED => if (is_debug) unreachable else return, // File was not locked.
7146 .ACCESS_VIOLATION => if (is_debug) unreachable else return, // bad io_status_block pointer
7147 else => if (is_debug) unreachable else return, // Resource deallocation must succeed.
64807148 };
6481 return;
64827149 }
64837150
64847151 const operation = posix.LOCK.SH | posix.LOCK.NB;
64857152
6486 try current_thread.beginSyscall();
7153 const syscall: Syscall = try .start();
64877154 while (true) {
64887155 switch (posix.errno(posix.system.flock(file.handle, operation))) {
64897156 .SUCCESS => {
6490 current_thread.endSyscall();
7157 syscall.finish();
64917158 return;
64927159 },
64937160 .INTR => {
6494 try current_thread.checkCancel();
7161 try syscall.checkCancel();
64957162 continue;
64967163 },
64977164 else => |e| {
6498 current_thread.endSyscall();
7165 syscall.finish();
64997166 switch (e) {
65007167 .AGAIN => |err| return errnoBug(err), // File was not locked in exclusive mode.
65017168 .BADF => |err| return errnoBug(err),
......@@ -6517,7 +7184,7 @@ fn dirOpenDirWasi(
65177184) Dir.OpenError!Dir {
65187185 if (builtin.link_libc) return dirOpenDirPosix(userdata, dir, sub_path, options);
65197186 const t: *Threaded = @ptrCast(@alignCast(userdata));
6520 const current_thread = Thread.getCurrent(t);
7187 _ = t;
65217188 const wasi = std.os.wasi;
65227189
65237190 var base: std.os.wasi.rights_t = .{
......@@ -6547,19 +7214,19 @@ fn dirOpenDirWasi(
65477214 const oflags: wasi.oflags_t = .{ .DIRECTORY = true };
65487215 const fdflags: wasi.fdflags_t = .{};
65497216 var fd: posix.fd_t = undefined;
6550 try current_thread.beginSyscall();
7217 const syscall: Syscall = try .start();
65517218 while (true) {
65527219 switch (wasi.path_open(dir.handle, lookup_flags, sub_path.ptr, sub_path.len, oflags, base, base, fdflags, &fd)) {
65537220 .SUCCESS => {
6554 current_thread.endSyscall();
7221 syscall.finish();
65557222 return .{ .handle = fd };
65567223 },
65577224 .INTR => {
6558 try current_thread.checkCancel();
7225 try syscall.checkCancel();
65597226 continue;
65607227 },
65617228 else => |e| {
6562 current_thread.endSyscall();
7229 syscall.finish();
65637230 switch (e) {
65647231 .FAULT => |err| return errnoBug(err),
65657232 .INVAL => return error.BadPathName,
......@@ -6594,13 +7261,13 @@ fn dirHardLink(
65947261) Dir.HardLinkError!void {
65957262 if (is_windows) return error.OperationUnsupported;
65967263 const t: *Threaded = @ptrCast(@alignCast(userdata));
6597 const current_thread = Thread.getCurrent(t);
7264 _ = t;
65987265
65997266 if (native_os == .wasi and !builtin.link_libc) {
66007267 const flags: std.os.wasi.lookupflags_t = .{
66017268 .SYMLINK_FOLLOW = options.follow_symlinks,
66027269 };
6603 try current_thread.beginSyscall();
7270 const syscall: Syscall = try .start();
66047271 while (true) {
66057272 switch (std.os.wasi.path_link(
66067273 old_dir.handle,
......@@ -6611,13 +7278,13 @@ fn dirHardLink(
66117278 new_sub_path.ptr,
66127279 new_sub_path.len,
66137280 )) {
6614 .SUCCESS => return current_thread.endSyscall(),
7281 .SUCCESS => return syscall.finish(),
66157282 .INTR => {
6616 try current_thread.checkCancel();
7283 try syscall.checkCancel();
66177284 continue;
66187285 },
66197286 else => |e| {
6620 current_thread.endSyscall();
7287 syscall.finish();
66217288 switch (e) {
66227289 .ACCES => return error.AccessDenied,
66237290 .DQUOT => return error.DiskQuota,
......@@ -6651,7 +7318,7 @@ fn dirHardLink(
66517318
66527319 const flags: u32 = if (!options.follow_symlinks) posix.AT.SYMLINK_NOFOLLOW else 0;
66537320
6654 try current_thread.beginSyscall();
7321 const syscall: Syscall = try .start();
66557322 while (true) {
66567323 switch (posix.errno(posix.system.linkat(
66577324 old_dir.handle,
......@@ -6660,13 +7327,13 @@ fn dirHardLink(
66607327 new_sub_path_posix,
66617328 flags,
66627329 ))) {
6663 .SUCCESS => return current_thread.endSyscall(),
7330 .SUCCESS => return syscall.finish(),
66647331 .INTR => {
6665 try current_thread.checkCancel();
7332 try syscall.checkCancel();
66667333 continue;
66677334 },
66687335 else => |e| {
6669 current_thread.endSyscall();
7336 syscall.finish();
66707337 switch (e) {
66717338 .ACCES => return error.AccessDenied,
66727339 .DQUOT => return error.DiskQuota,
......@@ -6705,7 +7372,7 @@ const fileReadStreaming = switch (native_os) {
67057372
67067373fn fileReadStreamingPosix(userdata: ?*anyopaque, file: File, data: []const []u8) File.Reader.Error!usize {
67077374 const t: *Threaded = @ptrCast(@alignCast(userdata));
6708 const current_thread = Thread.getCurrent(t);
7375 _ = t;
67097376
67107377 var iovecs_buffer: [max_iovecs_len]posix.iovec = undefined;
67117378 var i: usize = 0;
......@@ -6721,20 +7388,20 @@ fn fileReadStreamingPosix(userdata: ?*anyopaque, file: File, data: []const []u8)
67217388 assert(dest[0].len > 0);
67227389
67237390 if (native_os == .wasi and !builtin.link_libc) {
6724 try current_thread.beginSyscall();
7391 const syscall: Syscall = try .start();
67257392 while (true) {
67267393 var nread: usize = undefined;
67277394 switch (std.os.wasi.fd_read(file.handle, dest.ptr, dest.len, &nread)) {
67287395 .SUCCESS => {
6729 current_thread.endSyscall();
7396 syscall.finish();
67307397 return nread;
67317398 },
67327399 .INTR => {
6733 try current_thread.checkCancel();
7400 try syscall.checkCancel();
67347401 continue;
67357402 },
67367403 else => |e| {
6737 current_thread.endSyscall();
7404 syscall.finish();
67387405 switch (e) {
67397406 .INVAL => |err| return errnoBug(err),
67407407 .FAULT => |err| return errnoBug(err),
......@@ -6754,20 +7421,20 @@ fn fileReadStreamingPosix(userdata: ?*anyopaque, file: File, data: []const []u8)
67547421 }
67557422 }
67567423
6757 try current_thread.beginSyscall();
7424 const syscall: Syscall = try .start();
67587425 while (true) {
67597426 const rc = posix.system.readv(file.handle, dest.ptr, @intCast(dest.len));
67607427 switch (posix.errno(rc)) {
67617428 .SUCCESS => {
6762 current_thread.endSyscall();
7429 syscall.finish();
67637430 return @intCast(rc);
67647431 },
67657432 .INTR => {
6766 try current_thread.checkCancel();
7433 try syscall.checkCancel();
67677434 continue;
67687435 },
67697436 else => |e| {
6770 current_thread.endSyscall();
7437 syscall.finish();
67717438 switch (e) {
67727439 .INVAL => |err| return errnoBug(err),
67737440 .FAULT => |err| return errnoBug(err),
......@@ -6792,7 +7459,7 @@ fn fileReadStreamingPosix(userdata: ?*anyopaque, file: File, data: []const []u8)
67927459
67937460fn fileReadStreamingWindows(userdata: ?*anyopaque, file: File, data: []const []u8) File.Reader.Error!usize {
67947461 const t: *Threaded = @ptrCast(@alignCast(userdata));
6795 const current_thread = Thread.getCurrent(t);
7462 _ = t;
67967463
67977464 const DWORD = windows.DWORD;
67987465 var index: usize = 0;
......@@ -6801,28 +7468,41 @@ fn fileReadStreamingWindows(userdata: ?*anyopaque, file: File, data: []const []u
68017468 const buffer = data[index];
68027469 const want_read_count: DWORD = @min(std.math.maxInt(DWORD), buffer.len);
68037470
7471 const syscall: Syscall = try .start();
68047472 while (true) {
6805 try current_thread.checkCancel();
68067473 var n: DWORD = undefined;
6807 if (windows.kernel32.ReadFile(file.handle, buffer.ptr, want_read_count, &n, null) != 0)
7474 if (windows.kernel32.ReadFile(file.handle, buffer.ptr, want_read_count, &n, null) != 0) {
7475 syscall.finish();
68087476 return n;
7477 }
68097478 switch (windows.GetLastError()) {
6810 .IO_PENDING => |err| return windows.errorBug(err),
6811 .OPERATION_ABORTED => continue,
6812 .BROKEN_PIPE => return 0,
6813 .HANDLE_EOF => return 0,
6814 .NETNAME_DELETED => return error.ConnectionResetByPeer,
6815 .LOCK_VIOLATION => return error.LockViolation,
6816 .ACCESS_DENIED => return error.AccessDenied,
6817 .INVALID_HANDLE => return error.NotOpenForReading,
6818 else => |err| return windows.unexpectedError(err),
7479 .IO_PENDING => |err| {
7480 syscall.finish();
7481 return windows.errorBug(err);
7482 },
7483 .OPERATION_ABORTED => {
7484 try syscall.checkCancel();
7485 continue;
7486 },
7487 .BROKEN_PIPE, .HANDLE_EOF => {
7488 syscall.finish();
7489 return 0;
7490 },
7491 .NETNAME_DELETED => return syscall.fail(error.ConnectionResetByPeer),
7492 .LOCK_VIOLATION => return syscall.fail(error.LockViolation),
7493 .ACCESS_DENIED => return syscall.fail(error.AccessDenied),
7494 .INVALID_HANDLE => return syscall.fail(error.NotOpenForReading),
7495 else => |err| {
7496 syscall.finish();
7497 return windows.unexpectedError(err);
7498 },
68197499 }
68207500 }
68217501}
68227502
68237503fn fileReadPositionalPosix(userdata: ?*anyopaque, file: File, data: []const []u8, offset: u64) File.ReadPositionalError!usize {
68247504 const t: *Threaded = @ptrCast(@alignCast(userdata));
6825 const current_thread = Thread.getCurrent(t);
7505 _ = t;
68267506
68277507 if (!have_preadv) @compileError("TODO implement fileReadPositionalPosix for cursed operating systems that don't support preadv (it's only Haiku)");
68287508
......@@ -6840,20 +7520,20 @@ fn fileReadPositionalPosix(userdata: ?*anyopaque, file: File, data: []const []u8
68407520 assert(dest[0].len > 0);
68417521
68427522 if (native_os == .wasi and !builtin.link_libc) {
6843 try current_thread.beginSyscall();
7523 const syscall: Syscall = try .start();
68447524 while (true) {
68457525 var nread: usize = undefined;
68467526 switch (std.os.wasi.fd_pread(file.handle, dest.ptr, dest.len, offset, &nread)) {
68477527 .SUCCESS => {
6848 current_thread.endSyscall();
7528 syscall.finish();
68497529 return nread;
68507530 },
68517531 .INTR => {
6852 try current_thread.checkCancel();
7532 try syscall.checkCancel();
68537533 continue;
68547534 },
68557535 else => |e| {
6856 current_thread.endSyscall();
7536 syscall.finish();
68577537 switch (e) {
68587538 .INVAL => |err| return errnoBug(err),
68597539 .FAULT => |err| return errnoBug(err),
......@@ -6877,20 +7557,20 @@ fn fileReadPositionalPosix(userdata: ?*anyopaque, file: File, data: []const []u8
68777557 }
68787558 }
68797559
6880 try current_thread.beginSyscall();
7560 const syscall: Syscall = try .start();
68817561 while (true) {
68827562 const rc = preadv_sym(file.handle, dest.ptr, @intCast(dest.len), @bitCast(offset));
68837563 switch (posix.errno(rc)) {
68847564 .SUCCESS => {
6885 current_thread.endSyscall();
7565 syscall.finish();
68867566 return @bitCast(rc);
68877567 },
68887568 .INTR => {
6889 try current_thread.checkCancel();
7569 try syscall.checkCancel();
68907570 continue;
68917571 },
68927572 else => |e| {
6893 current_thread.endSyscall();
7573 syscall.finish();
68947574 switch (e) {
68957575 .INVAL => |err| return errnoBug(err),
68967576 .FAULT => |err| return errnoBug(err),
......@@ -6923,7 +7603,7 @@ const fileReadPositional = switch (native_os) {
69237603
69247604fn fileReadPositionalWindows(userdata: ?*anyopaque, file: File, data: []const []u8, offset: u64) File.ReadPositionalError!usize {
69257605 const t: *Threaded = @ptrCast(@alignCast(userdata));
6926 const current_thread = Thread.getCurrent(t);
7606 _ = t;
69277607
69287608 const DWORD = windows.DWORD;
69297609
......@@ -6945,45 +7625,58 @@ fn fileReadPositionalWindows(userdata: ?*anyopaque, file: File, data: []const []
69457625 .hEvent = null,
69467626 };
69477627
7628 const syscall: Syscall = try .start();
69487629 while (true) {
6949 try current_thread.checkCancel();
69507630 var n: DWORD = undefined;
6951 if (windows.kernel32.ReadFile(file.handle, buffer.ptr, want_read_count, &n, &overlapped) != 0)
7631 if (windows.kernel32.ReadFile(file.handle, buffer.ptr, want_read_count, &n, &overlapped) != 0) {
7632 syscall.finish();
69527633 return n;
7634 }
69537635 switch (windows.GetLastError()) {
6954 .IO_PENDING => |err| return windows.errorBug(err),
6955 .OPERATION_ABORTED => continue,
6956 .BROKEN_PIPE => return 0,
6957 .HANDLE_EOF => return 0,
6958 .NETNAME_DELETED => return error.ConnectionResetByPeer,
6959 .LOCK_VIOLATION => return error.LockViolation,
6960 .ACCESS_DENIED => return error.AccessDenied,
6961 .INVALID_HANDLE => return error.NotOpenForReading,
6962 else => |err| return windows.unexpectedError(err),
7636 .IO_PENDING => |err| {
7637 syscall.finish();
7638 return windows.errorBug(err);
7639 },
7640 .OPERATION_ABORTED => {
7641 try syscall.checkCancel();
7642 continue;
7643 },
7644 .BROKEN_PIPE, .HANDLE_EOF => {
7645 syscall.finish();
7646 return 0;
7647 },
7648 .NETNAME_DELETED => return syscall.fail(error.ConnectionResetByPeer),
7649 .LOCK_VIOLATION => return syscall.fail(error.LockViolation),
7650 .ACCESS_DENIED => return syscall.fail(error.AccessDenied),
7651 .INVALID_HANDLE => return syscall.fail(error.NotOpenForReading),
7652 else => |err| {
7653 syscall.finish();
7654 return windows.unexpectedError(err);
7655 },
69637656 }
69647657 }
69657658}
69667659
69677660fn fileSeekBy(userdata: ?*anyopaque, file: File, offset: i64) File.SeekError!void {
69687661 const t: *Threaded = @ptrCast(@alignCast(userdata));
6969 const current_thread = Thread.getCurrent(t);
7662 _ = t;
69707663 const fd = file.handle;
69717664
69727665 if (native_os == .linux and !builtin.link_libc and @sizeOf(usize) == 4) {
69737666 var result: u64 = undefined;
6974 try current_thread.beginSyscall();
7667 const syscall: Syscall = try .start();
69757668 while (true) {
69767669 switch (posix.errno(posix.system.llseek(fd, @bitCast(offset), &result, posix.SEEK.CUR))) {
69777670 .SUCCESS => {
6978 current_thread.endSyscall();
7671 syscall.finish();
69797672 return;
69807673 },
69817674 .INTR => {
6982 try current_thread.checkCancel();
7675 try syscall.checkCancel();
69837676 continue;
69847677 },
69857678 else => |e| {
6986 current_thread.endSyscall();
7679 syscall.finish();
69877680 switch (e) {
69887681 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
69897682 .INVAL => return error.Unseekable,
......@@ -6998,25 +7691,43 @@ fn fileSeekBy(userdata: ?*anyopaque, file: File, offset: i64) File.SeekError!voi
69987691 }
69997692
70007693 if (native_os == .windows) {
7001 try current_thread.checkCancel();
7002 return windows.SetFilePointerEx_CURRENT(fd, offset);
7694 const syscall: Syscall = try .start();
7695 while (true) {
7696 if (windows.kernel32.SetFilePointerEx(fd, offset, null, windows.FILE_CURRENT) != 0) {
7697 return syscall.finish();
7698 }
7699 switch (windows.GetLastError()) {
7700 .OPERATION_ABORTED => {
7701 try syscall.checkCancel();
7702 continue;
7703 },
7704 .INVALID_FUNCTION => return syscall.fail(error.Unseekable),
7705 .NEGATIVE_SEEK => return syscall.fail(error.Unseekable),
7706 .INVALID_PARAMETER => unreachable,
7707 .INVALID_HANDLE => unreachable,
7708 else => |err| {
7709 syscall.finish();
7710 return windows.unexpectedError(err);
7711 },
7712 }
7713 }
70037714 }
70047715
70057716 if (native_os == .wasi and !builtin.link_libc) {
70067717 var new_offset: std.os.wasi.filesize_t = undefined;
7007 try current_thread.beginSyscall();
7718 const syscall: Syscall = try .start();
70087719 while (true) {
70097720 switch (std.os.wasi.fd_seek(fd, offset, .CUR, &new_offset)) {
70107721 .SUCCESS => {
7011 current_thread.endSyscall();
7722 syscall.finish();
70127723 return;
70137724 },
70147725 .INTR => {
7015 try current_thread.checkCancel();
7726 try syscall.checkCancel();
70167727 continue;
70177728 },
70187729 else => |e| {
7019 current_thread.endSyscall();
7730 syscall.finish();
70207731 switch (e) {
70217732 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
70227733 .INVAL => return error.Unseekable,
......@@ -7033,19 +7744,19 @@ fn fileSeekBy(userdata: ?*anyopaque, file: File, offset: i64) File.SeekError!voi
70337744
70347745 if (posix.SEEK == void) return error.Unseekable;
70357746
7036 try current_thread.beginSyscall();
7747 const syscall: Syscall = try .start();
70377748 while (true) {
70387749 switch (posix.errno(lseek_sym(fd, offset, posix.SEEK.CUR))) {
70397750 .SUCCESS => {
7040 current_thread.endSyscall();
7751 syscall.finish();
70417752 return;
70427753 },
70437754 .INTR => {
7044 try current_thread.checkCancel();
7755 try syscall.checkCancel();
70457756 continue;
70467757 },
70477758 else => |e| {
7048 current_thread.endSyscall();
7759 syscall.finish();
70497760 switch (e) {
70507761 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
70517762 .INVAL => return error.Unseekable,
......@@ -7061,29 +7772,52 @@ fn fileSeekBy(userdata: ?*anyopaque, file: File, offset: i64) File.SeekError!voi
70617772
70627773fn fileSeekTo(userdata: ?*anyopaque, file: File, offset: u64) File.SeekError!void {
70637774 const t: *Threaded = @ptrCast(@alignCast(userdata));
7064 const current_thread = Thread.getCurrent(t);
7775 _ = t;
70657776 const fd = file.handle;
70667777
70677778 if (native_os == .windows) {
7068 try current_thread.checkCancel();
7069 return windows.SetFilePointerEx_BEGIN(fd, offset);
7779 // "The starting point is zero or the beginning of the file. If [FILE_BEGIN]
7780 // is specified, then the liDistanceToMove parameter is interpreted as an unsigned value."
7781 // https://docs.microsoft.com/en-us/windows/desktop/api/fileapi/nf-fileapi-setfilepointerex
7782 const ipos: windows.LARGE_INTEGER = @bitCast(offset);
7783
7784 const syscall: Syscall = try .start();
7785 while (true) {
7786 if (windows.kernel32.SetFilePointerEx(fd, ipos, null, windows.FILE_BEGIN) != 0) {
7787 return syscall.finish();
7788 }
7789 switch (windows.GetLastError()) {
7790 .OPERATION_ABORTED => {
7791 try syscall.checkCancel();
7792 continue;
7793 },
7794 .INVALID_FUNCTION => return syscall.fail(error.Unseekable),
7795 .NEGATIVE_SEEK => return syscall.fail(error.Unseekable),
7796 .INVALID_PARAMETER => unreachable,
7797 .INVALID_HANDLE => unreachable,
7798 else => |err| {
7799 syscall.finish();
7800 return windows.unexpectedError(err);
7801 },
7802 }
7803 }
70707804 }
70717805
70727806 if (native_os == .wasi and !builtin.link_libc) {
7073 try current_thread.beginSyscall();
7807 const syscall: Syscall = try .start();
70747808 while (true) {
70757809 var new_offset: std.os.wasi.filesize_t = undefined;
70767810 switch (std.os.wasi.fd_seek(fd, @bitCast(offset), .SET, &new_offset)) {
70777811 .SUCCESS => {
7078 current_thread.endSyscall();
7812 syscall.finish();
70797813 return;
70807814 },
70817815 .INTR => {
7082 try current_thread.checkCancel();
7816 try syscall.checkCancel();
70837817 continue;
70847818 },
70857819 else => |e| {
7086 current_thread.endSyscall();
7820 syscall.finish();
70877821 switch (e) {
70887822 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
70897823 .INVAL => return error.Unseekable,
......@@ -7100,25 +7834,25 @@ fn fileSeekTo(userdata: ?*anyopaque, file: File, offset: u64) File.SeekError!voi
71007834
71017835 if (posix.SEEK == void) return error.Unseekable;
71027836
7103 return posixSeekTo(current_thread, fd, offset);
7837 return posixSeekTo(fd, offset);
71047838}
71057839
7106fn posixSeekTo(current_thread: *Thread, fd: posix.fd_t, offset: u64) File.SeekError!void {
7840fn posixSeekTo(fd: posix.fd_t, offset: u64) File.SeekError!void {
71077841 if (native_os == .linux and !builtin.link_libc and @sizeOf(usize) == 4) {
7108 try current_thread.beginSyscall();
7842 const syscall: Syscall = try .start();
71097843 while (true) {
71107844 var result: u64 = undefined;
71117845 switch (posix.errno(posix.system.llseek(fd, offset, &result, posix.SEEK.SET))) {
71127846 .SUCCESS => {
7113 current_thread.endSyscall();
7847 syscall.finish();
71147848 return;
71157849 },
71167850 .INTR => {
7117 try current_thread.checkCancel();
7851 try syscall.checkCancel();
71187852 continue;
71197853 },
71207854 else => |e| {
7121 current_thread.endSyscall();
7855 syscall.finish();
71227856 switch (e) {
71237857 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
71247858 .INVAL => return error.Unseekable,
......@@ -7132,19 +7866,19 @@ fn posixSeekTo(current_thread: *Thread, fd: posix.fd_t, offset: u64) File.SeekEr
71327866 }
71337867 }
71347868
7135 try current_thread.beginSyscall();
7869 const syscall: Syscall = try .start();
71367870 while (true) {
71377871 switch (posix.errno(lseek_sym(fd, @bitCast(offset), posix.SEEK.SET))) {
71387872 .SUCCESS => {
7139 current_thread.endSyscall();
7873 syscall.finish();
71407874 return;
71417875 },
71427876 .INTR => {
7143 try current_thread.checkCancel();
7877 try syscall.checkCancel();
71447878 continue;
71457879 },
71467880 else => |e| {
7147 current_thread.endSyscall();
7881 syscall.finish();
71487882 switch (e) {
71497883 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
71507884 .INVAL => return error.Unseekable,
......@@ -7170,7 +7904,7 @@ fn processExecutableOpen(userdata: ?*anyopaque, flags: File.OpenFlags) std.proce
71707904 const image_path_unicode_string = &windows.peb().ProcessParameters.ImagePathName;
71717905 const image_path_name = image_path_unicode_string.Buffer.?[0 .. image_path_unicode_string.Length / 2 :0];
71727906 const prefixed_path_w = try windows.wToPrefixedFileW(null, image_path_name);
7173 return dirOpenFileWtf16(t, null, prefixed_path_w.span(), flags);
7907 return dirOpenFileWtf16(null, prefixed_path_w.span(), flags);
71747908 },
71757909 .driverkit,
71767910 .ios,
......@@ -7234,22 +7968,21 @@ fn processExecutablePath(userdata: ?*anyopaque, out_buffer: []u8) std.process.Ex
72347968 else => |e| return e,
72357969 },
72367970 .freebsd, .dragonfly => {
7237 const current_thread = Thread.getCurrent(t);
72387971 var mib: [4]c_int = .{ posix.CTL.KERN, posix.KERN.PROC, posix.KERN.PROC_PATHNAME, -1 };
72397972 var out_len: usize = out_buffer.len;
7240 try current_thread.beginSyscall();
7973 const syscall: Syscall = try .start();
72417974 while (true) {
72427975 switch (posix.errno(posix.system.sysctl(&mib, mib.len, out_buffer.ptr, &out_len, null, 0))) {
72437976 .SUCCESS => {
7244 current_thread.endSyscall();
7977 syscall.finish();
72457978 return out_len - 1; // discard terminating NUL
72467979 },
72477980 .INTR => {
7248 try current_thread.checkCancel();
7981 try syscall.checkCancel();
72497982 continue;
72507983 },
72517984 else => |e| {
7252 current_thread.endSyscall();
7985 syscall.finish();
72537986 switch (e) {
72547987 .FAULT => |err| return errnoBug(err),
72557988 .PERM => return error.PermissionDenied,
......@@ -7262,22 +7995,21 @@ fn processExecutablePath(userdata: ?*anyopaque, out_buffer: []u8) std.process.Ex
72627995 }
72637996 },
72647997 .netbsd => {
7265 const current_thread = Thread.getCurrent(t);
72667998 var mib = [4]c_int{ posix.CTL.KERN, posix.KERN.PROC_ARGS, -1, posix.KERN.PROC_PATHNAME };
72677999 var out_len: usize = out_buffer.len;
7268 try current_thread.beginSyscall();
8000 const syscall: Syscall = try .start();
72698001 while (true) {
72708002 switch (posix.errno(posix.system.sysctl(&mib, mib.len, out_buffer.ptr, &out_len, null, 0))) {
72718003 .SUCCESS => {
7272 current_thread.endSyscall();
8004 syscall.finish();
72738005 return out_len - 1; // discard terminating NUL
72748006 },
72758007 .INTR => {
7276 try current_thread.checkCancel();
8008 try syscall.checkCancel();
72778009 continue;
72788010 },
72798011 else => |e| {
7280 current_thread.endSyscall();
8012 syscall.finish();
72818013 switch (e) {
72828014 .FAULT => |err| return errnoBug(err),
72838015 .PERM => return error.PermissionDenied,
......@@ -7295,20 +8027,19 @@ fn processExecutablePath(userdata: ?*anyopaque, out_buffer: []u8) std.process.Ex
72958027 const argv0 = std.mem.span(t.argv0.value orelse return error.OperationUnsupported);
72968028 if (std.mem.findScalar(u8, argv0, '/') != null) {
72978029 // argv[0] is a path (relative or absolute): use realpath(3) directly
7298 const current_thread = Thread.getCurrent(t);
72998030 var resolved_buf: [std.c.PATH_MAX]u8 = undefined;
7300 try current_thread.beginSyscall();
8031 const syscall: Syscall = try .start();
73018032 while (true) {
73028033 if (std.c.realpath(argv0, &resolved_buf)) |p| {
73038034 assert(p == &resolved_buf);
7304 break current_thread.endSyscall();
8035 break syscall.finish();
73058036 } else switch (@as(std.c.E, @enumFromInt(std.c._errno().*))) {
73068037 .INTR => {
7307 try current_thread.checkCancel();
8038 try syscall.checkCancel();
73088039 continue;
73098040 },
73108041 else => |e| {
7311 current_thread.endSyscall();
8042 syscall.finish();
73128043 switch (e) {
73138044 .ACCES => return error.AccessDenied,
73148045 .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
73328063 // argv[0] is not empty (and not a path): search PATH
73338064 t.scanEnviron();
73348065 const PATH = t.environ.string.PATH orelse return error.FileNotFound;
7335 const current_thread = Thread.getCurrent(t);
73368066 var it = std.mem.tokenizeScalar(u8, PATH, ':');
73378067 it: while (it.next()) |dir| {
73388068 var resolved_path_buf: [std.c.PATH_MAX]u8 = undefined;
......@@ -7341,34 +8071,34 @@ fn processExecutablePath(userdata: ?*anyopaque, out_buffer: []u8) std.process.Ex
73418071 }, 0) catch continue;
73428072
73438073 var resolved_buf: [std.c.PATH_MAX]u8 = undefined;
7344 try current_thread.beginSyscall();
8074 const syscall: Syscall = try .start();
73458075 while (true) {
73468076 if (std.c.realpath(resolved_path, &resolved_buf)) |p| {
73478077 assert(p == &resolved_buf);
7348 break current_thread.endSyscall();
8078 break syscall.finish();
73498079 } else switch (@as(std.c.E, @enumFromInt(std.c._errno().*))) {
73508080 .INTR => {
7351 try current_thread.checkCancel();
8081 try syscall.checkCancel();
73528082 continue;
73538083 },
73548084 .NAMETOOLONG => {
7355 current_thread.endSyscall();
8085 syscall.finish();
73568086 return error.NameTooLong;
73578087 },
73588088 .NOMEM => {
7359 current_thread.endSyscall();
8089 syscall.finish();
73608090 return error.SystemResources;
73618091 },
73628092 .IO => {
7363 current_thread.endSyscall();
8093 syscall.finish();
73648094 return error.InputOutput;
73658095 },
73668096 .ACCES, .LOOP, .NOENT, .NOTDIR => {
7367 current_thread.endSyscall();
8097 syscall.finish();
73688098 continue :it;
73698099 },
73708100 else => |err| {
7371 current_thread.endSyscall();
8101 syscall.finish();
73728102 return posix.unexpectedErrno(err);
73738103 },
73748104 }
......@@ -7383,8 +8113,6 @@ fn processExecutablePath(userdata: ?*anyopaque, out_buffer: []u8) std.process.Ex
73838113 return error.FileNotFound;
73848114 },
73858115 .windows => {
7386 const current_thread = Thread.getCurrent(t);
7387 try current_thread.checkCancel();
73888116 const w = windows;
73898117 const image_path_unicode_string = &w.peb().ProcessParameters.ImagePathName;
73908118 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
73948122 // that the symlink points to, though, so we need to get the realpath.
73958123 var path_name_w_buf = try w.wToPrefixedFileW(null, image_path_name);
73968124
7397 const h_file = blk: {
7398 const res = w.OpenFile(path_name_w_buf.span(), .{
7399 .dir = null,
7400 .access_mask = .{
7401 .GENERIC = .{ .READ = true },
7402 .STANDARD = .{ .SYNCHRONIZE = true },
7403 },
7404 .creation = .OPEN,
7405 .filter = .any,
7406 }) catch |err| switch (err) {
7407 error.WouldBlock => unreachable,
7408 else => |e| return e,
7409 };
7410 break :blk res;
8125 const h_file = handle: {
8126 const syscall: Syscall = try .start();
8127 while (true) {
8128 if (w.OpenFile(path_name_w_buf.span(), .{
8129 .dir = null,
8130 .access_mask = .{
8131 .GENERIC = .{ .READ = true },
8132 .STANDARD = .{ .SYNCHRONIZE = true },
8133 },
8134 .creation = .OPEN,
8135 .filter = .any,
8136 })) |handle| {
8137 syscall.finish();
8138 break :handle handle;
8139 } else |err| switch (err) {
8140 error.WouldBlock => unreachable,
8141 error.OperationCanceled => {
8142 try syscall.checkCancel();
8143 continue;
8144 },
8145 else => |e| return e,
8146 }
8147 }
74118148 };
74128149 defer w.CloseHandle(h_file);
74138150
74148151 // TODO move GetFinalPathNameByHandle logic into std.Io.Threaded and add cancel checks
8152 try Thread.checkCancel();
74158153 const wide_slice = try w.GetFinalPathNameByHandle(h_file, .{}, &path_name_w_buf.data);
74168154
74178155 const len = std.unicode.calcWtf8Len(wide_slice);
......@@ -7434,19 +8172,19 @@ fn fileWritePositional(
74348172 offset: u64,
74358173) File.WritePositionalError!usize {
74368174 const t: *Threaded = @ptrCast(@alignCast(userdata));
7437 const current_thread = Thread.getCurrent(t);
8175 _ = t;
74388176
74398177 if (is_windows) {
74408178 if (header.len != 0) {
7441 return writeFilePositionalWindows(current_thread, file.handle, header, offset);
8179 return writeFilePositionalWindows(file.handle, header, offset);
74428180 }
74438181 for (data[0 .. data.len - 1]) |buf| {
74448182 if (buf.len == 0) continue;
7445 return writeFilePositionalWindows(current_thread, file.handle, buf, offset);
8183 return writeFilePositionalWindows(file.handle, buf, offset);
74468184 }
74478185 const pattern = data[data.len - 1];
74488186 if (pattern.len == 0 or splat == 0) return 0;
7449 return writeFilePositionalWindows(current_thread, file.handle, pattern, offset);
8187 return writeFilePositionalWindows(file.handle, pattern, offset);
74508188 }
74518189
74528190 var iovecs: [max_iovecs_len]posix.iovec_const = undefined;
......@@ -7484,19 +8222,19 @@ fn fileWritePositional(
74848222
74858223 if (native_os == .wasi and !builtin.link_libc) {
74868224 var n_written: usize = undefined;
7487 try current_thread.beginSyscall();
8225 const syscall: Syscall = try .start();
74888226 while (true) {
74898227 switch (std.os.wasi.fd_pwrite(file.handle, &iovecs, iovlen, offset, &n_written)) {
74908228 .SUCCESS => {
7491 current_thread.endSyscall();
8229 syscall.finish();
74928230 return n_written;
74938231 },
74948232 .INTR => {
7495 try current_thread.checkCancel();
8233 try syscall.checkCancel();
74968234 continue;
74978235 },
74988236 else => |e| {
7499 current_thread.endSyscall();
8237 syscall.finish();
75008238 switch (e) {
75018239 .INVAL => |err| return errnoBug(err),
75028240 .FAULT => |err| return errnoBug(err),
......@@ -7520,20 +8258,20 @@ fn fileWritePositional(
75208258 }
75218259 }
75228260
7523 try current_thread.beginSyscall();
8261 const syscall: Syscall = try .start();
75248262 while (true) {
75258263 const rc = pwritev_sym(file.handle, &iovecs, @intCast(iovlen), @bitCast(offset));
75268264 switch (posix.errno(rc)) {
75278265 .SUCCESS => {
7528 current_thread.endSyscall();
8266 syscall.finish();
75298267 return @intCast(rc);
75308268 },
75318269 .INTR => {
7532 try current_thread.checkCancel();
8270 try syscall.checkCancel();
75338271 continue;
75348272 },
75358273 else => |e| {
7536 current_thread.endSyscall();
8274 syscall.finish();
75378275 switch (e) {
75388276 .INVAL => |err| return errnoBug(err),
75398277 .FAULT => |err| return errnoBug(err),
......@@ -7560,13 +8298,10 @@ fn fileWritePositional(
75608298}
75618299
75628300fn writeFilePositionalWindows(
7563 current_thread: *Thread,
75648301 handle: windows.HANDLE,
75658302 bytes: []const u8,
75668303 offset: u64,
75678304) File.WritePositionalError!usize {
7568 try current_thread.checkCancel();
7569
75708305 var bytes_written: windows.DWORD = undefined;
75718306 var overlapped: windows.OVERLAPPED = .{
75728307 .Internal = 0,
......@@ -7580,21 +8315,31 @@ fn writeFilePositionalWindows(
75808315 .hEvent = null,
75818316 };
75828317 const adjusted_len = std.math.lossyCast(u32, bytes.len);
7583 if (windows.kernel32.WriteFile(handle, bytes.ptr, adjusted_len, &bytes_written, &overlapped) == 0) {
8318 const syscall: Syscall = try .start();
8319 while (true) {
8320 if (windows.kernel32.WriteFile(handle, bytes.ptr, adjusted_len, &bytes_written, &overlapped) != 0) {
8321 syscall.finish();
8322 return bytes_written;
8323 }
75848324 switch (windows.GetLastError()) {
7585 .INVALID_USER_BUFFER => return error.SystemResources,
7586 .NOT_ENOUGH_MEMORY => return error.SystemResources,
7587 .OPERATION_ABORTED => return error.Canceled,
7588 .NOT_ENOUGH_QUOTA => return error.SystemResources,
7589 .NO_DATA => return error.BrokenPipe,
7590 .INVALID_HANDLE => return error.NotOpenForWriting,
7591 .LOCK_VIOLATION => return error.LockViolation,
7592 .ACCESS_DENIED => return error.AccessDenied,
7593 .WORKING_SET_QUOTA => return error.SystemResources,
7594 else => |err| return windows.unexpectedError(err),
8325 .OPERATION_ABORTED => {
8326 try syscall.checkCancel();
8327 continue;
8328 },
8329 .INVALID_USER_BUFFER => return syscall.fail(error.SystemResources),
8330 .NOT_ENOUGH_MEMORY => return syscall.fail(error.SystemResources),
8331 .NOT_ENOUGH_QUOTA => return syscall.fail(error.SystemResources),
8332 .NO_DATA => return syscall.fail(error.BrokenPipe),
8333 .INVALID_HANDLE => return syscall.fail(error.NotOpenForWriting),
8334 .LOCK_VIOLATION => return syscall.fail(error.LockViolation),
8335 .ACCESS_DENIED => return syscall.fail(error.AccessDenied),
8336 .WORKING_SET_QUOTA => return syscall.fail(error.SystemResources),
8337 else => |err| {
8338 syscall.finish();
8339 return windows.unexpectedError(err);
8340 },
75958341 }
75968342 }
7597 return bytes_written;
75988343}
75998344
76008345fn fileWriteStreaming(
......@@ -7605,19 +8350,19 @@ fn fileWriteStreaming(
76058350 splat: usize,
76068351) File.Writer.Error!usize {
76078352 const t: *Threaded = @ptrCast(@alignCast(userdata));
7608 const current_thread = Thread.getCurrent(t);
8353 _ = t;
76098354
76108355 if (is_windows) {
76118356 if (header.len != 0) {
7612 return writeFileStreamingWindows(current_thread, file.handle, header);
8357 return writeFileStreamingWindows(file.handle, header);
76138358 }
76148359 for (data[0 .. data.len - 1]) |buf| {
76158360 if (buf.len == 0) continue;
7616 return writeFileStreamingWindows(current_thread, file.handle, buf);
8361 return writeFileStreamingWindows(file.handle, buf);
76178362 }
76188363 const pattern = data[data.len - 1];
76198364 if (pattern.len == 0 or splat == 0) return 0;
7620 return writeFileStreamingWindows(current_thread, file.handle, pattern);
8365 return writeFileStreamingWindows(file.handle, pattern);
76218366 }
76228367
76238368 var iovecs: [max_iovecs_len]posix.iovec_const = undefined;
......@@ -7655,19 +8400,19 @@ fn fileWriteStreaming(
76558400
76568401 if (native_os == .wasi and !builtin.link_libc) {
76578402 var n_written: usize = undefined;
7658 try current_thread.beginSyscall();
8403 const syscall: Syscall = try .start();
76598404 while (true) {
76608405 switch (std.os.wasi.fd_write(file.handle, &iovecs, iovlen, &n_written)) {
76618406 .SUCCESS => {
7662 current_thread.endSyscall();
8407 syscall.finish();
76638408 return n_written;
76648409 },
76658410 .INTR => {
7666 try current_thread.checkCancel();
8411 try syscall.checkCancel();
76678412 continue;
76688413 },
76698414 else => |e| {
7670 current_thread.endSyscall();
8415 syscall.finish();
76718416 switch (e) {
76728417 .INVAL => |err| return errnoBug(err),
76738418 .FAULT => |err| return errnoBug(err),
......@@ -7688,20 +8433,20 @@ fn fileWriteStreaming(
76888433 }
76898434 }
76908435
7691 try current_thread.beginSyscall();
8436 const syscall: Syscall = try .start();
76928437 while (true) {
76938438 const rc = posix.system.writev(file.handle, &iovecs, @intCast(iovlen));
76948439 switch (posix.errno(rc)) {
76958440 .SUCCESS => {
7696 current_thread.endSyscall();
8441 syscall.finish();
76978442 return @intCast(rc);
76988443 },
76998444 .INTR => {
7700 try current_thread.checkCancel();
8445 try syscall.checkCancel();
77018446 continue;
77028447 },
77038448 else => |e| {
7704 current_thread.endSyscall();
8449 syscall.finish();
77058450 switch (e) {
77068451 .INVAL => |err| return errnoBug(err),
77078452 .FAULT => |err| return errnoBug(err),
......@@ -7724,29 +8469,36 @@ fn fileWriteStreaming(
77248469}
77258470
77268471fn writeFileStreamingWindows(
7727 current_thread: *Thread,
77288472 handle: windows.HANDLE,
77298473 bytes: []const u8,
77308474) File.Writer.Error!usize {
7731 try current_thread.checkCancel();
7732
77338475 var bytes_written: windows.DWORD = undefined;
77348476 const adjusted_len = std.math.lossyCast(u32, bytes.len);
7735 if (windows.kernel32.WriteFile(handle, bytes.ptr, adjusted_len, &bytes_written, null) == 0) {
8477 const syscall: Syscall = try .start();
8478 while (true) {
8479 if (windows.kernel32.WriteFile(handle, bytes.ptr, adjusted_len, &bytes_written, null) != 0) {
8480 syscall.finish();
8481 return bytes_written;
8482 }
77368483 switch (windows.GetLastError()) {
7737 .INVALID_USER_BUFFER => return error.SystemResources,
7738 .NOT_ENOUGH_MEMORY => return error.SystemResources,
7739 .OPERATION_ABORTED => return error.Canceled,
7740 .NOT_ENOUGH_QUOTA => return error.SystemResources,
7741 .NO_DATA => return error.BrokenPipe,
7742 .INVALID_HANDLE => return error.NotOpenForWriting,
7743 .LOCK_VIOLATION => return error.LockViolation,
7744 .ACCESS_DENIED => return error.AccessDenied,
7745 .WORKING_SET_QUOTA => return error.SystemResources,
7746 else => |err| return windows.unexpectedError(err),
8484 .OPERATION_ABORTED => {
8485 try syscall.checkCancel();
8486 continue;
8487 },
8488 .INVALID_USER_BUFFER => return syscall.fail(error.SystemResources),
8489 .NOT_ENOUGH_MEMORY => return syscall.fail(error.SystemResources),
8490 .NOT_ENOUGH_QUOTA => return syscall.fail(error.SystemResources),
8491 .NO_DATA => return syscall.fail(error.BrokenPipe),
8492 .INVALID_HANDLE => return syscall.fail(error.NotOpenForWriting),
8493 .LOCK_VIOLATION => return syscall.fail(error.LockViolation),
8494 .ACCESS_DENIED => return syscall.fail(error.AccessDenied),
8495 .WORKING_SET_QUOTA => return syscall.fail(error.SystemResources),
8496 else => |err| {
8497 syscall.finish();
8498 return windows.unexpectedError(err);
8499 },
77478500 }
77488501 }
7749 return bytes_written;
77508502}
77518503
77528504fn fileWriteFileStreaming(
......@@ -7807,40 +8559,39 @@ fn fileWriteFileStreaming(
78078559 const nbytes: usize = @min(file_limit, std.math.maxInt(usize));
78088560 const flags = 0;
78098561
7810 const current_thread = Thread.getCurrent(t);
7811 try current_thread.beginSyscall();
8562 const syscall: Syscall = try .start();
78128563 while (true) {
78138564 switch (posix.errno(std.c.sendfile(in_fd, out_fd, offset, nbytes, hdtr, &sbytes, flags))) {
78148565 .SUCCESS => {
7815 current_thread.endSyscall();
8566 syscall.finish();
78168567 break;
78178568 },
78188569 .INVAL, .OPNOTSUPP, .NOTSOCK, .NOSYS => {
78198570 // Give calling code chance to observe before trying
78208571 // something else.
7821 current_thread.endSyscall();
8572 syscall.finish();
78228573 @atomicStore(UseSendfile, &t.use_sendfile, .disabled, .monotonic);
78238574 return 0;
78248575 },
78258576 .INTR, .BUSY => {
78268577 if (sbytes == 0) {
7827 try current_thread.checkCancel();
8578 try syscall.checkCancel();
78288579 continue;
78298580 } else {
78308581 // Even if we are being canceled, there have been side
78318582 // effects, so it is better to report those side
78328583 // effects to the caller.
7833 current_thread.endSyscall();
8584 syscall.finish();
78348585 break;
78358586 }
78368587 },
78378588 .AGAIN => {
7838 current_thread.endSyscall();
8589 syscall.finish();
78398590 if (sbytes == 0) return error.WouldBlock;
78408591 break;
78418592 },
78428593 else => |e| {
7843 current_thread.endSyscall();
8594 syscall.finish();
78448595 assert(error.Unexpected == switch (e) {
78458596 .NOTCONN => return error.BrokenPipe,
78468597 .IO => return error.InputOutput,
......@@ -7893,40 +8644,39 @@ fn fileWriteFileStreaming(
78938644 const max_count = std.math.maxInt(i32); // Avoid EINVAL.
78948645 var len: std.c.off_t = @min(file_limit, max_count);
78958646 const flags = 0;
7896 const current_thread = Thread.getCurrent(t);
7897 try current_thread.beginSyscall();
8647 const syscall: Syscall = try .start();
78988648 while (true) {
78998649 switch (posix.errno(std.c.sendfile(in_fd, out_fd, offset, &len, hdtr, flags))) {
79008650 .SUCCESS => {
7901 current_thread.endSyscall();
8651 syscall.finish();
79028652 break;
79038653 },
79048654 .OPNOTSUPP, .NOTSOCK, .NOSYS => {
79058655 // Give calling code chance to observe before trying
79068656 // something else.
7907 current_thread.endSyscall();
8657 syscall.finish();
79088658 @atomicStore(UseSendfile, &t.use_sendfile, .disabled, .monotonic);
79098659 return 0;
79108660 },
79118661 .INTR => {
79128662 if (len == 0) {
7913 try current_thread.checkCancel();
8663 try syscall.checkCancel();
79148664 continue;
79158665 } else {
79168666 // Even if we are being canceled, there have been side
79178667 // effects, so it is better to report those side
79188668 // effects to the caller.
7919 current_thread.endSyscall();
8669 syscall.finish();
79208670 break;
79218671 }
79228672 },
79238673 .AGAIN => {
7924 current_thread.endSyscall();
8674 syscall.finish();
79258675 if (len == 0) return error.WouldBlock;
79268676 break;
79278677 },
79288678 else => |e| {
7929 current_thread.endSyscall();
8679 syscall.finish();
79308680 assert(error.Unexpected == switch (e) {
79318681 .NOTCONN => return error.BrokenPipe,
79328682 .IO => return error.InputOutput,
......@@ -7973,28 +8723,27 @@ fn fileWriteFileStreaming(
79738723 .streaming_simple, .positional_simple => break :sf,
79748724 .failure => return error.ReadFailed,
79758725 };
7976 const current_thread = Thread.getCurrent(t);
7977 try current_thread.beginSyscall();
8726 const syscall: Syscall = try .start();
79788727 const n: usize = while (true) {
79798728 const rc = sendfile_sym(out_fd, in_fd, off_ptr, count);
79808729 switch (posix.errno(rc)) {
79818730 .SUCCESS => {
7982 current_thread.endSyscall();
8731 syscall.finish();
79838732 break @intCast(rc);
79848733 },
79858734 .NOSYS, .INVAL => {
79868735 // Give calling code chance to observe before trying
79878736 // something else.
7988 current_thread.endSyscall();
8737 syscall.finish();
79898738 @atomicStore(UseSendfile, &t.use_sendfile, .disabled, .monotonic);
79908739 return 0;
79918740 },
79928741 .INTR => {
7993 try current_thread.checkCancel();
8742 try syscall.checkCancel();
79948743 continue;
79958744 },
79968745 else => |e| {
7997 current_thread.endSyscall();
8746 syscall.finish();
79988747 assert(error.Unexpected == switch (e) {
79998748 .NOTCONN => return error.BrokenPipe, // `out_fd` is an unconnected socket
80008749 .AGAIN => return error.WouldBlock,
......@@ -8050,30 +8799,29 @@ fn fileWriteFileStreaming(
80508799 .streaming => null,
80518800 .failure => return error.ReadFailed,
80528801 };
8053 const current_thread = Thread.getCurrent(t);
80548802 const n: usize = switch (native_os) {
80558803 .linux => n: {
8056 try current_thread.beginSyscall();
8804 const syscall: Syscall = try .start();
80578805 while (true) {
80588806 const rc = linux_copy_file_range_sys.copy_file_range(in_fd, off_in_ptr, out_fd, null, @intFromEnum(limit), 0);
80598807 switch (linux_copy_file_range_sys.errno(rc)) {
80608808 .SUCCESS => {
8061 current_thread.endSyscall();
8809 syscall.finish();
80628810 break :n @intCast(rc);
80638811 },
80648812 .INTR => {
8065 try current_thread.checkCancel();
8813 try syscall.checkCancel();
80668814 continue;
80678815 },
80688816 .OPNOTSUPP, .INVAL, .NOSYS => {
80698817 // Give calling code chance to observe before trying
80708818 // something else.
8071 current_thread.endSyscall();
8819 syscall.finish();
80728820 @atomicStore(UseCopyFileRange, &t.use_copy_file_range, .disabled, .monotonic);
80738821 return 0;
80748822 },
80758823 else => |e| {
8076 current_thread.endSyscall();
8824 syscall.finish();
80778825 assert(error.Unexpected == switch (e) {
80788826 .FBIG => return error.FileTooBig,
80798827 .IO => return error.InputOutput,
......@@ -8097,27 +8845,27 @@ fn fileWriteFileStreaming(
80978845 }
80988846 },
80998847 .freebsd => n: {
8100 try current_thread.beginSyscall();
8848 const syscall: Syscall = try .start();
81018849 while (true) {
81028850 const rc = std.c.copy_file_range(in_fd, off_in_ptr, out_fd, null, @intFromEnum(limit), 0);
81038851 switch (std.c.errno(rc)) {
81048852 .SUCCESS => {
8105 current_thread.endSyscall();
8853 syscall.finish();
81068854 break :n @intCast(rc);
81078855 },
81088856 .INTR => {
8109 try current_thread.checkCancel();
8857 try syscall.checkCancel();
81108858 continue;
81118859 },
81128860 .OPNOTSUPP, .INVAL, .NOSYS => {
81138861 // Give calling code chance to observe before trying
81148862 // something else.
8115 current_thread.endSyscall();
8863 syscall.finish();
81168864 @atomicStore(UseCopyFileRange, &t.use_copy_file_range, .disabled, .monotonic);
81178865 return 0;
81188866 },
81198867 else => |e| {
8120 current_thread.endSyscall();
8868 syscall.finish();
81218869 assert(error.Unexpected == switch (e) {
81228870 .FBIG => return error.FileTooBig,
81238871 .IO => return error.InputOutput,
......@@ -8226,30 +8974,29 @@ fn fileWriteFilePositional(
82268974 .failure => return error.ReadFailed,
82278975 };
82288976 var off_out: i64 = @intCast(offset);
8229 const current_thread = Thread.getCurrent(t);
82308977 const n: usize = switch (native_os) {
82318978 .linux => n: {
8232 try current_thread.beginSyscall();
8979 const syscall: Syscall = try .start();
82338980 while (true) {
82348981 const rc = linux_copy_file_range_sys.copy_file_range(in_fd, off_in_ptr, out_fd, &off_out, @intFromEnum(limit), 0);
82358982 switch (linux_copy_file_range_sys.errno(rc)) {
82368983 .SUCCESS => {
8237 current_thread.endSyscall();
8984 syscall.finish();
82388985 break :n @intCast(rc);
82398986 },
82408987 .INTR => {
8241 try current_thread.checkCancel();
8988 try syscall.checkCancel();
82428989 continue;
82438990 },
82448991 .OPNOTSUPP, .INVAL, .NOSYS => {
82458992 // Give calling code chance to observe before trying
82468993 // something else.
8247 current_thread.endSyscall();
8994 syscall.finish();
82488995 @atomicStore(UseCopyFileRange, &t.use_copy_file_range, .disabled, .monotonic);
82498996 return 0;
82508997 },
82518998 else => |e| {
8252 current_thread.endSyscall();
8999 syscall.finish();
82539000 assert(error.Unexpected == switch (e) {
82549001 .FBIG => return error.FileTooBig,
82559002 .IO => return error.InputOutput,
......@@ -8274,27 +9021,27 @@ fn fileWriteFilePositional(
82749021 }
82759022 },
82769023 .freebsd => n: {
8277 try current_thread.beginSyscall();
9024 const syscall: Syscall = try .start();
82789025 while (true) {
82799026 const rc = std.c.copy_file_range(in_fd, off_in_ptr, out_fd, &off_out, @intFromEnum(limit), 0);
82809027 switch (std.c.errno(rc)) {
82819028 .SUCCESS => {
8282 current_thread.endSyscall();
9029 syscall.finish();
82839030 break :n @intCast(rc);
82849031 },
82859032 .INTR => {
8286 try current_thread.checkCancel();
9033 try syscall.checkCancel();
82879034 continue;
82889035 },
82899036 .OPNOTSUPP, .INVAL, .NOSYS => {
82909037 // Give calling code chance to observe before trying
82919038 // something else.
8292 current_thread.endSyscall();
9039 syscall.finish();
82939040 @atomicStore(UseCopyFileRange, &t.use_copy_file_range, .disabled, .monotonic);
82949041 return 0;
82959042 },
82969043 else => |e| {
8297 current_thread.endSyscall();
9044 syscall.finish();
82989045 assert(error.Unexpected == switch (e) {
82999046 .FBIG => return error.FileTooBig,
83009047 .IO => return error.InputOutput,
......@@ -8334,28 +9081,27 @@ fn fileWriteFilePositional(
83349081 file_reader.interface.toss(n -| header.len);
83359082 return n;
83369083 }
8337 const current_thread = Thread.getCurrent(t);
8338 try current_thread.beginSyscall();
9084 const syscall: Syscall = try .start();
83399085 while (true) {
83409086 const rc = std.c.fcopyfile(in_fd, out_fd, null, .{ .DATA = true });
83419087 switch (posix.errno(rc)) {
83429088 .SUCCESS => {
8343 current_thread.endSyscall();
9089 syscall.finish();
83449090 break;
83459091 },
83469092 .INTR => {
8347 try current_thread.checkCancel();
9093 try syscall.checkCancel();
83489094 continue;
83499095 },
83509096 .OPNOTSUPP => {
83519097 // Give calling code chance to observe before trying
83529098 // something else.
8353 current_thread.endSyscall();
9099 syscall.finish();
83549100 @atomicStore(UseFcopyfile, &t.use_fcopyfile, .disabled, .monotonic);
83559101 return 0;
83569102 },
83579103 else => |e| {
8358 current_thread.endSyscall();
9104 syscall.finish();
83599105 assert(error.Unexpected == switch (e) {
83609106 .NOMEM => return error.SystemResources,
83619107 .INVAL => |err| errnoBug(err),
......@@ -8372,9 +9118,7 @@ fn fileWriteFilePositional(
83729118 return error.Unimplemented;
83739119}
83749120
8375fn nowPosix(userdata: ?*anyopaque, clock: Io.Clock) Io.Clock.Error!Io.Timestamp {
8376 const t: *Threaded = @ptrCast(@alignCast(userdata));
8377 _ = t;
9121fn nowPosix(clock: Io.Clock) Io.Clock.Error!Io.Timestamp {
83789122 const clock_id: posix.clockid_t = clockToPosix(clock);
83799123 var tp: posix.timespec = undefined;
83809124 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
83849128 }
83859129}
83869130
8387const now = switch (native_os) {
8388 .windows => nowWindows,
8389 .wasi => nowWasi,
8390 else => nowPosix,
8391};
8392
8393fn nowWindows(userdata: ?*anyopaque, clock: Io.Clock) Io.Clock.Error!Io.Timestamp {
9131fn now(userdata: ?*anyopaque, clock: Io.Clock) Io.Clock.Error!Io.Timestamp {
83949132 const t: *Threaded = @ptrCast(@alignCast(userdata));
83959133 _ = t;
9134 return switch (native_os) {
9135 .windows => nowWindows(clock),
9136 .wasi => nowWasi(clock),
9137 else => nowPosix(clock),
9138 };
9139}
9140
9141fn nowWindows(clock: Io.Clock) Io.Clock.Error!Io.Timestamp {
83969142 switch (clock) {
83979143 .real => {
83989144 // RtlGetSystemTimePrecise() has a granularity of 100 nanoseconds
......@@ -8425,25 +9171,24 @@ fn nowWindows(userdata: ?*anyopaque, clock: Io.Clock) Io.Clock.Error!Io.Timestam
84259171 }
84269172}
84279173
8428fn nowWasi(userdata: ?*anyopaque, clock: Io.Clock) Io.Clock.Error!Io.Timestamp {
8429 const t: *Threaded = @ptrCast(@alignCast(userdata));
8430 _ = t;
9174fn nowWasi(clock: Io.Clock) Io.Clock.Error!Io.Timestamp {
84319175 var ns: std.os.wasi.timestamp_t = undefined;
84329176 const err = std.os.wasi.clock_time_get(clockToWasi(clock), 1, &ns);
84339177 if (err != .SUCCESS) return error.Unexpected;
84349178 return .fromNanoseconds(ns);
84359179}
84369180
8437const sleep = switch (native_os) {
8438 .windows => sleepWindows,
8439 .wasi => sleepWasi,
8440 .linux => sleepLinux,
8441 else => sleepPosix,
8442};
8443
8444fn sleepLinux(userdata: ?*anyopaque, timeout: Io.Timeout) Io.SleepError!void {
9181fn sleep(userdata: ?*anyopaque, timeout: Io.Timeout) Io.SleepError!void {
84459182 const t: *Threaded = @ptrCast(@alignCast(userdata));
8446 const current_thread = Thread.getCurrent(t);
9183 if (use_parking_sleep) return parking_sleep.sleep(timeout);
9184 switch (native_os) {
9185 .wasi => return sleepWasi(t, timeout),
9186 .linux => return sleepLinux(timeout),
9187 else => return sleepPosix(t, timeout),
9188 }
9189}
9190
9191fn sleepLinux(timeout: Io.Timeout) Io.SleepError!void {
84479192 const clock_id: posix.clockid_t = clockToPosix(switch (timeout) {
84489193 .none => .awake,
84499194 .duration => |d| d.clock,
......@@ -8455,22 +9200,22 @@ fn sleepLinux(userdata: ?*anyopaque, timeout: Io.Timeout) Io.SleepError!void {
84559200 .deadline => |deadline| deadline.raw.nanoseconds,
84569201 };
84579202 var timespec: posix.timespec = timestampToPosix(deadline_nanoseconds);
8458 try current_thread.beginSyscall();
9203 const syscall: Syscall = try .start();
84599204 while (true) {
84609205 switch (std.os.linux.errno(std.os.linux.clock_nanosleep(clock_id, .{ .ABSTIME = switch (timeout) {
84619206 .none, .duration => false,
84629207 .deadline => true,
84639208 } }, &timespec, &timespec))) {
84649209 .SUCCESS => {
8465 current_thread.endSyscall();
9210 syscall.finish();
84669211 return;
84679212 },
84689213 .INTR => {
8469 try current_thread.checkCancel();
9214 try syscall.checkCancel();
84709215 continue;
84719216 },
84729217 else => |e| {
8473 current_thread.endSyscall();
9218 syscall.finish();
84749219 switch (e) {
84759220 .INVAL => return error.UnsupportedClock,
84769221 else => |err| return posix.unexpectedErrno(err),
......@@ -8480,23 +9225,7 @@ fn sleepLinux(userdata: ?*anyopaque, timeout: Io.Timeout) Io.SleepError!void {
84809225 }
84819226}
84829227
8483fn sleepWindows(userdata: ?*anyopaque, timeout: Io.Timeout) Io.SleepError!void {
8484 const t: *Threaded = @ptrCast(@alignCast(userdata));
8485 const current_thread = Thread.getCurrent(t);
8486 const t_io = ioBasic(t);
8487 try current_thread.checkCancel();
8488 const ms = ms: {
8489 const d = (try timeout.toDurationFromNow(t_io)) orelse
8490 break :ms std.math.maxInt(windows.DWORD);
8491 break :ms std.math.lossyCast(windows.DWORD, d.raw.toMilliseconds());
8492 };
8493 // TODO: alertable true with checkCancel in a loop plus deadline
8494 _ = windows.kernel32.SleepEx(ms, windows.FALSE);
8495}
8496
8497fn sleepWasi(userdata: ?*anyopaque, timeout: Io.Timeout) Io.SleepError!void {
8498 const t: *Threaded = @ptrCast(@alignCast(userdata));
8499 const current_thread = Thread.getCurrent(t);
9228fn sleepWasi(t: *Threaded, timeout: Io.Timeout) Io.SleepError!void {
85009229 const t_io = ioBasic(t);
85019230 const w = std.os.wasi;
85029231
......@@ -8520,14 +9249,12 @@ fn sleepWasi(userdata: ?*anyopaque, timeout: Io.Timeout) Io.SleepError!void {
85209249 };
85219250 var event: w.event_t = undefined;
85229251 var nevents: usize = undefined;
8523 try current_thread.beginSyscall();
9252 const syscall: Syscall = try .start();
85249253 _ = w.poll_oneoff(&in, &event, 1, &nevents);
8525 current_thread.endSyscall();
9254 syscall.finish();
85269255}
85279256
8528fn sleepPosix(userdata: ?*anyopaque, timeout: Io.Timeout) Io.SleepError!void {
8529 const t: *Threaded = @ptrCast(@alignCast(userdata));
8530 const current_thread = Thread.getCurrent(t);
9257fn sleepPosix(t: *Threaded, timeout: Io.Timeout) Io.SleepError!void {
85319258 const t_io = ioBasic(t);
85329259 const sec_type = @typeInfo(posix.timespec).@"struct".fields[0].type;
85339260 const nsec_type = @typeInfo(posix.timespec).@"struct".fields[1].type;
......@@ -8539,48 +9266,85 @@ fn sleepPosix(userdata: ?*anyopaque, timeout: Io.Timeout) Io.SleepError!void {
85399266 };
85409267 break :t timestampToPosix(d.raw.toNanoseconds());
85419268 };
8542 try current_thread.beginSyscall();
9269 const syscall: Syscall = try .start();
85439270 while (true) {
85449271 switch (posix.errno(posix.system.nanosleep(&timespec, &timespec))) {
85459272 .INTR => {
8546 try current_thread.checkCancel();
9273 try syscall.checkCancel();
85479274 continue;
85489275 },
85499276 // This prong handles success as well as unexpected errors.
8550 else => return current_thread.endSyscall(),
9277 else => return syscall.finish(),
85519278 }
85529279 }
85539280}
85549281
85559282fn select(userdata: ?*anyopaque, futures: []const *Io.AnyFuture) Io.Cancelable!usize {
85569283 const t: *Threaded = @ptrCast(@alignCast(userdata));
9284 _ = t;
85579285
8558 var event: Io.Event = .unset;
9286 var num_completed: std.atomic.Value(u32) = .init(0);
85599287
8560 for (futures, 0..) |future, i| {
8561 const closure: *AsyncClosure = @ptrCast(@alignCast(future));
8562 if (@atomicRmw(?*Io.Event, &closure.select_condition, .Xchg, &event, .seq_cst) == AsyncClosure.done_event) {
8563 for (futures[0..i]) |cleanup_future| {
8564 const cleanup_closure: *AsyncClosure = @ptrCast(@alignCast(cleanup_future));
8565 if (@atomicRmw(?*Io.Event, &cleanup_closure.select_condition, .Xchg, null, .seq_cst) == AsyncClosure.done_event) {
8566 cleanup_closure.event.waitUncancelable(ioBasic(t)); // Ensure no reference to our stack-allocated event.
8567 }
8568 }
8569 return i;
9288 for (futures, 0..) |any_future, i| {
9289 const future: *Future = @ptrCast(@alignCast(any_future));
9290 future.awaiter = &num_completed;
9291 const old_status = future.status.fetchOr(
9292 .{ .tag = .pending_awaited, .thread = .null },
9293 .release, // release `future.awaiter`
9294 );
9295 switch (old_status.tag) {
9296 .pending => {},
9297 .pending_awaited => unreachable, // `await` raced with `select`
9298 .pending_canceled => unreachable, // `cancel` raced with `select`
9299 .done => {
9300 future.status.store(old_status, .monotonic);
9301 _ = finishSelect(&num_completed, futures[0..i]);
9302 return i;
9303 },
85709304 }
85719305 }
85729306
8573 try event.wait(ioBasic(t));
9307 errdefer _ = finishSelect(&num_completed, futures);
85749308
8575 var result: ?usize = null;
8576 for (futures, 0..) |future, i| {
8577 const closure: *AsyncClosure = @ptrCast(@alignCast(future));
8578 if (@atomicRmw(?*Io.Event, &closure.select_condition, .Xchg, null, .seq_cst) == AsyncClosure.done_event) {
8579 closure.event.waitUncancelable(ioBasic(t)); // Ensure no reference to our stack-allocated event.
8580 if (result == null) result = i; // In case multiple are ready, return first.
8581 }
9309 while (true) {
9310 const n = num_completed.load(.acquire);
9311 if (n > 0) break;
9312 assert(n < futures.len);
9313 try Thread.futexWait(&num_completed.raw, n, null);
9314 }
9315 return finishSelect(&num_completed, futures).?;
9316}
9317fn finishSelect(
9318 num_completed: *std.atomic.Value(u32),
9319 futures: []const *Io.AnyFuture,
9320) ?usize {
9321 var completed_index: ?usize = null;
9322 var expect_completed: u32 = 0;
9323 for (futures, 0..) |any_future, i| {
9324 const future: *Future = @ptrCast(@alignCast(any_future));
9325 // This operation will convert `.pending_awaited` to `.pending`, or leave `.done` untouched.
9326 switch (future.status.fetchAnd(
9327 .{ .tag = @enumFromInt(0b10), .thread = .all_ones },
9328 .monotonic,
9329 ).tag) {
9330 .pending_awaited => {},
9331 .pending => unreachable,
9332 .pending_canceled => unreachable,
9333 .done => {
9334 expect_completed += 1;
9335 completed_index = i;
9336 },
9337 }
9338 }
9339 // If any future has just finished, wait for it to signal `num_completed` to avoid dangling
9340 // references to stack memory.
9341 while (true) {
9342 const n = num_completed.load(.acquire);
9343 if (n == expect_completed) break;
9344 assert(n < expect_completed);
9345 Thread.futexWaitUncancelable(&num_completed.raw, n, null);
85829346 }
8583 return result.?;
9347 return completed_index;
85849348}
85859349
85869350fn netListenIpPosix(
......@@ -8590,37 +9354,37 @@ fn netListenIpPosix(
85909354) IpAddress.ListenError!net.Server {
85919355 if (!have_networking) return error.NetworkDown;
85929356 const t: *Threaded = @ptrCast(@alignCast(userdata));
8593 const current_thread = Thread.getCurrent(t);
9357 _ = t;
85949358 const family = posixAddressFamily(&address);
8595 const socket_fd = try openSocketPosix(current_thread, family, .{
9359 const socket_fd = try openSocketPosix(family, .{
85969360 .mode = options.mode,
85979361 .protocol = options.protocol,
85989362 });
85999363 errdefer posix.close(socket_fd);
86009364
86019365 if (options.reuse_address) {
8602 try setSocketOption(current_thread, socket_fd, posix.SOL.SOCKET, posix.SO.REUSEADDR, 1);
9366 try setSocketOption(socket_fd, posix.SOL.SOCKET, posix.SO.REUSEADDR, 1);
86039367 if (@hasDecl(posix.SO, "REUSEPORT"))
8604 try setSocketOption(current_thread, socket_fd, posix.SOL.SOCKET, posix.SO.REUSEPORT, 1);
9368 try setSocketOption(socket_fd, posix.SOL.SOCKET, posix.SO.REUSEPORT, 1);
86059369 }
86069370
86079371 var storage: PosixAddress = undefined;
86089372 var addr_len = addressToPosix(&address, &storage);
8609 try posixBind(current_thread, socket_fd, &storage.any, addr_len);
9373 try posixBind(socket_fd, &storage.any, addr_len);
86109374
8611 try current_thread.beginSyscall();
9375 const syscall: Syscall = try .start();
86129376 while (true) {
86139377 switch (posix.errno(posix.system.listen(socket_fd, options.kernel_backlog))) {
86149378 .SUCCESS => {
8615 current_thread.endSyscall();
9379 syscall.finish();
86169380 break;
86179381 },
86189382 .INTR => {
8619 try current_thread.checkCancel();
9383 try syscall.checkCancel();
86209384 continue;
86219385 },
86229386 else => |e| {
8623 current_thread.endSyscall();
9387 syscall.finish();
86249388 switch (e) {
86259389 .ADDRINUSE => return error.AddressInUse,
86269390 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
......@@ -8630,7 +9394,7 @@ fn netListenIpPosix(
86309394 }
86319395 }
86329396
8633 try posixGetSockName(current_thread, socket_fd, &storage.any, &addr_len);
9397 try posixGetSockName(socket_fd, &storage.any, &addr_len);
86349398 return .{
86359399 .socket = .{
86369400 .handle = socket_fd,
......@@ -8646,9 +9410,8 @@ fn netListenIpWindows(
86469410) IpAddress.ListenError!net.Server {
86479411 if (!have_networking) return error.NetworkDown;
86489412 const t: *Threaded = @ptrCast(@alignCast(userdata));
8649 const current_thread = Thread.getCurrent(t);
86509413 const family = posixAddressFamily(&address);
8651 const socket_handle = try openSocketWsa(t, current_thread, family, .{
9414 const socket_handle = try openSocketWsa(t, family, .{
86529415 .mode = options.mode,
86539416 .protocol = options.protocol,
86549417 });
......@@ -8660,27 +9423,27 @@ fn netListenIpWindows(
86609423 var storage: WsaAddress = undefined;
86619424 var addr_len = addressToWsa(&address, &storage);
86629425
8663 try current_thread.beginSyscall();
9426 var syscall: Syscall = try .start();
86649427 while (true) {
86659428 const rc = ws2_32.bind(socket_handle, &storage.any, addr_len);
86669429 if (rc != ws2_32.SOCKET_ERROR) {
8667 current_thread.endSyscall();
9430 syscall.finish();
86689431 break;
86699432 }
86709433 switch (ws2_32.WSAGetLastError()) {
8671 .EINTR => {
8672 try current_thread.checkCancel();
8673 continue;
8674 },
86759434 .NOTINITIALISED => {
9435 syscall.finish();
86769436 try initializeWsa(t);
8677 try current_thread.checkCancel();
9437 syscall = try .start();
9438 continue;
9439 },
9440 .EINTR, .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => {
9441 try syscall.checkCancel();
86789442 continue;
86799443 },
86809444 else => |e| {
8681 current_thread.endSyscall();
9445 syscall.finish();
86829446 switch (e) {
8683 .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled,
86849447 .EADDRINUSE => return error.AddressInUse,
86859448 .EADDRNOTAVAIL => return error.AddressUnavailable,
86869449 .ENOTSOCK => |err| return wsaErrorBug(err),
......@@ -8694,27 +9457,27 @@ fn netListenIpWindows(
86949457 }
86959458 }
86969459
8697 try current_thread.beginSyscall();
9460 syscall = try .start();
86989461 while (true) {
86999462 const rc = ws2_32.listen(socket_handle, options.kernel_backlog);
87009463 if (rc != ws2_32.SOCKET_ERROR) {
8701 current_thread.endSyscall();
9464 syscall.finish();
87029465 break;
87039466 }
87049467 switch (ws2_32.WSAGetLastError()) {
8705 .EINTR => {
8706 try current_thread.checkCancel();
8707 continue;
8708 },
87099468 .NOTINITIALISED => {
9469 syscall.finish();
87109470 try initializeWsa(t);
8711 try current_thread.checkCancel();
9471 syscall = try .start();
9472 continue;
9473 },
9474 .EINTR, .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => {
9475 try syscall.checkCancel();
87129476 continue;
87139477 },
87149478 else => |e| {
8715 current_thread.endSyscall();
9479 syscall.finish();
87169480 switch (e) {
8717 .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled,
87189481 .ENETDOWN => return error.NetworkDown,
87199482 .EADDRINUSE => return error.AddressInUse,
87209483 .EISCONN => |err| return wsaErrorBug(err),
......@@ -8729,7 +9492,7 @@ fn netListenIpWindows(
87299492 }
87309493 }
87319494
8732 try wsaGetSockName(t, current_thread, socket_handle, &storage.any, &addr_len);
9495 try wsaGetSockName(t, socket_handle, &storage.any, &addr_len);
87339496
87349497 return .{
87359498 .socket = .{
......@@ -8757,8 +9520,8 @@ fn netListenUnixPosix(
87579520) net.UnixAddress.ListenError!net.Socket.Handle {
87589521 if (!net.has_unix_sockets) return error.AddressFamilyUnsupported;
87599522 const t: *Threaded = @ptrCast(@alignCast(userdata));
8760 const current_thread = Thread.getCurrent(t);
8761 const socket_fd = openSocketPosix(current_thread, posix.AF.UNIX, .{ .mode = .stream }) catch |err| switch (err) {
9523 _ = t;
9524 const socket_fd = openSocketPosix(posix.AF.UNIX, .{ .mode = .stream }) catch |err| switch (err) {
87629525 error.ProtocolUnsupportedBySystem => return error.AddressFamilyUnsupported,
87639526 error.ProtocolUnsupportedByAddressFamily => return error.AddressFamilyUnsupported,
87649527 error.SocketModeUnsupported => return error.AddressFamilyUnsupported,
......@@ -8769,21 +9532,21 @@ fn netListenUnixPosix(
87699532
87709533 var storage: UnixAddress = undefined;
87719534 const addr_len = addressUnixToPosix(address, &storage);
8772 try posixBindUnix(current_thread, socket_fd, &storage.any, addr_len);
9535 try posixBindUnix(socket_fd, &storage.any, addr_len);
87739536
8774 try current_thread.beginSyscall();
9537 const syscall: Syscall = try .start();
87759538 while (true) {
87769539 switch (posix.errno(posix.system.listen(socket_fd, options.kernel_backlog))) {
87779540 .SUCCESS => {
8778 current_thread.endSyscall();
9541 syscall.finish();
87799542 break;
87809543 },
87819544 .INTR => {
8782 try current_thread.checkCancel();
9545 try syscall.checkCancel();
87839546 continue;
87849547 },
87859548 else => |e| {
8786 current_thread.endSyscall();
9549 syscall.finish();
87879550 switch (e) {
87889551 .ADDRINUSE => return error.AddressInUse,
87899552 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
......@@ -8803,9 +9566,8 @@ fn netListenUnixWindows(
88039566) net.UnixAddress.ListenError!net.Socket.Handle {
88049567 if (!net.has_unix_sockets) return error.AddressFamilyUnsupported;
88059568 const t: *Threaded = @ptrCast(@alignCast(userdata));
8806 const current_thread = Thread.getCurrent(t);
88079569
8808 const socket_handle = openSocketWsa(t, current_thread, posix.AF.UNIX, .{ .mode = .stream }) catch |err| switch (err) {
9570 const socket_handle = openSocketWsa(t, posix.AF.UNIX, .{ .mode = .stream }) catch |err| switch (err) {
88099571 error.ProtocolUnsupportedByAddressFamily => return error.AddressFamilyUnsupported,
88109572 else => |e| return e,
88119573 };
......@@ -8814,24 +9576,24 @@ fn netListenUnixWindows(
88149576 var storage: WsaAddress = undefined;
88159577 const addr_len = addressUnixToWsa(address, &storage);
88169578
8817 try current_thread.beginSyscall();
9579 var syscall: Syscall = try .start();
88189580 while (true) {
88199581 const rc = ws2_32.bind(socket_handle, &storage.any, addr_len);
88209582 if (rc != ws2_32.SOCKET_ERROR) break;
88219583 switch (ws2_32.WSAGetLastError()) {
8822 .EINTR => {
8823 try current_thread.checkCancel();
8824 continue;
8825 },
88269584 .NOTINITIALISED => {
9585 syscall.finish();
88279586 try initializeWsa(t);
8828 try current_thread.checkCancel();
9587 syscall = try .start();
9588 continue;
9589 },
9590 .EINTR, .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => {
9591 try syscall.checkCancel();
88299592 continue;
88309593 },
88319594 else => |e| {
8832 current_thread.endSyscall();
9595 syscall.finish();
88339596 switch (e) {
8834 .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled,
88359597 .EADDRINUSE => return error.AddressInUse,
88369598 .EADDRNOTAVAIL => return error.AddressUnavailable,
88379599 .ENOTSOCK => |err| return wsaErrorBug(err),
......@@ -8846,22 +9608,23 @@ fn netListenUnixWindows(
88469608 }
88479609
88489610 while (true) {
8849 try current_thread.checkCancel();
9611 try syscall.checkCancel();
88509612 const rc = ws2_32.listen(socket_handle, options.kernel_backlog);
88519613 if (rc != ws2_32.SOCKET_ERROR) {
8852 current_thread.endSyscall();
9614 syscall.finish();
88539615 return socket_handle;
88549616 }
88559617 switch (ws2_32.WSAGetLastError()) {
8856 .EINTR => continue,
9618 .EINTR, .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => continue,
88579619 .NOTINITIALISED => {
9620 syscall.finish();
88589621 try initializeWsa(t);
9622 syscall = try .start();
88599623 continue;
88609624 },
88619625 else => |e| {
8862 current_thread.endSyscall();
9626 syscall.finish();
88639627 switch (e) {
8864 .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled,
88659628 .ENETDOWN => return error.NetworkDown,
88669629 .EADDRINUSE => return error.AddressInUse,
88679630 .EISCONN => |err| return wsaErrorBug(err),
......@@ -8889,24 +9652,23 @@ fn netListenUnixUnavailable(
88899652}
88909653
88919654fn posixBindUnix(
8892 current_thread: *Thread,
88939655 fd: posix.socket_t,
88949656 addr: *const posix.sockaddr,
88959657 addr_len: posix.socklen_t,
88969658) !void {
8897 try current_thread.beginSyscall();
9659 const syscall: Syscall = try .start();
88989660 while (true) {
88999661 switch (posix.errno(posix.system.bind(fd, addr, addr_len))) {
89009662 .SUCCESS => {
8901 current_thread.endSyscall();
9663 syscall.finish();
89029664 break;
89039665 },
89049666 .INTR => {
8905 try current_thread.checkCancel();
9667 try syscall.checkCancel();
89069668 continue;
89079669 },
89089670 else => |e| {
8909 current_thread.endSyscall();
9671 syscall.finish();
89109672 switch (e) {
89119673 .ACCES => return error.AccessDenied,
89129674 .ADDRINUSE => return error.AddressInUse,
......@@ -8933,24 +9695,23 @@ fn posixBindUnix(
89339695}
89349696
89359697fn posixBind(
8936 current_thread: *Thread,
89379698 socket_fd: posix.socket_t,
89389699 addr: *const posix.sockaddr,
89399700 addr_len: posix.socklen_t,
89409701) !void {
8941 try current_thread.beginSyscall();
9702 const syscall: Syscall = try .start();
89429703 while (true) {
89439704 switch (posix.errno(posix.system.bind(socket_fd, addr, addr_len))) {
89449705 .SUCCESS => {
8945 current_thread.endSyscall();
9706 syscall.finish();
89469707 break;
89479708 },
89489709 .INTR => {
8949 try current_thread.checkCancel();
9710 try syscall.checkCancel();
89509711 continue;
89519712 },
89529713 else => |e| {
8953 current_thread.endSyscall();
9714 syscall.finish();
89549715 switch (e) {
89559716 .ADDRINUSE => return error.AddressInUse,
89569717 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
......@@ -8968,24 +9729,23 @@ fn posixBind(
89689729}
89699730
89709731fn posixConnect(
8971 current_thread: *Thread,
89729732 socket_fd: posix.socket_t,
89739733 addr: *const posix.sockaddr,
89749734 addr_len: posix.socklen_t,
89759735) !void {
8976 try current_thread.beginSyscall();
9736 const syscall: Syscall = try .start();
89779737 while (true) {
89789738 switch (posix.errno(posix.system.connect(socket_fd, addr, addr_len))) {
89799739 .SUCCESS => {
8980 current_thread.endSyscall();
9740 syscall.finish();
89819741 return;
89829742 },
89839743 .INTR => {
8984 try current_thread.checkCancel();
9744 try syscall.checkCancel();
89859745 continue;
89869746 },
89879747 else => |e| {
8988 current_thread.endSyscall();
9748 syscall.finish();
89899749 switch (e) {
89909750 .ADDRNOTAVAIL => return error.AddressUnavailable,
89919751 .AFNOSUPPORT => return error.AddressFamilyUnsupported,
......@@ -9014,24 +9774,23 @@ fn posixConnect(
90149774}
90159775
90169776fn posixConnectUnix(
9017 current_thread: *Thread,
90189777 fd: posix.socket_t,
90199778 addr: *const posix.sockaddr,
90209779 addr_len: posix.socklen_t,
90219780) !void {
9022 try current_thread.beginSyscall();
9781 const syscall: Syscall = try .start();
90239782 while (true) {
90249783 switch (posix.errno(posix.system.connect(fd, addr, addr_len))) {
90259784 .SUCCESS => {
9026 current_thread.endSyscall();
9785 syscall.finish();
90279786 return;
90289787 },
90299788 .INTR => {
9030 try current_thread.checkCancel();
9789 try syscall.checkCancel();
90319790 continue;
90329791 },
90339792 else => |e| {
9034 current_thread.endSyscall();
9793 syscall.finish();
90359794 switch (e) {
90369795 .AFNOSUPPORT => return error.AddressFamilyUnsupported,
90379796 .AGAIN => return error.WouldBlock,
......@@ -9058,24 +9817,23 @@ fn posixConnectUnix(
90589817}
90599818
90609819fn posixGetSockName(
9061 current_thread: *Thread,
90629820 socket_fd: posix.fd_t,
90639821 addr: *posix.sockaddr,
90649822 addr_len: *posix.socklen_t,
90659823) !void {
9066 try current_thread.beginSyscall();
9824 const syscall: Syscall = try .start();
90679825 while (true) {
90689826 switch (posix.errno(posix.system.getsockname(socket_fd, addr, addr_len))) {
90699827 .SUCCESS => {
9070 current_thread.endSyscall();
9828 syscall.finish();
90719829 break;
90729830 },
90739831 .INTR => {
9074 try current_thread.checkCancel();
9832 try syscall.checkCancel();
90759833 continue;
90769834 },
90779835 else => |e| {
9078 current_thread.endSyscall();
9836 syscall.finish();
90799837 switch (e) {
90809838 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
90819839 .FAULT => |err| return errnoBug(err),
......@@ -9091,32 +9849,31 @@ fn posixGetSockName(
90919849
90929850fn wsaGetSockName(
90939851 t: *Threaded,
9094 current_thread: *Thread,
90959852 handle: ws2_32.SOCKET,
90969853 addr: *ws2_32.sockaddr,
90979854 addr_len: *i32,
90989855) !void {
9099 try current_thread.beginSyscall();
9856 var syscall: Syscall = try .start();
91009857 while (true) {
91019858 const rc = ws2_32.getsockname(handle, addr, addr_len);
91029859 if (rc != ws2_32.SOCKET_ERROR) {
9103 current_thread.endSyscall();
9860 syscall.finish();
91049861 return;
91059862 }
91069863 switch (ws2_32.WSAGetLastError()) {
9107 .EINTR => {
9108 try current_thread.checkCancel();
9864 .EINTR, .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => {
9865 try syscall.checkCancel();
91099866 continue;
91109867 },
91119868 .NOTINITIALISED => {
9869 syscall.finish();
91129870 try initializeWsa(t);
9113 try current_thread.checkCancel();
9871 syscall = try .start();
91149872 continue;
91159873 },
91169874 else => |e| {
9117 current_thread.endSyscall();
9875 syscall.finish();
91189876 switch (e) {
9119 .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled,
91209877 .ENETDOWN => return error.NetworkDown,
91219878 .EFAULT => |err| return wsaErrorBug(err),
91229879 .ENOTSOCK => |err| return wsaErrorBug(err),
......@@ -9128,21 +9885,21 @@ fn wsaGetSockName(
91289885 }
91299886}
91309887
9131fn setSocketOption(current_thread: *Thread, fd: posix.fd_t, level: i32, opt_name: u32, option: u32) !void {
9888fn setSocketOption(fd: posix.fd_t, level: i32, opt_name: u32, option: u32) !void {
91329889 const o: []const u8 = @ptrCast(&option);
9133 try current_thread.beginSyscall();
9890 const syscall: Syscall = try .start();
91349891 while (true) {
91359892 switch (posix.errno(posix.system.setsockopt(fd, level, opt_name, o.ptr, @intCast(o.len)))) {
91369893 .SUCCESS => {
9137 current_thread.endSyscall();
9894 syscall.finish();
91389895 return;
91399896 },
91409897 .INTR => {
9141 try current_thread.checkCancel();
9898 try syscall.checkCancel();
91429899 continue;
91439900 },
91449901 else => |e| {
9145 current_thread.endSyscall();
9902 syscall.finish();
91469903 switch (e) {
91479904 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
91489905 .NOTSOCK => |err| return errnoBug(err),
......@@ -9157,21 +9914,30 @@ fn setSocketOption(current_thread: *Thread, fd: posix.fd_t, level: i32, opt_name
91579914
91589915fn setSocketOptionWsa(t: *Threaded, socket: Io.net.Socket.Handle, level: i32, opt_name: u32, option: u32) !void {
91599916 const o: []const u8 = @ptrCast(&option);
9917 var syscall: Syscall = try .start();
91609918 const rc = ws2_32.setsockopt(socket, level, @bitCast(opt_name), o.ptr, @intCast(o.len));
91619919 while (true) {
9162 if (rc != ws2_32.SOCKET_ERROR) return;
9920 if (rc != ws2_32.SOCKET_ERROR) return syscall.finish();
91639921 switch (ws2_32.WSAGetLastError()) {
9164 .EINTR => continue,
9165 .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled,
9922 .EINTR, .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => {
9923 try syscall.checkCancel();
9924 continue;
9925 },
91669926 .NOTINITIALISED => {
9927 syscall.finish();
91679928 try initializeWsa(t);
9929 syscall = try .start();
91689930 continue;
91699931 },
9170 .ENETDOWN => return error.NetworkDown,
9171 .EFAULT => |err| return wsaErrorBug(err),
9172 .ENOTSOCK => |err| return wsaErrorBug(err),
9173 .EINVAL => |err| return wsaErrorBug(err),
9174 else => |err| return windows.unexpectedWSAError(err),
9932 .ENETDOWN => return syscall.fail(error.NetworkDown),
9933 .EFAULT, .ENOTSOCK, .EINVAL => |err| {
9934 syscall.finish();
9935 return wsaErrorBug(err);
9936 },
9937 else => |err| {
9938 syscall.finish();
9939 return windows.unexpectedWSAError(err);
9940 },
91759941 }
91769942 }
91779943}
......@@ -9184,17 +9950,17 @@ fn netConnectIpPosix(
91849950 if (!have_networking) return error.NetworkDown;
91859951 if (options.timeout != .none) @panic("TODO implement netConnectIpPosix with timeout");
91869952 const t: *Threaded = @ptrCast(@alignCast(userdata));
9187 const current_thread = Thread.getCurrent(t);
9953 _ = t;
91889954 const family = posixAddressFamily(address);
9189 const socket_fd = try openSocketPosix(current_thread, family, .{
9955 const socket_fd = try openSocketPosix(family, .{
91909956 .mode = options.mode,
91919957 .protocol = options.protocol,
91929958 });
91939959 errdefer posix.close(socket_fd);
91949960 var storage: PosixAddress = undefined;
91959961 var addr_len = addressToPosix(address, &storage);
9196 try posixConnect(current_thread, socket_fd, &storage.any, addr_len);
9197 try posixGetSockName(current_thread, socket_fd, &storage.any, &addr_len);
9962 try posixConnect(socket_fd, &storage.any, addr_len);
9963 try posixGetSockName(socket_fd, &storage.any, &addr_len);
91989964 return .{ .socket = .{
91999965 .handle = socket_fd,
92009966 .address = addressFromPosix(&storage),
......@@ -9209,9 +9975,8 @@ fn netConnectIpWindows(
92099975 if (!have_networking) return error.NetworkDown;
92109976 if (options.timeout != .none) @panic("TODO implement netConnectIpWindows with timeout");
92119977 const t: *Threaded = @ptrCast(@alignCast(userdata));
9212 const current_thread = Thread.getCurrent(t);
92139978 const family = posixAddressFamily(address);
9214 const socket_handle = try openSocketWsa(t, current_thread, family, .{
9979 const socket_handle = try openSocketWsa(t, family, .{
92159980 .mode = options.mode,
92169981 .protocol = options.protocol,
92179982 });
......@@ -9220,27 +9985,27 @@ fn netConnectIpWindows(
92209985 var storage: WsaAddress = undefined;
92219986 var addr_len = addressToWsa(address, &storage);
92229987
9223 try current_thread.beginSyscall();
9988 var syscall: Syscall = try .start();
92249989 while (true) {
92259990 const rc = ws2_32.connect(socket_handle, &storage.any, addr_len);
92269991 if (rc != ws2_32.SOCKET_ERROR) {
9227 current_thread.endSyscall();
9992 syscall.finish();
92289993 break;
92299994 }
92309995 switch (ws2_32.WSAGetLastError()) {
9231 .EINTR => {
9232 try current_thread.checkCancel();
9996 .EINTR, .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => {
9997 try syscall.checkCancel();
92339998 continue;
92349999 },
923510000 .NOTINITIALISED => {
10001 syscall.finish();
923610002 try initializeWsa(t);
9237 try current_thread.checkCancel();
10003 syscall = try .start();
923810004 continue;
923910005 },
924010006 else => |e| {
9241 current_thread.endSyscall();
10007 syscall.finish();
924210008 switch (e) {
9243 .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled,
924410009 .EADDRNOTAVAIL => return error.AddressUnavailable,
924510010 .ECONNREFUSED => return error.ConnectionRefused,
924610011 .ECONNRESET => return error.ConnectionResetByPeer,
......@@ -9261,7 +10026,7 @@ fn netConnectIpWindows(
926110026 }
926210027 }
926310028
9264 try wsaGetSockName(t, current_thread, socket_handle, &storage.any, &addr_len);
10029 try wsaGetSockName(t, socket_handle, &storage.any, &addr_len);
926510030
926610031 return .{ .socket = .{
926710032 .handle = socket_handle,
......@@ -9286,15 +10051,15 @@ fn netConnectUnixPosix(
928610051) net.UnixAddress.ConnectError!net.Socket.Handle {
928710052 if (!net.has_unix_sockets) return error.AddressFamilyUnsupported;
928810053 const t: *Threaded = @ptrCast(@alignCast(userdata));
9289 const current_thread = Thread.getCurrent(t);
9290 const socket_fd = openSocketPosix(current_thread, posix.AF.UNIX, .{ .mode = .stream }) catch |err| switch (err) {
10054 _ = t;
10055 const socket_fd = openSocketPosix(posix.AF.UNIX, .{ .mode = .stream }) catch |err| switch (err) {
929110056 error.OptionUnsupported => return error.Unexpected,
929210057 else => |e| return e,
929310058 };
929410059 errdefer posix.close(socket_fd);
929510060 var storage: UnixAddress = undefined;
929610061 const addr_len = addressUnixToPosix(address, &storage);
9297 try posixConnectUnix(current_thread, socket_fd, &storage.any, addr_len);
10062 try posixConnectUnix(socket_fd, &storage.any, addr_len);
929810063 return socket_fd;
929910064}
930010065
......@@ -9304,34 +10069,42 @@ fn netConnectUnixWindows(
930410069) net.UnixAddress.ConnectError!net.Socket.Handle {
930510070 if (!net.has_unix_sockets) return error.AddressFamilyUnsupported;
930610071 const t: *Threaded = @ptrCast(@alignCast(userdata));
9307 const current_thread = Thread.getCurrent(t);
930810072
9309 const socket_handle = try openSocketWsa(t, current_thread, posix.AF.UNIX, .{ .mode = .stream });
10073 const socket_handle = try openSocketWsa(t, posix.AF.UNIX, .{ .mode = .stream });
931010074 errdefer closeSocketWindows(socket_handle);
931110075 var storage: WsaAddress = undefined;
931210076 const addr_len = addressUnixToWsa(address, &storage);
931310077
10078 var syscall: Syscall = try .start();
931410079 while (true) {
931510080 const rc = ws2_32.connect(socket_handle, &storage.any, addr_len);
931610081 if (rc != ws2_32.SOCKET_ERROR) break;
931710082 switch (ws2_32.WSAGetLastError()) {
9318 .EINTR => continue,
9319 .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled,
10083 .EINTR, .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => {
10084 try syscall.checkCancel();
10085 continue;
10086 },
932010087 .NOTINITIALISED => {
10088 syscall.finish();
932110089 try initializeWsa(t);
10090 syscall = try .start();
932210091 continue;
932310092 },
9324
9325 .ECONNREFUSED => return error.FileNotFound,
9326 .EFAULT => |err| return wsaErrorBug(err),
9327 .EINVAL => |err| return wsaErrorBug(err),
9328 .EISCONN => |err| return wsaErrorBug(err),
9329 .ENOTSOCK => |err| return wsaErrorBug(err),
9330 .EWOULDBLOCK => return error.WouldBlock,
9331 .EACCES => return error.AccessDenied,
9332 .ENOBUFS => return error.SystemResources,
9333 .EAFNOSUPPORT => return error.AddressFamilyUnsupported,
9334 else => |err| return windows.unexpectedWSAError(err),
10093 else => |e| {
10094 syscall.finish();
10095 switch (e) {
10096 .ECONNREFUSED => return error.FileNotFound,
10097 .EFAULT => |err| return wsaErrorBug(err),
10098 .EINVAL => |err| return wsaErrorBug(err),
10099 .EISCONN => |err| return wsaErrorBug(err),
10100 .ENOTSOCK => |err| return wsaErrorBug(err),
10101 .EWOULDBLOCK => return error.WouldBlock,
10102 .EACCES => return error.AccessDenied,
10103 .ENOBUFS => return error.SystemResources,
10104 .EAFNOSUPPORT => return error.AddressFamilyUnsupported,
10105 else => |err| return windows.unexpectedWSAError(err),
10106 }
10107 },
933510108 }
933610109 }
933710110
......@@ -9354,14 +10127,14 @@ fn netBindIpPosix(
935410127) IpAddress.BindError!net.Socket {
935510128 if (!have_networking) return error.NetworkDown;
935610129 const t: *Threaded = @ptrCast(@alignCast(userdata));
9357 const current_thread = Thread.getCurrent(t);
10130 _ = t;
935810131 const family = posixAddressFamily(address);
9359 const socket_fd = try openSocketPosix(current_thread, family, options);
10132 const socket_fd = try openSocketPosix(family, options);
936010133 errdefer posix.close(socket_fd);
936110134 var storage: PosixAddress = undefined;
936210135 var addr_len = addressToPosix(address, &storage);
9363 try posixBind(current_thread, socket_fd, &storage.any, addr_len);
9364 try posixGetSockName(current_thread, socket_fd, &storage.any, &addr_len);
10136 try posixBind(socket_fd, &storage.any, addr_len);
10137 try posixGetSockName(socket_fd, &storage.any, &addr_len);
936510138 return .{
936610139 .handle = socket_fd,
936710140 .address = addressFromPosix(&storage),
......@@ -9375,9 +10148,8 @@ fn netBindIpWindows(
937510148) IpAddress.BindError!net.Socket {
937610149 if (!have_networking) return error.NetworkDown;
937710150 const t: *Threaded = @ptrCast(@alignCast(userdata));
9378 const current_thread = Thread.getCurrent(t);
937910151 const family = posixAddressFamily(address);
9380 const socket_handle = try openSocketWsa(t, current_thread, family, .{
10152 const socket_handle = try openSocketWsa(t, family, .{
938110153 .mode = options.mode,
938210154 .protocol = options.protocol,
938310155 });
......@@ -9386,27 +10158,27 @@ fn netBindIpWindows(
938610158 var storage: WsaAddress = undefined;
938710159 var addr_len = addressToWsa(address, &storage);
938810160
9389 try current_thread.beginSyscall();
10161 var syscall: Syscall = try .start();
939010162 while (true) {
939110163 const rc = ws2_32.bind(socket_handle, &storage.any, addr_len);
939210164 if (rc != ws2_32.SOCKET_ERROR) {
9393 current_thread.endSyscall();
10165 syscall.finish();
939410166 break;
939510167 }
939610168 switch (ws2_32.WSAGetLastError()) {
9397 .EINTR => {
9398 try current_thread.checkCancel();
10169 .EINTR, .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => {
10170 try syscall.checkCancel();
939910171 continue;
940010172 },
940110173 .NOTINITIALISED => {
10174 syscall.finish();
940210175 try initializeWsa(t);
9403 try current_thread.checkCancel();
10176 syscall = try .start();
940410177 continue;
940510178 },
940610179 else => |e| {
9407 current_thread.endSyscall();
10180 syscall.finish();
940810181 switch (e) {
9409 .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled,
941010182 .EADDRINUSE => return error.AddressInUse,
941110183 .EADDRNOTAVAIL => return error.AddressUnavailable,
941210184 .ENOTSOCK => |err| return wsaErrorBug(err),
......@@ -9420,7 +10192,7 @@ fn netBindIpWindows(
942010192 }
942110193 }
942210194
9423 try wsaGetSockName(t, current_thread, socket_handle, &storage.any, &addr_len);
10195 try wsaGetSockName(t, socket_handle, &storage.any, &addr_len);
942410196
942510197 return .{
942610198 .handle = socket_handle,
......@@ -9440,7 +10212,6 @@ fn netBindIpUnavailable(
944010212}
944110213
944210214fn openSocketPosix(
9443 current_thread: *Thread,
944410215 family: posix.sa_family_t,
944510216 options: IpAddress.BindOptions,
944610217) error{
......@@ -9457,7 +10228,7 @@ fn openSocketPosix(
945710228}!posix.socket_t {
945810229 const mode = posixSocketMode(options.mode);
945910230 const protocol = posixProtocol(options.protocol);
9460 try current_thread.beginSyscall();
10231 const syscall: Syscall = try .start();
946110232 const socket_fd = while (true) {
946210233 const flags: u32 = mode | if (socket_flags_unsupported) 0 else posix.SOCK.CLOEXEC;
946310234 const socket_rc = posix.system.socket(family, flags, protocol);
......@@ -9466,25 +10237,25 @@ fn openSocketPosix(
946610237 const fd: posix.fd_t = @intCast(socket_rc);
946710238 errdefer posix.close(fd);
946810239 if (socket_flags_unsupported) while (true) {
9469 try current_thread.checkCancel();
10240 try syscall.checkCancel();
947010241 switch (posix.errno(posix.system.fcntl(fd, posix.F.SETFD, @as(usize, posix.FD_CLOEXEC)))) {
947110242 .SUCCESS => break,
947210243 .INTR => continue,
947310244 else => |err| {
9474 current_thread.endSyscall();
10245 syscall.finish();
947510246 return posix.unexpectedErrno(err);
947610247 },
947710248 }
947810249 };
9479 current_thread.endSyscall();
10250 syscall.finish();
948010251 break fd;
948110252 },
948210253 .INTR => {
9483 try current_thread.checkCancel();
10254 try syscall.checkCancel();
948410255 continue;
948510256 },
948610257 else => |e| {
9487 current_thread.endSyscall();
10258 syscall.finish();
948810259 switch (e) {
948910260 .AFNOSUPPORT => return error.AddressFamilyUnsupported,
949010261 .INVAL => return error.ProtocolUnsupportedBySystem,
......@@ -9503,7 +10274,7 @@ fn openSocketPosix(
950310274
950410275 if (options.ip6_only) {
950510276 if (posix.IPV6 == void) return error.OptionUnsupported;
9506 try setSocketOption(current_thread, socket_fd, posix.IPPROTO.IPV6, posix.IPV6.V6ONLY, 0);
10277 try setSocketOption(socket_fd, posix.IPPROTO.IPV6, posix.IPV6.V6ONLY, 0);
950710278 }
950810279
950910280 return socket_fd;
......@@ -9511,34 +10282,33 @@ fn openSocketPosix(
951110282
951210283fn openSocketWsa(
951310284 t: *Threaded,
9514 current_thread: *Thread,
951510285 family: posix.sa_family_t,
951610286 options: IpAddress.BindOptions,
951710287) !ws2_32.SOCKET {
951810288 const mode = posixSocketMode(options.mode);
951910289 const protocol = posixProtocol(options.protocol);
952010290 const flags: u32 = ws2_32.WSA_FLAG_OVERLAPPED | ws2_32.WSA_FLAG_NO_HANDLE_INHERIT;
9521 try current_thread.beginSyscall();
10291 var syscall: Syscall = try .start();
952210292 while (true) {
952310293 const rc = ws2_32.WSASocketW(family, @bitCast(mode), @bitCast(protocol), null, 0, flags);
952410294 if (rc != ws2_32.INVALID_SOCKET) {
9525 current_thread.endSyscall();
10295 syscall.finish();
952610296 return rc;
952710297 }
952810298 switch (ws2_32.WSAGetLastError()) {
9529 .EINTR => {
9530 try current_thread.checkCancel();
10299 .EINTR, .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => {
10300 try syscall.checkCancel();
953110301 continue;
953210302 },
953310303 .NOTINITIALISED => {
10304 syscall.finish();
953410305 try initializeWsa(t);
9535 try current_thread.checkCancel();
10306 syscall = try .start();
953610307 continue;
953710308 },
953810309 else => |e| {
9539 current_thread.endSyscall();
10310 syscall.finish();
954010311 switch (e) {
9541 .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled,
954210312 .EAFNOSUPPORT => return error.AddressFamilyUnsupported,
954310313 .EMFILE => return error.ProcessFdQuotaExceeded,
954410314 .ENOBUFS => return error.SystemResources,
......@@ -9553,10 +10323,10 @@ fn openSocketWsa(
955310323fn netAcceptPosix(userdata: ?*anyopaque, listen_fd: net.Socket.Handle) net.Server.AcceptError!net.Stream {
955410324 if (!have_networking) return error.NetworkDown;
955510325 const t: *Threaded = @ptrCast(@alignCast(userdata));
9556 const current_thread = Thread.getCurrent(t);
10326 _ = t;
955710327 var storage: PosixAddress = undefined;
955810328 var addr_len: posix.socklen_t = @sizeOf(PosixAddress);
9559 try current_thread.beginSyscall();
10329 const syscall: Syscall = try .start();
956010330 const fd = while (true) {
956110331 const rc = if (have_accept4)
956210332 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
956710337 const fd: posix.fd_t = @intCast(rc);
956810338 errdefer posix.close(fd);
956910339 if (!have_accept4) while (true) {
9570 try current_thread.checkCancel();
10340 try syscall.checkCancel();
957110341 switch (posix.errno(posix.system.fcntl(fd, posix.F.SETFD, @as(usize, posix.FD_CLOEXEC)))) {
957210342 .SUCCESS => break,
957310343 .INTR => continue,
957410344 else => |err| {
9575 current_thread.endSyscall();
10345 syscall.finish();
957610346 return posix.unexpectedErrno(err);
957710347 },
957810348 }
957910349 };
9580 current_thread.endSyscall();
10350 syscall.finish();
958110351 break fd;
958210352 },
958310353 .INTR => {
9584 try current_thread.checkCancel();
10354 try syscall.checkCancel();
958510355 continue;
958610356 },
958710357 else => |e| {
9588 current_thread.endSyscall();
10358 syscall.finish();
958910359 switch (e) {
959010360 .AGAIN => |err| return errnoBug(err),
959110361 .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
961410384fn netAcceptWindows(userdata: ?*anyopaque, listen_handle: net.Socket.Handle) net.Server.AcceptError!net.Stream {
961510385 if (!have_networking) return error.NetworkDown;
961610386 const t: *Threaded = @ptrCast(@alignCast(userdata));
9617 const current_thread = Thread.getCurrent(t);
961810387 var storage: WsaAddress = undefined;
961910388 var addr_len: i32 = @sizeOf(WsaAddress);
9620 try current_thread.beginSyscall();
10389 var syscall: Syscall = try .start();
962110390 while (true) {
962210391 const rc = ws2_32.accept(listen_handle, &storage.any, &addr_len);
962310392 if (rc != ws2_32.INVALID_SOCKET) {
9624 current_thread.endSyscall();
10393 syscall.finish();
962510394 return .{ .socket = .{
962610395 .handle = rc,
962710396 .address = addressFromWsa(&storage),
962810397 } };
962910398 }
963010399 switch (ws2_32.WSAGetLastError()) {
9631 .EINTR => {
9632 try current_thread.checkCancel();
10400 .EINTR, .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => {
10401 try syscall.checkCancel();
963310402 continue;
963410403 },
963510404 .NOTINITIALISED => {
10405 syscall.finish();
963610406 try initializeWsa(t);
9637 try current_thread.checkCancel();
10407 syscall = try .start();
963810408 continue;
963910409 },
964010410 else => |e| {
9641 current_thread.endSyscall();
10411 syscall.finish();
964210412 switch (e) {
9643 .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled,
964410413 .ECONNRESET => return error.ConnectionAborted,
964510414 .EFAULT => |err| return wsaErrorBug(err),
964610415 .ENOTSOCK => |err| return wsaErrorBug(err),
......@@ -9665,7 +10434,7 @@ fn netAcceptUnavailable(userdata: ?*anyopaque, listen_handle: net.Socket.Handle)
966510434fn netReadPosix(userdata: ?*anyopaque, fd: net.Socket.Handle, data: [][]u8) net.Stream.Reader.Error!usize {
966610435 if (!have_networking) return error.NetworkDown;
966710436 const t: *Threaded = @ptrCast(@alignCast(userdata));
9668 const current_thread = Thread.getCurrent(t);
10437 _ = t;
966910438
967010439 var iovecs_buffer: [max_iovecs_len]posix.iovec = undefined;
967110440 var i: usize = 0;
......@@ -9680,20 +10449,20 @@ fn netReadPosix(userdata: ?*anyopaque, fd: net.Socket.Handle, data: [][]u8) net.
968010449 assert(dest[0].len > 0);
968110450
968210451 if (native_os == .wasi and !builtin.link_libc) {
9683 try current_thread.beginSyscall();
10452 const syscall: Syscall = try .start();
968410453 while (true) {
968510454 var n: usize = undefined;
968610455 switch (std.os.wasi.fd_read(fd, dest.ptr, dest.len, &n)) {
968710456 .SUCCESS => {
9688 current_thread.endSyscall();
10457 syscall.finish();
968910458 return n;
969010459 },
969110460 .INTR => {
9692 try current_thread.checkCancel();
10461 try syscall.checkCancel();
969310462 continue;
969410463 },
969510464 else => |e| {
9696 current_thread.endSyscall();
10465 syscall.finish();
969710466 switch (e) {
969810467 .INVAL => |err| return errnoBug(err),
969910468 .FAULT => |err| return errnoBug(err),
......@@ -9712,20 +10481,20 @@ fn netReadPosix(userdata: ?*anyopaque, fd: net.Socket.Handle, data: [][]u8) net.
971210481 }
971310482 }
971410483
9715 try current_thread.beginSyscall();
10484 const syscall: Syscall = try .start();
971610485 while (true) {
971710486 const rc = posix.system.readv(fd, dest.ptr, @intCast(dest.len));
971810487 switch (posix.errno(rc)) {
971910488 .SUCCESS => {
9720 current_thread.endSyscall();
10489 syscall.finish();
972110490 return @intCast(rc);
972210491 },
972310492 .INTR => {
9724 try current_thread.checkCancel();
10493 try syscall.checkCancel();
972510494 continue;
972610495 },
972710496 else => |e| {
9728 current_thread.endSyscall();
10497 syscall.finish();
972910498 switch (e) {
973010499 .INVAL => |err| return errnoBug(err),
973110500 .FAULT => |err| return errnoBug(err),
......@@ -9748,7 +10517,6 @@ fn netReadPosix(userdata: ?*anyopaque, fd: net.Socket.Handle, data: [][]u8) net.
974810517fn netReadWindows(userdata: ?*anyopaque, handle: net.Socket.Handle, data: [][]u8) net.Stream.Reader.Error!usize {
974910518 if (!have_networking) return error.NetworkDown;
975010519 const t: *Threaded = @ptrCast(@alignCast(userdata));
9751 const current_thread = Thread.getCurrent(t);
975210520
975310521 const bufs = b: {
975410522 var iovec_buffer: [max_iovecs_len]ws2_32.WSABUF = undefined;
......@@ -9775,48 +10543,41 @@ fn netReadWindows(userdata: ?*anyopaque, handle: net.Socket.Handle, data: [][]u8
977510543 break :b bufs;
977610544 };
977710545
10546 var syscall: Syscall = try .start();
977810547 while (true) {
9779 try current_thread.checkCancel();
9780
978110548 var flags: u32 = 0;
9782 var overlapped: windows.OVERLAPPED = std.mem.zeroes(windows.OVERLAPPED);
978310549 var n: u32 = undefined;
9784 const rc = ws2_32.WSARecv(handle, bufs.ptr, @intCast(bufs.len), &n, &flags, &overlapped, null);
9785 if (rc != ws2_32.SOCKET_ERROR) return n;
9786 const wsa_error: ws2_32.WinsockError = switch (ws2_32.WSAGetLastError()) {
9787 .IO_PENDING => e: {
9788 var result_flags: u32 = undefined;
9789 const overlapped_rc = ws2_32.WSAGetOverlappedResult(
9790 handle,
9791 &overlapped,
9792 &n,
9793 windows.TRUE,
9794 &result_flags,
9795 );
9796 if (overlapped_rc == windows.FALSE) {
9797 break :e ws2_32.WSAGetLastError();
9798 } else {
9799 return n;
9800 }
10550 const rc = ws2_32.WSARecv(handle, bufs.ptr, @intCast(bufs.len), &n, &flags, null, null);
10551 if (rc != ws2_32.SOCKET_ERROR) {
10552 syscall.finish();
10553 return n;
10554 }
10555 switch (ws2_32.WSAGetLastError()) {
10556 .EINTR, .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => {
10557 try syscall.checkCancel();
10558 continue;
980110559 },
9802 else => |err| err,
9803 };
9804 switch (wsa_error) {
9805 .EINTR => continue,
9806 .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled,
980710560 .NOTINITIALISED => {
10561 syscall.finish();
980810562 try initializeWsa(t);
10563 syscall = try .start();
980910564 continue;
981010565 },
981110566
9812 .ECONNRESET => return error.ConnectionResetByPeer,
10567 .ECONNRESET => return syscall.fail(error.ConnectionResetByPeer),
10568 .ENETDOWN => return syscall.fail(error.NetworkDown),
10569 .ENETRESET => return syscall.fail(error.ConnectionResetByPeer),
10570 .ENOTCONN => return syscall.fail(error.SocketUnconnected),
981310571 .EFAULT => unreachable, // a pointer is not completely contained in user address space.
9814 .EINVAL => |err| return wsaErrorBug(err),
9815 .EMSGSIZE => |err| return wsaErrorBug(err),
9816 .ENETDOWN => return error.NetworkDown,
9817 .ENETRESET => return error.ConnectionResetByPeer,
9818 .ENOTCONN => return error.SocketUnconnected,
9819 else => |err| return windows.unexpectedWSAError(err),
10572
10573 else => |err| {
10574 syscall.finish();
10575 switch (err) {
10576 .EINVAL => return wsaErrorBug(err),
10577 .EMSGSIZE => return wsaErrorBug(err),
10578 else => return windows.unexpectedWSAError(err),
10579 }
10580 },
982010581 }
982110582 }
982210583}
......@@ -9836,7 +10597,6 @@ fn netSendPosix(
983610597) struct { ?net.Socket.SendError, usize } {
983710598 if (!have_networking) return .{ error.NetworkDown, 0 };
983810599 const t: *Threaded = @ptrCast(@alignCast(userdata));
9839 const current_thread = Thread.getCurrent(t);
984010600
984110601 const posix_flags: u32 =
984210602 @as(u32, if (@hasDecl(posix.MSG, "CONFIRM") and flags.confirm) posix.MSG.CONFIRM else 0) |
......@@ -9849,10 +10609,10 @@ fn netSendPosix(
984910609 var i: usize = 0;
985010610 while (messages.len - i != 0) {
985110611 if (have_sendmmsg) {
9852 i += netSendMany(current_thread, handle, messages[i..], posix_flags) catch |err| return .{ err, i };
10612 i += netSendMany(handle, messages[i..], posix_flags) catch |err| return .{ err, i };
985310613 continue;
985410614 }
9855 netSendOne(t, current_thread, handle, &messages[i], posix_flags) catch |err| return .{ err, i };
10615 netSendOne(t, handle, &messages[i], posix_flags) catch |err| return .{ err, i };
985610616 i += 1;
985710617 }
985810618 return .{ null, i };
......@@ -9888,7 +10648,6 @@ fn netSendUnavailable(
988810648
988910649fn netSendOne(
989010650 t: *Threaded,
9891 current_thread: *Thread,
989210651 handle: net.Socket.Handle,
989310652 message: *net.OutgoingMessage,
989410653 flags: u32,
......@@ -9905,29 +10664,29 @@ fn netSendOne(
990510664 .controllen = @intCast(message.control.len),
990610665 .flags = 0,
990710666 };
9908 try current_thread.beginSyscall();
10667 var syscall: Syscall = try .start();
990910668 while (true) {
991010669 const rc = posix.system.sendmsg(handle, &msg, flags);
991110670 if (is_windows) {
991210671 if (rc != ws2_32.SOCKET_ERROR) {
9913 current_thread.endSyscall();
10672 syscall.finish();
991410673 message.data_len = @intCast(rc);
991510674 return;
991610675 }
991710676 switch (ws2_32.WSAGetLastError()) {
9918 .EINTR => {
9919 try current_thread.checkCancel();
10677 .EINTR, .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => {
10678 try syscall.checkCancel();
992010679 continue;
992110680 },
992210681 .NOTINITIALISED => {
10682 syscall.finish();
992310683 try initializeWsa(t);
9924 try current_thread.checkCancel();
10684 syscall = try .start();
992510685 continue;
992610686 },
992710687 else => |e| {
9928 current_thread.endSyscall();
10688 syscall.finish();
992910689 switch (e) {
9930 .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled,
993110690 .EACCES => return error.AccessDenied,
993210691 .EADDRNOTAVAIL => return error.AddressUnavailable,
993310692 .ECONNRESET => return error.ConnectionResetByPeer,
......@@ -9951,16 +10710,16 @@ fn netSendOne(
995110710 }
995210711 switch (posix.errno(rc)) {
995310712 .SUCCESS => {
9954 current_thread.endSyscall();
10713 syscall.finish();
995510714 message.data_len = @intCast(rc);
995610715 return;
995710716 },
995810717 .INTR => {
9959 try current_thread.checkCancel();
10718 try syscall.checkCancel();
996010719 continue;
996110720 },
996210721 else => |e| {
9963 current_thread.endSyscall();
10722 syscall.finish();
996410723 switch (e) {
996510724 .ACCES => return error.AccessDenied,
996610725 .ALREADY => return error.FastOpenAlreadyInProgress,
......@@ -9989,7 +10748,6 @@ fn netSendOne(
998910748}
999010749
999110750fn netSendMany(
9992 current_thread: *Thread,
999310751 handle: net.Socket.Handle,
999410752 messages: []net.OutgoingMessage,
999510753 flags: u32,
......@@ -10019,12 +10777,12 @@ fn netSendMany(
1001910777 };
1002010778 }
1002110779
10022 try current_thread.beginSyscall();
10780 const syscall: Syscall = try .start();
1002310781 while (true) {
1002410782 const rc = posix.system.sendmmsg(handle, clamped_msgs.ptr, @intCast(clamped_msgs.len), flags);
1002510783 switch (posix.errno(rc)) {
1002610784 .SUCCESS => {
10027 current_thread.endSyscall();
10785 syscall.finish();
1002810786 const n: usize = @intCast(rc);
1002910787 for (clamped_messages[0..n], clamped_msgs[0..n]) |*message, *msg| {
1003010788 message.data_len = msg.len;
......@@ -10032,11 +10790,11 @@ fn netSendMany(
1003210790 return n;
1003310791 },
1003410792 .INTR => {
10035 try current_thread.checkCancel();
10793 try syscall.checkCancel();
1003610794 continue;
1003710795 },
1003810796 else => |e| {
10039 current_thread.endSyscall();
10797 syscall.finish();
1004010798 switch (e) {
1004110799 .AGAIN => |err| return errnoBug(err),
1004210800 .ALREADY => return error.FastOpenAlreadyInProgress,
......@@ -10074,7 +10832,6 @@ fn netReceivePosix(
1007410832) struct { ?net.Socket.ReceiveTimeoutError, usize } {
1007510833 if (!have_networking) return .{ error.NetworkDown, 0 };
1007610834 const t: *Threaded = @ptrCast(@alignCast(userdata));
10077 const current_thread = Thread.getCurrent(t);
1007810835 const t_io = io(t);
1007910836
1008010837 // recvmmsg is useless, here's why:
......@@ -10120,9 +10877,12 @@ fn netReceivePosix(
1012010877 .flags = undefined,
1012110878 };
1012210879
10123 current_thread.beginSyscall() catch |err| return .{ err, message_i };
10124 const recv_rc = posix.system.recvmsg(handle, &msg, posix_flags);
10125 current_thread.endSyscall();
10880 const recv_rc = rc: {
10881 const syscall = Syscall.start() catch |err| return .{ err, message_i };
10882 const rc = posix.system.recvmsg(handle, &msg, posix_flags);
10883 syscall.finish();
10884 break :rc rc;
10885 };
1012610886 switch (posix.errno(recv_rc)) {
1012710887 .SUCCESS => {
1012810888 const data = remaining_data_buffer[0..@intCast(recv_rc)];
......@@ -10152,9 +10912,9 @@ fn netReceivePosix(
1015210912 break :t @intCast(@min(max_poll_ms, duration.raw.toMilliseconds()));
1015310913 } else max_poll_ms;
1015410914
10155 current_thread.beginSyscall() catch |err| return .{ err, message_i };
10915 const syscall = Syscall.start() catch |err| return .{ err, message_i };
1015610916 const poll_rc = posix.system.poll(&poll_fds, poll_fds.len, timeout_ms);
10157 current_thread.endSyscall();
10917 syscall.finish();
1015810918
1015910919 switch (posix.errno(poll_rc)) {
1016010920 .SUCCESS => {
......@@ -10240,7 +11000,7 @@ fn netWritePosix(
1024011000) net.Stream.Writer.Error!usize {
1024111001 if (!have_networking) return error.NetworkDown;
1024211002 const t: *Threaded = @ptrCast(@alignCast(userdata));
10243 const current_thread = Thread.getCurrent(t);
11003 _ = t;
1024411004
1024511005 var iovecs: [max_iovecs_len]posix.iovec_const = undefined;
1024611006 var msg: posix.msghdr_const = .{
......@@ -10282,20 +11042,20 @@ fn netWritePosix(
1028211042 };
1028311043 const flags = posix.MSG.NOSIGNAL;
1028411044
10285 try current_thread.beginSyscall();
11045 const syscall: Syscall = try .start();
1028611046 while (true) {
1028711047 const rc = posix.system.sendmsg(fd, &msg, flags);
1028811048 switch (posix.errno(rc)) {
1028911049 .SUCCESS => {
10290 current_thread.endSyscall();
11050 syscall.finish();
1029111051 return @intCast(rc);
1029211052 },
1029311053 .INTR => {
10294 try current_thread.checkCancel();
11054 try syscall.checkCancel();
1029511055 continue;
1029611056 },
1029711057 else => |e| {
10298 current_thread.endSyscall();
11058 syscall.finish();
1029911059 switch (e) {
1030011060 .ACCES => |err| return errnoBug(err),
1030111061 .AGAIN => |err| return errnoBug(err),
......@@ -10332,7 +11092,6 @@ fn netWriteWindows(
1033211092 splat: usize,
1033311093) net.Stream.Writer.Error!usize {
1033411094 const t: *Threaded = @ptrCast(@alignCast(userdata));
10335 const current_thread = Thread.getCurrent(t);
1033611095 comptime assert(native_os == .windows);
1033711096
1033811097 var iovecs: [max_iovecs_len]ws2_32.WSABUF = undefined;
......@@ -10365,49 +11124,44 @@ fn netWriteWindows(
1036511124 },
1036611125 };
1036711126
11127 var syscall: Syscall = try .start();
1036811128 while (true) {
10369 try current_thread.checkCancel();
10370
1037111129 var n: u32 = undefined;
10372 var overlapped: windows.OVERLAPPED = std.mem.zeroes(windows.OVERLAPPED);
10373 const rc = ws2_32.WSASend(handle, &iovecs, len, &n, 0, &overlapped, null);
10374 if (rc != ws2_32.SOCKET_ERROR) return n;
10375 const wsa_error: ws2_32.WinsockError = switch (ws2_32.WSAGetLastError()) {
10376 .IO_PENDING => e: {
10377 var result_flags: u32 = undefined;
10378 const overlapped_rc = ws2_32.WSAGetOverlappedResult(
10379 handle,
10380 &overlapped,
10381 &n,
10382 windows.TRUE,
10383 &result_flags,
10384 );
10385 if (overlapped_rc == windows.FALSE) {
10386 break :e ws2_32.WSAGetLastError();
10387 } else {
10388 return n;
10389 }
11130 const rc = ws2_32.WSASend(handle, &iovecs, len, &n, 0, null, null);
11131 if (rc != ws2_32.SOCKET_ERROR) {
11132 syscall.finish();
11133 return n;
11134 }
11135 switch (ws2_32.WSAGetLastError()) {
11136 .IO_PENDING => unreachable, // not overlapped
11137 .EINTR, .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => {
11138 try syscall.checkCancel();
11139 continue;
1039011140 },
10391 else => |err| err,
10392 };
10393 switch (wsa_error) {
10394 .EINTR, .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => continue,
1039511141 .NOTINITIALISED => {
11142 syscall.finish();
1039611143 try initializeWsa(t);
11144 syscall = try .start();
1039711145 continue;
1039811146 },
1039911147
10400 .ECONNABORTED => return error.ConnectionResetByPeer,
10401 .ECONNRESET => return error.ConnectionResetByPeer,
10402 .EINVAL => return error.SocketUnconnected,
10403 .ENETDOWN => return error.NetworkDown,
10404 .ENETRESET => return error.ConnectionResetByPeer,
10405 .ENOBUFS => return error.SystemResources,
10406 .ENOTCONN => return error.SocketUnconnected,
10407 .ENOTSOCK => |err| return wsaErrorBug(err),
10408 .EOPNOTSUPP => |err| return wsaErrorBug(err),
10409 .ESHUTDOWN => |err| return wsaErrorBug(err),
10410 else => |err| return windows.unexpectedWSAError(err),
11148 .ECONNABORTED => return syscall.fail(error.ConnectionResetByPeer),
11149 .ECONNRESET => return syscall.fail(error.ConnectionResetByPeer),
11150 .EINVAL => return syscall.fail(error.SocketUnconnected),
11151 .ENETDOWN => return syscall.fail(error.NetworkDown),
11152 .ENETRESET => return syscall.fail(error.ConnectionResetByPeer),
11153 .ENOBUFS => return syscall.fail(error.SystemResources),
11154 .ENOTCONN => return syscall.fail(error.SocketUnconnected),
11155
11156 else => |err| {
11157 syscall.finish();
11158 switch (err) {
11159 .ENOTSOCK => return wsaErrorBug(err),
11160 .EOPNOTSUPP => return wsaErrorBug(err),
11161 .ESHUTDOWN => return wsaErrorBug(err),
11162 else => return windows.unexpectedWSAError(err),
11163 }
11164 },
1041111165 }
1041211166 }
1041311167}
......@@ -10476,7 +11230,7 @@ fn netCloseUnavailable(userdata: ?*anyopaque, handles: []const net.Socket.Handle
1047611230fn netShutdownPosix(userdata: ?*anyopaque, handle: net.Socket.Handle, how: net.ShutdownHow) net.ShutdownError!void {
1047711231 if (!have_networking) return error.NetworkDown;
1047811232 const t: *Threaded = @ptrCast(@alignCast(userdata));
10479 const current_thread = Thread.getCurrent(t);
11233 _ = t;
1048011234
1048111235 const posix_how: i32 = switch (how) {
1048211236 .recv => posix.SHUT.RD,
......@@ -10484,19 +11238,16 @@ fn netShutdownPosix(userdata: ?*anyopaque, handle: net.Socket.Handle, how: net.S
1048411238 .both => posix.SHUT.RDWR,
1048511239 };
1048611240
10487 try current_thread.beginSyscall();
11241 const syscall: Syscall = try .start();
1048811242 while (true) {
1048911243 switch (posix.errno(posix.system.shutdown(handle, posix_how))) {
10490 .SUCCESS => {
10491 current_thread.endSyscall();
10492 return;
10493 },
11244 .SUCCESS => return syscall.finish(),
1049411245 .INTR => {
10495 try current_thread.checkCancel();
11246 try syscall.checkCancel();
1049611247 continue;
1049711248 },
1049811249 else => |e| {
10499 current_thread.endSyscall();
11250 syscall.finish();
1050011251 switch (e) {
1050111252 .BADF, .NOTSOCK, .INVAL => |err| return errnoBug(err),
1050211253 .NOTCONN => return error.SocketUnconnected,
......@@ -10511,7 +11262,6 @@ fn netShutdownPosix(userdata: ?*anyopaque, handle: net.Socket.Handle, how: net.S
1051111262fn netShutdownWindows(userdata: ?*anyopaque, handle: net.Socket.Handle, how: net.ShutdownHow) net.ShutdownError!void {
1051211263 if (!have_networking) return error.NetworkDown;
1051311264 const t: *Threaded = @ptrCast(@alignCast(userdata));
10514 const current_thread = Thread.getCurrent(t);
1051511265
1051611266 const wsa_how: i32 = switch (how) {
1051711267 .recv => ws2_32.SD_RECEIVE,
......@@ -10519,27 +11269,27 @@ fn netShutdownWindows(userdata: ?*anyopaque, handle: net.Socket.Handle, how: net
1051911269 .both => ws2_32.SD_BOTH,
1052011270 };
1052111271
10522 try current_thread.beginSyscall();
11272 var syscall: Syscall = try .start();
1052311273 while (true) {
1052411274 const rc = ws2_32.shutdown(handle, wsa_how);
1052511275 if (rc != ws2_32.SOCKET_ERROR) {
10526 current_thread.endSyscall();
11276 syscall.finish();
1052711277 return;
1052811278 }
1052911279 switch (ws2_32.WSAGetLastError()) {
10530 .EINTR => {
10531 try current_thread.checkCancel();
11280 .EINTR, .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => {
11281 try syscall.checkCancel();
1053211282 continue;
1053311283 },
1053411284 .NOTINITIALISED => {
11285 syscall.finish();
1053511286 try initializeWsa(t);
10536 try current_thread.checkCancel();
11287 syscall = try .start();
1053711288 continue;
1053811289 },
1053911290 else => |e| {
10540 current_thread.endSyscall();
11291 syscall.finish();
1054111292 switch (e) {
10542 .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled,
1054311293 .ECONNABORTED => return error.ConnectionAborted,
1054411294 .ECONNRESET => return error.ConnectionResetByPeer,
1054511295 .ENETDOWN => return error.NetworkDown,
......@@ -10562,10 +11312,10 @@ fn netInterfaceNameResolve(
1056211312) net.Interface.Name.ResolveError!net.Interface {
1056311313 if (!have_networking) return error.InterfaceNotFound;
1056411314 const t: *Threaded = @ptrCast(@alignCast(userdata));
10565 const current_thread = Thread.getCurrent(t);
11315 _ = t;
1056611316
1056711317 if (native_os == .linux) {
10568 const sock_fd = openSocketPosix(current_thread, posix.AF.UNIX, .{ .mode = .dgram }) catch |err| switch (err) {
11318 const sock_fd = openSocketPosix(posix.AF.UNIX, .{ .mode = .dgram }) catch |err| switch (err) {
1056911319 error.ProcessFdQuotaExceeded => return error.SystemResources,
1057011320 error.SystemFdQuotaExceeded => return error.SystemResources,
1057111321 error.AddressFamilyUnsupported => return error.Unexpected,
......@@ -10582,19 +11332,19 @@ fn netInterfaceNameResolve(
1058211332 .ifru = undefined,
1058311333 };
1058411334
10585 try current_thread.beginSyscall();
11335 const syscall: Syscall = try .start();
1058611336 while (true) {
1058711337 switch (posix.errno(posix.system.ioctl(sock_fd, posix.SIOCGIFINDEX, @intFromPtr(&ifr)))) {
1058811338 .SUCCESS => {
10589 current_thread.endSyscall();
11339 syscall.finish();
1059011340 return .{ .index = @bitCast(ifr.ifru.ivalue) };
1059111341 },
1059211342 .INTR => {
10593 try current_thread.checkCancel();
11343 try syscall.checkCancel();
1059411344 continue;
1059511345 },
1059611346 else => |e| {
10597 current_thread.endSyscall();
11347 syscall.finish();
1059811348 switch (e) {
1059911349 .INVAL => |err| return errnoBug(err), // Bad parameters.
1060011350 .NOTTY => |err| return errnoBug(err),
......@@ -10611,12 +11361,12 @@ fn netInterfaceNameResolve(
1061111361 }
1061211362
1061311363 if (native_os == .windows) {
10614 try current_thread.checkCancel();
11364 try Thread.checkCancel();
1061511365 @panic("TODO implement netInterfaceNameResolve for Windows");
1061611366 }
1061711367
1061811368 if (builtin.link_libc) {
10619 try current_thread.checkCancel();
11369 try Thread.checkCancel();
1062011370 const index = std.c.if_nametoindex(&name.bytes);
1062111371 if (index == 0) return error.InterfaceNotFound;
1062211372 return .{ .index = @bitCast(index) };
......@@ -10636,8 +11386,8 @@ fn netInterfaceNameResolveUnavailable(
1063611386
1063711387fn netInterfaceName(userdata: ?*anyopaque, interface: net.Interface) net.Interface.NameError!net.Interface.Name {
1063811388 const t: *Threaded = @ptrCast(@alignCast(userdata));
10639 const current_thread = Thread.getCurrent(t);
10640 try current_thread.checkCancel();
11389 _ = t;
11390 try Thread.checkCancel();
1064111391
1064211392 if (native_os == .linux) {
1064311393 _ = interface;
......@@ -10696,7 +11446,6 @@ fn netLookupFallible(
1069611446) (net.HostName.LookupError || Io.QueueClosedError)!void {
1069711447 if (!have_networking) return error.NetworkDown;
1069811448
10699 const current_thread: *Thread = .getCurrent(t);
1070011449 const t_io = io(t);
1070111450 const name = host_name.bytes;
1070211451 assert(name.len <= HostName.max_len);
......@@ -10733,18 +11482,17 @@ fn netLookupFallible(
1073311482 .provider = null,
1073411483 .next = null,
1073511484 };
10736 const cancel_handle: ?*windows.HANDLE = null;
1073711485 var res: *ws2_32.ADDRINFOEXW = undefined;
1073811486 const timeout: ?*ws2_32.timeval = null;
1073911487 while (true) {
10740 try current_thread.checkCancel(); // TODO make requestCancel call GetAddrInfoExCancel
10741 // TODO make this append to the queue eagerly rather than blocking until
10742 // the whole thing finishes
10743 const rc: ws2_32.WinsockError = @enumFromInt(ws2_32.GetAddrInfoExW(name_w, port_w, .DNS, null, &hints, &res, timeout, null, null, cancel_handle));
11488 // TODO: hook this up to cancelation with `Thread.Status.cancelation.blocked_windows_dns`.
11489 // See matching TODO in `Thread.cancelAwaitable`.
11490 try Thread.checkCancel();
11491 // TODO make this append to the queue eagerly rather than blocking until the whole thing finishes
11492 const rc: ws2_32.WinsockError = @enumFromInt(ws2_32.GetAddrInfoExW(name_w, port_w, .DNS, null, &hints, &res, timeout, null, null, null));
1074411493 switch (rc) {
1074511494 @as(ws2_32.WinsockError, @enumFromInt(0)) => break,
10746 .EINTR => continue,
10747 .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled,
11495 .EINTR, .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => continue,
1074811496 .NOTINITIALISED => {
1074911497 try initializeWsa(t);
1075011498 continue;
......@@ -10884,25 +11632,25 @@ fn netLookupFallible(
1088411632 .next = null,
1088511633 };
1088611634 var res: ?*posix.addrinfo = null;
10887 try current_thread.beginSyscall();
11635 const syscall: Syscall = try .start();
1088811636 while (true) {
1088911637 switch (posix.system.getaddrinfo(name_c.ptr, port_c.ptr, &hints, &res)) {
1089011638 @as(posix.system.EAI, @enumFromInt(0)) => {
10891 current_thread.endSyscall();
11639 syscall.finish();
1089211640 break;
1089311641 },
1089411642 .SYSTEM => switch (posix.errno(-1)) {
1089511643 .INTR => {
10896 try current_thread.checkCancel();
11644 try syscall.checkCancel();
1089711645 continue;
1089811646 },
1089911647 else => |e| {
10900 current_thread.endSyscall();
11648 syscall.finish();
1090111649 return posix.unexpectedErrno(e);
1090211650 },
1090311651 },
1090411652 else => |e| {
10905 current_thread.endSyscall();
11653 syscall.finish();
1090611654 switch (e) {
1090711655 .ADDRFAMILY => return error.AddressFamilyUnsupported,
1090811656 .AGAIN => return error.NameServerFailure,
......@@ -10977,7 +11725,7 @@ fn unlockStderr(userdata: ?*anyopaque) void {
1097711725 const t: *Threaded = @ptrCast(@alignCast(userdata));
1097811726 t.stderr_writer.interface.flush() catch |err| switch (err) {
1097911727 error.WriteFailed => switch (t.stderr_writer.err.?) {
10980 error.Canceled => recancel(t),
11728 error.Canceled => recancelInner(),
1098111729 else => {},
1098211730 },
1098311731 };
......@@ -10989,62 +11737,66 @@ fn unlockStderr(userdata: ?*anyopaque) void {
1098911737fn processSetCurrentDir(userdata: ?*anyopaque, dir: Dir) std.process.SetCurrentDirError!void {
1099011738 if (native_os == .wasi) return error.OperationUnsupported;
1099111739 const t: *Threaded = @ptrCast(@alignCast(userdata));
10992 const current_thread = Thread.getCurrent(t);
11740 _ = t;
1099311741
1099411742 if (is_windows) {
10995 try current_thread.checkCancel();
1099611743 var dir_path_buffer: [windows.PATH_MAX_WIDE]u16 = undefined;
1099711744 // TODO move GetFinalPathNameByHandle logic into std.Io.Threaded and add cancel checks
11745 try Thread.checkCancel();
1099811746 const dir_path = try windows.GetFinalPathNameByHandle(dir.handle, .{}, &dir_path_buffer);
1099911747 const path_len_bytes = std.math.cast(u16, dir_path.len * 2) orelse return error.NameTooLong;
11000 try current_thread.checkCancel();
1100111748 var nt_name: windows.UNICODE_STRING = .{
1100211749 .Length = path_len_bytes,
1100311750 .MaximumLength = path_len_bytes,
1100411751 .Buffer = @constCast(dir_path.ptr),
1100511752 };
11006 switch (windows.ntdll.RtlSetCurrentDirectory_U(&nt_name)) {
11007 .SUCCESS => return,
11008 .OBJECT_NAME_INVALID => return error.BadPathName,
11009 .OBJECT_NAME_NOT_FOUND => return error.FileNotFound,
11010 .OBJECT_PATH_NOT_FOUND => return error.FileNotFound,
11011 .NO_MEDIA_IN_DEVICE => return error.NoDevice,
11012 .INVALID_PARAMETER => |err| return windows.statusBug(err),
11013 .ACCESS_DENIED => return error.AccessDenied,
11014 .OBJECT_PATH_SYNTAX_BAD => |err| return windows.statusBug(err),
11015 .NOT_A_DIRECTORY => return error.NotDir,
11016 else => |status| return windows.unexpectedStatus(status),
11017 }
11753 const syscall: Syscall = try .start();
11754 while (true) switch (windows.ntdll.RtlSetCurrentDirectory_U(&nt_name)) {
11755 .SUCCESS => return syscall.finish(),
11756 .OBJECT_NAME_INVALID => return syscall.fail(error.BadPathName),
11757 .OBJECT_NAME_NOT_FOUND => return syscall.fail(error.FileNotFound),
11758 .OBJECT_PATH_NOT_FOUND => return syscall.fail(error.FileNotFound),
11759 .NO_MEDIA_IN_DEVICE => return syscall.fail(error.NoDevice),
11760 .INVALID_PARAMETER => |err| return syscall.ntstatusBug(err),
11761 .ACCESS_DENIED => return syscall.fail(error.AccessDenied),
11762 .OBJECT_PATH_SYNTAX_BAD => |err| return syscall.ntstatusBug(err),
11763 .NOT_A_DIRECTORY => return syscall.fail(error.NotDir),
11764 .CANCELLED => {
11765 try syscall.checkCancel();
11766 continue;
11767 },
11768 else => |status| return syscall.unexpectedNtstatus(status),
11769 };
1101811770 }
1101911771
1102011772 if (dir.handle == posix.AT.FDCWD) return;
1102111773
11022 try current_thread.beginSyscall();
11774 const syscall: Syscall = try .start();
1102311775 while (true) {
1102411776 switch (posix.errno(posix.system.fchdir(dir.handle))) {
11025 .SUCCESS => return current_thread.endSyscall(),
11777 .SUCCESS => return syscall.finish(),
1102611778 .INTR => {
11027 try current_thread.checkCancel();
11779 try syscall.checkCancel();
1102811780 continue;
1102911781 },
1103011782 .ACCES => {
11031 current_thread.endSyscall();
11783 syscall.finish();
1103211784 return error.AccessDenied;
1103311785 },
1103411786 .BADF => |err| {
11035 current_thread.endSyscall();
11787 syscall.finish();
1103611788 return errnoBug(err);
1103711789 },
1103811790 .NOTDIR => {
11039 current_thread.endSyscall();
11791 syscall.finish();
1104011792 return error.NotDir;
1104111793 },
1104211794 .IO => {
11043 current_thread.endSyscall();
11795 syscall.finish();
1104411796 return error.FileSystem;
1104511797 },
1104611798 else => |err| {
11047 current_thread.endSyscall();
11799 syscall.finish();
1104811800 return posix.unexpectedErrno(err);
1104911801 },
1105011802 }
......@@ -11825,391 +12577,6 @@ fn initializeWsa(t: *Threaded) error{ NetworkDown, Canceled }!void {
1182512577
1182612578fn doNothingSignalHandler(_: posix.SIG) callconv(.c) void {}
1182712579
11828const pthreads_futex = struct {
11829 const c = std.c;
11830 const atomic = std.atomic;
11831
11832 const Event = struct {
11833 cond: c.pthread_cond_t,
11834 mutex: c.pthread_mutex_t,
11835 state: enum { empty, waiting, notified },
11836
11837 fn init(self: *Event) void {
11838 // Use static init instead of pthread_cond/mutex_init() since this is generally faster.
11839 self.cond = .{};
11840 self.mutex = .{};
11841 self.state = .empty;
11842 }
11843
11844 fn deinit(self: *Event) void {
11845 // Some platforms reportedly give EINVAL for statically initialized pthread types.
11846 const rc = c.pthread_cond_destroy(&self.cond);
11847 assert(rc == .SUCCESS or rc == .INVAL);
11848
11849 const rm = c.pthread_mutex_destroy(&self.mutex);
11850 assert(rm == .SUCCESS or rm == .INVAL);
11851
11852 self.* = undefined;
11853 }
11854
11855 fn wait(self: *Event, timeout: ?u64) error{Timeout}!void {
11856 assert(c.pthread_mutex_lock(&self.mutex) == .SUCCESS);
11857 defer assert(c.pthread_mutex_unlock(&self.mutex) == .SUCCESS);
11858
11859 // Early return if the event was already set.
11860 if (self.state == .notified) {
11861 return;
11862 }
11863
11864 // Compute the absolute timeout if one was specified.
11865 // POSIX requires that REALTIME is used by default for the pthread timedwait functions.
11866 // This can be changed with pthread_condattr_setclock, but it's an extension and may not be available everywhere.
11867 var ts: c.timespec = undefined;
11868 if (timeout) |timeout_ns| {
11869 ts = std.posix.clock_gettime(c.CLOCK.REALTIME) catch return error.Timeout;
11870 ts.sec +|= @as(@TypeOf(ts.sec), @intCast(timeout_ns / std.time.ns_per_s));
11871 ts.nsec += @as(@TypeOf(ts.nsec), @intCast(timeout_ns % std.time.ns_per_s));
11872
11873 if (ts.nsec >= std.time.ns_per_s) {
11874 ts.sec +|= 1;
11875 ts.nsec -= std.time.ns_per_s;
11876 }
11877 }
11878
11879 // Start waiting on the event - there can be only one thread waiting.
11880 assert(self.state == .empty);
11881 self.state = .waiting;
11882
11883 while (true) {
11884 // Block using either pthread_cond_wait or pthread_cond_timewait if there's an absolute timeout.
11885 const rc = blk: {
11886 if (timeout == null) break :blk c.pthread_cond_wait(&self.cond, &self.mutex);
11887 break :blk c.pthread_cond_timedwait(&self.cond, &self.mutex, &ts);
11888 };
11889
11890 // After waking up, check if the event was set.
11891 if (self.state == .notified) {
11892 return;
11893 }
11894
11895 assert(self.state == .waiting);
11896 switch (rc) {
11897 .SUCCESS => {},
11898 .TIMEDOUT => {
11899 // If timed out, reset the event to avoid the set() thread doing an unnecessary signal().
11900 self.state = .empty;
11901 return error.Timeout;
11902 },
11903 .INVAL => recoverableOsBugDetected(), // cond, mutex, and potentially ts should all be valid
11904 .PERM => recoverableOsBugDetected(), // mutex is locked when cond_*wait() functions are called
11905 else => recoverableOsBugDetected(),
11906 }
11907 }
11908 }
11909
11910 fn set(self: *Event) void {
11911 assert(c.pthread_mutex_lock(&self.mutex) == .SUCCESS);
11912 defer assert(c.pthread_mutex_unlock(&self.mutex) == .SUCCESS);
11913
11914 // Make sure that multiple calls to set() were not done on the same Event.
11915 const old_state = self.state;
11916 assert(old_state != .notified);
11917
11918 // Mark the event as set and wake up the waiting thread if there was one.
11919 // This must be done while the mutex as the wait() thread could deallocate
11920 // the condition variable once it observes the new state, potentially causing a UAF if done unlocked.
11921 self.state = .notified;
11922 if (old_state == .waiting) {
11923 assert(c.pthread_cond_signal(&self.cond) == .SUCCESS);
11924 }
11925 }
11926 };
11927
11928 const Treap = std.Treap(usize, std.math.order);
11929 const Waiter = struct {
11930 node: Treap.Node,
11931 prev: ?*Waiter,
11932 next: ?*Waiter,
11933 tail: ?*Waiter,
11934 is_queued: bool,
11935 event: Event,
11936 };
11937
11938 // An unordered set of Waiters
11939 const WaitList = struct {
11940 top: ?*Waiter = null,
11941 len: usize = 0,
11942
11943 fn push(self: *WaitList, waiter: *Waiter) void {
11944 waiter.next = self.top;
11945 self.top = waiter;
11946 self.len += 1;
11947 }
11948
11949 fn pop(self: *WaitList) ?*Waiter {
11950 const waiter = self.top orelse return null;
11951 self.top = waiter.next;
11952 self.len -= 1;
11953 return waiter;
11954 }
11955 };
11956
11957 const WaitQueue = struct {
11958 fn insert(treap: *Treap, address: usize, waiter: *Waiter) void {
11959 // prepare the waiter to be inserted.
11960 waiter.next = null;
11961 waiter.is_queued = true;
11962
11963 // Find the wait queue entry associated with the address.
11964 // If there isn't a wait queue on the address, this waiter creates the queue.
11965 var entry = treap.getEntryFor(address);
11966 const entry_node = entry.node orelse {
11967 waiter.prev = null;
11968 waiter.tail = waiter;
11969 entry.set(&waiter.node);
11970 return;
11971 };
11972
11973 // There's a wait queue on the address; get the queue head and tail.
11974 const head: *Waiter = @fieldParentPtr("node", entry_node);
11975 const tail = head.tail orelse unreachable;
11976
11977 // Push the waiter to the tail by replacing it and linking to the previous tail.
11978 head.tail = waiter;
11979 tail.next = waiter;
11980 waiter.prev = tail;
11981 }
11982
11983 fn remove(treap: *Treap, address: usize, max_waiters: usize) WaitList {
11984 // Find the wait queue associated with this address and get the head/tail if any.
11985 var entry = treap.getEntryFor(address);
11986 var queue_head: ?*Waiter = if (entry.node) |node| @fieldParentPtr("node", node) else null;
11987 const queue_tail = if (queue_head) |head| head.tail else null;
11988
11989 // Once we're done updating the head, fix it's tail pointer and update the treap's queue head as well.
11990 defer entry.set(blk: {
11991 const new_head = queue_head orelse break :blk null;
11992 new_head.tail = queue_tail;
11993 break :blk &new_head.node;
11994 });
11995
11996 var removed = WaitList{};
11997 while (removed.len < max_waiters) {
11998 // dequeue and collect waiters from their wait queue.
11999 const waiter = queue_head orelse break;
12000 queue_head = waiter.next;
12001 removed.push(waiter);
12002
12003 // When dequeueing, we must mark is_queued as false.
12004 // This ensures that a waiter which calls tryRemove() returns false.
12005 assert(waiter.is_queued);
12006 waiter.is_queued = false;
12007 }
12008
12009 return removed;
12010 }
12011
12012 fn tryRemove(treap: *Treap, address: usize, waiter: *Waiter) bool {
12013 if (!waiter.is_queued) {
12014 return false;
12015 }
12016
12017 queue_remove: {
12018 // Find the wait queue associated with the address.
12019 var entry = blk: {
12020 // A waiter without a previous link means it's the queue head that's in the treap so we can avoid lookup.
12021 if (waiter.prev == null) {
12022 assert(waiter.node.key == address);
12023 break :blk treap.getEntryForExisting(&waiter.node);
12024 }
12025 break :blk treap.getEntryFor(address);
12026 };
12027
12028 // The queue head and tail must exist if we're removing a queued waiter.
12029 const head: *Waiter = @fieldParentPtr("node", entry.node orelse unreachable);
12030 const tail = head.tail orelse unreachable;
12031
12032 // A waiter with a previous link is never the head of the queue.
12033 if (waiter.prev) |prev| {
12034 assert(waiter != head);
12035 prev.next = waiter.next;
12036
12037 // A waiter with both a previous and next link is in the middle.
12038 // We only need to update the surrounding waiter's links to remove it.
12039 if (waiter.next) |next| {
12040 assert(waiter != tail);
12041 next.prev = waiter.prev;
12042 break :queue_remove;
12043 }
12044
12045 // A waiter with a previous but no next link means it's the tail of the queue.
12046 // In that case, we need to update the head's tail reference.
12047 assert(waiter == tail);
12048 head.tail = waiter.prev;
12049 break :queue_remove;
12050 }
12051
12052 // A waiter with no previous link means it's the queue head of queue.
12053 // We must replace (or remove) the head waiter reference in the treap.
12054 assert(waiter == head);
12055 entry.set(blk: {
12056 const new_head = waiter.next orelse break :blk null;
12057 new_head.tail = head.tail;
12058 break :blk &new_head.node;
12059 });
12060 }
12061
12062 // Mark the waiter as successfully removed.
12063 waiter.is_queued = false;
12064 return true;
12065 }
12066 };
12067
12068 const Bucket = struct {
12069 mutex: c.pthread_mutex_t align(atomic.cache_line) = .{},
12070 pending: atomic.Value(usize) = atomic.Value(usize).init(0),
12071 treap: Treap = .{},
12072
12073 // Global array of buckets that addresses map to.
12074 // Bucket array size is pretty much arbitrary here, but it must be a power of two for fibonacci hashing.
12075 var buckets = [_]Bucket{.{}} ** @bitSizeOf(usize);
12076
12077 // https://github.com/Amanieu/parking_lot/blob/1cf12744d097233316afa6c8b7d37389e4211756/core/src/parking_lot.rs#L343-L353
12078 fn from(address: usize) *Bucket {
12079 // The upper `@bitSizeOf(usize)` bits of the fibonacci golden ratio.
12080 // Hashing this via (h * k) >> (64 - b) where k=golden-ration and b=bitsize-of-array
12081 // evenly lays out h=hash values over the bit range even when the hash has poor entropy (identity-hash for pointers).
12082 const max_multiplier_bits = @bitSizeOf(usize);
12083 const fibonacci_multiplier = 0x9E3779B97F4A7C15 >> (64 - max_multiplier_bits);
12084
12085 const max_bucket_bits = @ctz(buckets.len);
12086 comptime assert(std.math.isPowerOfTwo(buckets.len));
12087
12088 const index = (address *% fibonacci_multiplier) >> (max_multiplier_bits - max_bucket_bits);
12089 return &buckets[index];
12090 }
12091 };
12092
12093 const Address = struct {
12094 fn from(ptr: *const u32) usize {
12095 // Get the alignment of the pointer.
12096 const alignment = @alignOf(atomic.Value(u32));
12097 comptime assert(std.math.isPowerOfTwo(alignment));
12098
12099 // Make sure the pointer is aligned,
12100 // then cut off the zero bits from the alignment to get the unique address.
12101 const addr = @intFromPtr(ptr);
12102 assert(addr & (alignment - 1) == 0);
12103 return addr >> @ctz(@as(usize, alignment));
12104 }
12105 };
12106
12107 fn wait(ptr: *const u32, expect: u32, timeout: ?u64) error{Timeout}!void {
12108 const address = Address.from(ptr);
12109 const bucket = Bucket.from(address);
12110
12111 // Announce that there's a waiter in the bucket before checking the ptr/expect condition.
12112 // If the announcement is reordered after the ptr check, the waiter could deadlock:
12113 //
12114 // - T1: checks ptr == expect which is true
12115 // - T2: updates ptr to != expect
12116 // - T2: does Futex.wake(), sees no pending waiters, exits
12117 // - T1: bumps pending waiters (was reordered after the ptr == expect check)
12118 // - T1: goes to sleep and misses both the ptr change and T2's wake up
12119 //
12120 // acquire barrier to ensure the announcement happens before the ptr check below.
12121 var pending = bucket.pending.fetchAdd(1, .acquire);
12122 assert(pending < std.math.maxInt(usize));
12123
12124 // If the wait gets canceled, remove the pending count we previously added.
12125 // This is done outside the mutex lock to keep the critical section short in case of contention.
12126 var canceled = false;
12127 defer if (canceled) {
12128 pending = bucket.pending.fetchSub(1, .monotonic);
12129 assert(pending > 0);
12130 };
12131
12132 var waiter: Waiter = undefined;
12133 {
12134 assert(c.pthread_mutex_lock(&bucket.mutex) == .SUCCESS);
12135 defer assert(c.pthread_mutex_unlock(&bucket.mutex) == .SUCCESS);
12136
12137 canceled = @atomicLoad(u32, ptr, .monotonic) != expect;
12138 if (canceled) {
12139 return;
12140 }
12141
12142 waiter.event.init();
12143 WaitQueue.insert(&bucket.treap, address, &waiter);
12144 }
12145
12146 defer {
12147 assert(!waiter.is_queued);
12148 waiter.event.deinit();
12149 }
12150
12151 waiter.event.wait(timeout) catch {
12152 // If we fail to cancel after a timeout, it means a wake() thread
12153 // dequeued us and will wake us up. We must wait until the event is
12154 // set as that's a signal that the wake() thread won't access the
12155 // waiter memory anymore. If we return early without waiting, the
12156 // waiter on the stack would be invalidated and the wake() thread
12157 // risks a UAF.
12158 defer if (!canceled) waiter.event.wait(null) catch unreachable;
12159
12160 assert(c.pthread_mutex_lock(&bucket.mutex) == .SUCCESS);
12161 defer assert(c.pthread_mutex_unlock(&bucket.mutex) == .SUCCESS);
12162
12163 canceled = WaitQueue.tryRemove(&bucket.treap, address, &waiter);
12164 if (canceled) {
12165 return error.Timeout;
12166 }
12167 };
12168 }
12169
12170 fn wake(ptr: *const u32, max_waiters: u32) void {
12171 const address = Address.from(ptr);
12172 const bucket = Bucket.from(address);
12173
12174 // Quick check if there's even anything to wake up.
12175 // The change to the ptr's value must happen before we check for pending waiters.
12176 // If not, the wake() thread could miss a sleeping waiter and have it deadlock:
12177 //
12178 // - T2: p = has pending waiters (reordered before the ptr update)
12179 // - T1: bump pending waiters
12180 // - T1: if ptr == expected: sleep()
12181 // - T2: update ptr != expected
12182 // - T2: p is false from earlier so doesn't wake (T1 missed ptr update and T2 missed T1 sleeping)
12183 //
12184 // What we really want here is a Release load, but that doesn't exist under the C11 memory model.
12185 // We could instead do `bucket.pending.fetchAdd(0, Release) == 0` which achieves effectively the same thing,
12186 // LLVM lowers the fetchAdd(0, .release) into an mfence+load which avoids gaining ownership of the cache-line.
12187 if (bucket.pending.fetchAdd(0, .release) == 0) {
12188 return;
12189 }
12190
12191 // Keep a list of all the waiters notified and wake then up outside the mutex critical section.
12192 var notified = WaitList{};
12193 defer if (notified.len > 0) {
12194 const pending = bucket.pending.fetchSub(notified.len, .monotonic);
12195 assert(pending >= notified.len);
12196
12197 while (notified.pop()) |waiter| {
12198 assert(!waiter.is_queued);
12199 waiter.event.set();
12200 }
12201 };
12202
12203 assert(c.pthread_mutex_lock(&bucket.mutex) == .SUCCESS);
12204 defer assert(c.pthread_mutex_unlock(&bucket.mutex) == .SUCCESS);
12205
12206 // Another pending check again to avoid the WaitQueue lookup if not necessary.
12207 if (bucket.pending.load(.monotonic) > 0) {
12208 notified = WaitQueue.remove(&bucket.treap, address, max_waiters);
12209 }
12210 }
12211};
12212
1221312580fn scanEnviron(t: *Threaded) void {
1221412581 t.mutex.lock();
1221512582 defer t.mutex.unlock();
......@@ -12328,3 +12695,459 @@ fn scanEnviron(t: *Threaded) void {
1232812695test {
1232912696 _ = @import("Threaded/test.zig");
1233012697}
12698
12699const use_parking_futex = switch (builtin.target.os.tag) {
12700 .windows => true, // RtlWaitOnAddress is a userland implementation anyway
12701 .netbsd => true, // NetBSD has `futex(2)`, but it's historically been quite buggy. TODO: evaluate whether it's okay to use now.
12702 .illumos => true, // Illumos has no futex mechanism
12703 else => false,
12704};
12705const use_parking_sleep = switch (builtin.target.os.tag) {
12706 // On Windows, we can implement sleep either with `NtDelayExecution` (which is how `SleepEx` in
12707 // kernel32 works) or `NtWaitForAlertByThreadId` (thread parking). We're already using the
12708 // latter for futex, so we may as well use it for sleeping too, to maximise code reuse. I'm
12709 // also more confident that it will always correctly handle the cancelation race (so "unpark"
12710 // before "park" causes "park" to return immediately): it *seems* like alertable sleeps paired
12711 // with `NtAlertThread` do actually do this too, but there could be some caveat (e.g. it might
12712 // fail under some specific condition), whereas `NtWaitForAlertByThreadId` must reliably trigger
12713 // this behavior because `RtlWaitOnAddress` relies on it.
12714 .windows => true,
12715
12716 // These targets have `_lwp_park`, which is superior to POSIX nanosleep because it has a better
12717 // cancelation mechanism.
12718 .netbsd,
12719 .illumos,
12720 => true,
12721
12722 else => false,
12723};
12724
12725const parking_futex = struct {
12726 comptime {
12727 assert(use_parking_futex);
12728 }
12729
12730 const Bucket = struct {
12731 /// Used as a fast check for `wake` to avoid having to acquire `mutex` to discover there are no
12732 /// waiters. It is important for `wait` to increment this *before* checking the futex value to
12733 /// avoid a race.
12734 num_waiters: std.atomic.Value(u32),
12735 /// Protects `waiters`.
12736 mutex: std.Thread.Mutex,
12737 waiters: std.DoublyLinkedList,
12738
12739 /// Prevent false sharing between buckets.
12740 _: void align(std.atomic.cache_line) = {},
12741
12742 const init: Bucket = .{ .num_waiters = .init(0), .mutex = .{}, .waiters = .{} };
12743 };
12744
12745 const Waiter = struct {
12746 node: std.DoublyLinkedList.Node,
12747 address: usize,
12748 tid: std.Thread.Id,
12749 /// `thread_status.cancelation` is `.parked` while the thread is waiting. The single thread
12750 /// which atomically updates it (to `.none` or `.canceling`) is responsible for:
12751 ///
12752 /// * Removing the `Waiter` from `Bucket.waiters`
12753 /// * Decrementing `Bucket.num_waiters`
12754 /// * Unparking the thread (*after* the above, so that the `Waiter` does not go out of scope
12755 /// while it is still in the `Bucket`).
12756 thread_status: *std.atomic.Value(Thread.Status),
12757 };
12758
12759 fn bucketForAddress(address: usize) *Bucket {
12760 const global = struct {
12761 /// Length must be a power of two. The longer this array, the less likely contention is
12762 /// between different futexes. This length seems like it'll provide a reasonable balance
12763 /// between contention and memory usage: assuming a 128-byte `Bucket` (due to cache line
12764 /// alignment), this uses 32 KiB of memory.
12765 var buckets: [256]Bucket = @splat(.init);
12766 };
12767
12768 // Here we use Fibonacci hashing: the golden ratio can be used to evenly redistribute input
12769 // values across a range, giving a poor, but extremely quick to compute, hash.
12770
12771 // This literal is the rounded value of '2^64 / phi' (where 'phi' is the golden ratio). The
12772 // shift then converts it to '2^b / phi', where 'b' is the pointer bit width.
12773 const fibonacci_multiplier = 0x9E3779B97F4A7C15 >> (64 - @bitSizeOf(usize));
12774 const hashed = address *% fibonacci_multiplier;
12775
12776 comptime assert(std.math.isPowerOfTwo(global.buckets.len));
12777 // The high bits of `hashed` have better entropy than the low bits.
12778 const index = hashed >> (@bitSizeOf(usize) - @ctz(global.buckets.len));
12779
12780 return &global.buckets[index];
12781 }
12782
12783 fn wait(ptr: *const u32, expect: u32, uncancelable: bool, timeout: Io.Timeout) Io.Cancelable!void {
12784 const bucket = bucketForAddress(@intFromPtr(ptr));
12785
12786 // Put the threadlocal access outside of the critical section.
12787 const opt_thread = Thread.current;
12788 const self_tid = if (opt_thread) |thread| thread.id else std.Thread.getCurrentId();
12789
12790 var waiter: Waiter = .{
12791 .node = undefined, // populated by list append
12792 .address = @intFromPtr(ptr),
12793 .tid = self_tid,
12794 .thread_status = undefined, // populated in critical section
12795 };
12796
12797 var status_buf: std.atomic.Value(Thread.Status) = undefined;
12798
12799 {
12800 bucket.mutex.lock();
12801 defer bucket.mutex.unlock();
12802
12803 _ = bucket.num_waiters.fetchAdd(1, .acquire);
12804
12805 if (@atomicLoad(u32, ptr, .monotonic) != expect) {
12806 assert(bucket.num_waiters.fetchSub(1, .monotonic) > 0);
12807 return;
12808 }
12809
12810 // This is in the critical section to avoid marking the thread as parked until we're
12811 // certain that we're actually going to park.
12812 waiter.thread_status = status: {
12813 cancelable: {
12814 if (uncancelable) break :cancelable;
12815 const thread = opt_thread orelse break :cancelable;
12816 switch (thread.cancel_protection) {
12817 .blocked => break :cancelable,
12818 .unblocked => {},
12819 }
12820 thread.futex_waiter = &waiter;
12821 const old_status = thread.status.fetchOr(
12822 .{ .cancelation = @enumFromInt(0b001), .awaitable = .null },
12823 .release, // release `thread.futex_waiter`
12824 );
12825 switch (old_status.cancelation) {
12826 .none => {}, // status is now `.parked`
12827 .canceling => {
12828 // status is now `.canceled`
12829 assert(bucket.num_waiters.fetchSub(1, .monotonic) > 0);
12830 return error.Canceled;
12831 },
12832 .canceled => break :cancelable, // status is still `.canceled`
12833 .parked => unreachable,
12834 .blocked => unreachable,
12835 .blocked_windows_dns => unreachable,
12836 .blocked_canceling => unreachable,
12837 }
12838 // We could now be unparked for a cancelation at any time!
12839 break :status &thread.status;
12840 }
12841 // This is an uncancelable wait, so just use `status_buf`. Note that the value of
12842 // `status_buf.awaitable` is irrelevant because this is only visible to futex code,
12843 // while only cancelation cares about `awaitable`.
12844 status_buf.raw = .{ .cancelation = .parked, .awaitable = .null };
12845 break :status &status_buf;
12846 };
12847
12848 bucket.waiters.append(&waiter.node);
12849 }
12850
12851 if (park(timeout, ptr)) {
12852 // We were unparked by either `wake` or cancelation, so our current status is either
12853 // `.none` or `.canceling`. In either case, they've already removed `waiter` from
12854 // `bucket`, so we have nothing more to do!
12855 } else |err| switch (err) {
12856 error.Timeout => {
12857 // We're not out of the woods yet: an unpark could race with the timeout.
12858 const old_status = waiter.thread_status.fetchAnd(
12859 .{ .cancelation = @enumFromInt(0b110), .awaitable = .all_ones },
12860 .monotonic,
12861 );
12862 switch (old_status.cancelation) {
12863 .parked => {
12864 // No race. It is our responsibility to remove `waiter` from `bucket`.
12865 // New status is `.none`.
12866 bucket.mutex.lock();
12867 defer bucket.mutex.unlock();
12868 bucket.waiters.remove(&waiter.node);
12869 assert(bucket.num_waiters.fetchSub(1, .monotonic) > 0);
12870 },
12871 .none, .canceling => {
12872 // Race condition: the timeout was reached, then `wake` or a canceler tried
12873 // to unpark us. Whoever did that will remove us from `bucket`. Wait for
12874 // that (and drop the unpark request in doing so).
12875 // New status is `.none` or `.canceling` respectively.
12876 park(.none, ptr) catch |e| switch (e) {
12877 error.Timeout => unreachable,
12878 };
12879 },
12880 .canceled => unreachable,
12881 .blocked => unreachable,
12882 .blocked_windows_dns => unreachable,
12883 .blocked_canceling => unreachable,
12884 }
12885 },
12886 }
12887 }
12888
12889 fn wake(ptr: *const u32, max_waiters: u32) void {
12890 if (max_waiters == 0) return;
12891
12892 const bucket = bucketForAddress(@intFromPtr(ptr));
12893
12894 // To ensure the store to `ptr` is ordered before this check, we effectively want a `.release`
12895 // load, but that doesn't exist in the C11 memory model, so emulate it with a non-mutating rmw.
12896 if (bucket.num_waiters.fetchAdd(0, .release) == 0) {
12897 @branchHint(.likely);
12898 return; // no waiters
12899 }
12900
12901 // Waiters removed from the linked list under the mutex so we can unpark their threads outside
12902 // of the critical section. This forms a singly-linked list of waiters using `Waiter.node.next`.
12903 var waking_head: ?*std.DoublyLinkedList.Node = null;
12904 {
12905 bucket.mutex.lock();
12906 defer bucket.mutex.unlock();
12907
12908 var num_removed: u32 = 0;
12909 var it = bucket.waiters.first;
12910 while (num_removed < max_waiters) {
12911 const waiter: *Waiter = @fieldParentPtr("node", it orelse break);
12912 it = waiter.node.next;
12913 if (waiter.address != @intFromPtr(ptr)) continue;
12914 const old_status = waiter.thread_status.fetchAnd(
12915 .{ .cancelation = @enumFromInt(0b110), .awaitable = .all_ones },
12916 .monotonic,
12917 );
12918 switch (old_status.cancelation) {
12919 .parked => {}, // state updated to `.none`
12920 .none => unreachable, // if another `wake` call is unparking this thread, it should have removed it from the list
12921 .canceling => continue, // race with a canceler who hasn't called `removeCanceledWaiter` yet
12922 .canceled => unreachable,
12923 .blocked => unreachable,
12924 .blocked_windows_dns => unreachable,
12925 .blocked_canceling => unreachable,
12926 }
12927 // We're waking this waiter. Remove them from the bucket and add them to our local list.
12928 bucket.waiters.remove(&waiter.node);
12929 waiter.node.next = waking_head;
12930 waking_head = &waiter.node;
12931 num_removed += 1;
12932 // Signal to `waiter` that they're about to be unparked, in case we're racing with their
12933 // timeout. See corresponding logic in `wake`.
12934 waiter.address = 0;
12935 }
12936
12937 _ = bucket.num_waiters.fetchSub(num_removed, .monotonic);
12938 }
12939
12940 var unpark_buf: [128]UnparkTid = undefined;
12941 var unpark_len: usize = 0;
12942
12943 // Finally, unpark the threads.
12944 while (waking_head) |node| {
12945 waking_head = node.next;
12946 const waiter: *Waiter = @fieldParentPtr("node", node);
12947 unpark_buf[unpark_len] = waiter.tid;
12948 unpark_len += 1;
12949 if (unpark_len == unpark_buf.len) {
12950 unpark(&unpark_buf, ptr);
12951 unpark_len = 0;
12952 }
12953 }
12954 if (unpark_len > 0) {
12955 unpark(unpark_buf[0..unpark_len], ptr);
12956 }
12957 }
12958
12959 fn removeCanceledWaiter(waiter: *Waiter) void {
12960 const bucket = bucketForAddress(waiter.address);
12961 bucket.mutex.lock();
12962 defer bucket.mutex.unlock();
12963 bucket.waiters.remove(&waiter.node);
12964 assert(bucket.num_waiters.fetchSub(1, .monotonic) > 0);
12965 }
12966};
12967const parking_sleep = struct {
12968 comptime {
12969 assert(use_parking_sleep);
12970 }
12971 fn sleep(timeout: Io.Timeout) Io.Cancelable!void {
12972 const opt_thread = Thread.current;
12973 cancelable: {
12974 const thread = opt_thread orelse break :cancelable;
12975 switch (thread.cancel_protection) {
12976 .blocked => break :cancelable,
12977 .unblocked => {},
12978 }
12979 thread.futex_waiter = null;
12980 {
12981 const old_status = thread.status.fetchOr(
12982 .{ .cancelation = @enumFromInt(0b001), .awaitable = .null },
12983 .release, // release `thread.futex_waiter`
12984 );
12985 switch (old_status.cancelation) {
12986 .none => {}, // status is now `.parked`
12987 .canceling => return error.Canceled, // status is now `.canceled`
12988 .canceled => break :cancelable, // status is still `.canceled`
12989 .parked => unreachable,
12990 .blocked => unreachable,
12991 .blocked_windows_dns => unreachable,
12992 .blocked_canceling => unreachable,
12993 }
12994 }
12995 if (park(timeout, null)) {
12996 // The only reason this could possibly happen is cancelation.
12997 const old_status = thread.status.load(.monotonic);
12998 assert(old_status.cancelation == .canceling);
12999 thread.status.store(
13000 .{ .cancelation = .canceled, .awaitable = old_status.awaitable },
13001 .monotonic,
13002 );
13003 return error.Canceled;
13004 } else |err| switch (err) {
13005 error.Timeout => {
13006 // We're not out of the woods yet: an unpark could race with the timeout.
13007 const old_status = thread.status.fetchAnd(
13008 .{ .cancelation = @enumFromInt(0b110), .awaitable = .all_ones },
13009 .monotonic,
13010 );
13011 switch (old_status.cancelation) {
13012 .parked => return, // No race; new status is `.none`
13013 .canceling => {
13014 // Race condition: the timeout was reached, then someone tried to unpark
13015 // us for a cancelation. Whoever did that will have called `unpark`, so
13016 // drop that unpark request by waiting for it.
13017 // Status is still `.canceling`.
13018 park(.none, null) catch |e| switch (e) {
13019 error.Timeout => unreachable,
13020 };
13021 return;
13022 },
13023 .none => unreachable,
13024 .canceled => unreachable,
13025 .blocked => unreachable,
13026 .blocked_windows_dns => unreachable,
13027 .blocked_canceling => unreachable,
13028 }
13029 },
13030 }
13031 }
13032 // Uncancelable sleep; we expect not to be manually unparked.
13033 if (park(timeout, null)) {
13034 unreachable; // unexpected unpark
13035 } else |err| switch (err) {
13036 error.Timeout => return,
13037 }
13038 }
13039};
13040
13041/// `addr_hint` has no semantic effect, but may allow the OS to optimize this operation.
13042fn park(timeout: Io.Timeout, addr_hint: ?*const anyopaque) error{Timeout}!void {
13043 comptime assert(use_parking_futex or use_parking_sleep);
13044 switch (builtin.target.os.tag) {
13045 .windows => {
13046 var timeout_buf: windows.LARGE_INTEGER = undefined;
13047 const raw_timeout: ?*windows.LARGE_INTEGER = timeout: switch (timeout) {
13048 .none => null,
13049 .deadline => |timestamp| continue :timeout .{ .duration = .{
13050 .clock = timestamp.clock,
13051 .raw = (nowWindows(timestamp.clock) catch unreachable).durationTo(timestamp.raw),
13052 } },
13053 .duration => |duration| {
13054 _ = duration.clock; // Windows only supports monotonic
13055 timeout_buf = @intCast(@divTrunc(-duration.raw.nanoseconds, 100));
13056 break :timeout &timeout_buf;
13057 },
13058 };
13059 // `RtlWaitOnAddress` passes the futex address in as the first argument to this call,
13060 // but it's unclear what that actually does, especially since `NtAlertThreadByThreadId`
13061 // does *not* accept the address so the kernel can't really be using it as a hint. An
13062 // old Microsoft blog post discusses a more traditional futex-like mechanism in the
13063 // kernel which definitely isn't how `RtlWaitOnAddress` works today:
13064 //
13065 // https://devblogs.microsoft.com/oldnewthing/20160826-00/?p=94185
13066 //
13067 // ...so it's possible this argument is simply a remnant which no longer does anything
13068 // (perhaps the implementation changed during development but someone forgot to remove
13069 // this parameter). However, to err on the side of caution, let's match the behavior of
13070 // `RtlWaitOnAddress` and pass the pointer, in case the kernel ever does something
13071 // stupid such as trying to dereference it.
13072 switch (windows.ntdll.NtWaitForAlertByThreadId(addr_hint, raw_timeout)) {
13073 .ALERTED => return,
13074 .TIMEOUT => return error.Timeout,
13075 else => unreachable,
13076 }
13077 },
13078 .netbsd => {
13079 var ts_buf: posix.timespec = undefined;
13080 const ts: ?*posix.timespec, const abstime: bool, const clock_real: bool = switch (timeout) {
13081 .none => .{ null, false, false },
13082 .deadline => |timestamp| timeout: {
13083 ts_buf = timestampToPosix(timestamp.raw.nanoseconds);
13084 break :timeout .{ &ts_buf, true, timestamp.clock == .real };
13085 },
13086 .duration => |duration| timeout: {
13087 ts_buf = timestampToPosix(duration.raw.nanoseconds);
13088 break :timeout .{ &ts_buf, false, duration.clock == .real };
13089 },
13090 };
13091 switch (posix.errno(std.c._lwp_park(
13092 if (clock_real) .REALTIME else .MONOTONIC,
13093 .{ .ABSTIME = abstime },
13094 ts,
13095 0,
13096 addr_hint,
13097 null,
13098 ))) {
13099 .SUCCESS, .ALREADY, .INTR => return,
13100 .TIMEDOUT => return error.Timeout,
13101 .INVAL => unreachable,
13102 .SRCH => unreachable,
13103 else => unreachable,
13104 }
13105 },
13106 .illumos => @panic("TODO: illumos lwp_park"),
13107 else => comptime unreachable,
13108 }
13109}
13110
13111const UnparkTid = switch (builtin.target.os.tag) {
13112 // `NtAlertMultipleThreadByThreadId` is weird and wants 64-bit thread handles?
13113 .windows => usize,
13114 else => std.Thread.Id,
13115};
13116/// `addr_hint` has no semantic effect, but may allow the OS to optimize this operation.
13117fn unpark(tids: []const UnparkTid, addr_hint: ?*const anyopaque) void {
13118 comptime assert(use_parking_futex or use_parking_sleep);
13119 switch (builtin.target.os.tag) {
13120 .windows => {
13121 // TODO: this condition is currently disabled because mingw-w64 does not contain this
13122 // symbol. Once it's added, enable this check to use the new bulk API where possible.
13123 if (false and (builtin.os.version_range.windows.isAtLeast(.win11_dt) orelse false)) {
13124 _ = windows.ntdll.NtAlertMultipleThreadByThreadId(tids.ptr, @intCast(tids.len), null, null);
13125 } else {
13126 for (tids) |tid| {
13127 _ = windows.ntdll.NtAlertThreadByThreadId(@intCast(tid));
13128 }
13129 }
13130 },
13131 .netbsd => {
13132 switch (posix.errno(std.c._lwp_unpark_all(@ptrCast(tids.ptr), tids.len, addr_hint))) {
13133 .SUCCESS => return,
13134 // For errors, fall through to a loop over `tids`, though this is only expected to
13135 // be possible for ENOMEM (and even that is questionable).
13136 .SRCH => recoverableOsBugDetected(),
13137 .FAULT => recoverableOsBugDetected(),
13138 .INVAL => recoverableOsBugDetected(),
13139 .NOMEM => {},
13140 else => recoverableOsBugDetected(),
13141 }
13142 for (tids) |tid| {
13143 switch (posix.errno(std.c._lwp_unpark(@bitCast(tid), addr_hint))) {
13144 .SUCCESS => {},
13145 .SRCH => recoverableOsBugDetected(),
13146 else => recoverableOsBugDetected(),
13147 }
13148 }
13149 },
13150 .illumos => @panic("TODO: illumos lwp_unpark"),
13151 else => comptime unreachable,
13152 }
13153}
lib/std/Io/Threaded/test.zig+46-1
......@@ -124,7 +124,7 @@ test "Group.async context alignment" {
124124 var group: std.Io.Group = .init;
125125 var result: ByteArray512 = undefined;
126126 group.async(io, concatByteArraysResultPtr, .{ a, b, &result });
127 group.awaitUncancelable(io);
127 try group.await(io);
128128 try std.testing.expectEqualSlices(u8, &expected.x, &result.x);
129129}
130130
......@@ -141,3 +141,48 @@ test "async with array return type" {
141141 const result = future.await(io);
142142 try std.testing.expectEqualSlices(u8, &@as([32]u8, @splat(5)), &result);
143143}
144
145test "cancel blocked read from pipe" {
146 const global = struct {
147 fn readFromPipe(io: Io, pipe: Io.File) !void {
148 var buf: [1]u8 = undefined;
149 if (pipe.readStreaming(io, &.{&buf})) |_| {
150 return error.UnexpectedData;
151 } else |err| switch (err) {
152 error.Canceled => return,
153 else => |e| return e,
154 }
155 }
156 };
157
158 var threaded: std.Io.Threaded = .init(std.testing.allocator, .{});
159 defer threaded.deinit();
160 const io = threaded.io();
161
162 var read_end: Io.File = undefined;
163 var write_end: Io.File = undefined;
164 switch (builtin.target.os.tag) {
165 .wasi => return error.SkipZigTest,
166 .windows => try std.os.windows.CreatePipe(&read_end.handle, &write_end.handle, &.{
167 .nLength = @sizeOf(std.os.windows.SECURITY_ATTRIBUTES),
168 .lpSecurityDescriptor = null,
169 .bInheritHandle = std.os.windows.FALSE,
170 }),
171 else => {
172 const pipe = try std.posix.pipe();
173 read_end = .{ .handle = pipe[0] };
174 write_end = .{ .handle = pipe[1] };
175 },
176 }
177 defer {
178 read_end.close(io);
179 write_end.close(io);
180 }
181
182 var future = io.concurrent(global.readFromPipe, .{ io, read_end }) catch |err| switch (err) {
183 error.ConcurrencyUnavailable => return error.SkipZigTest,
184 };
185 defer _ = future.cancel(io) catch {};
186 try io.sleep(.fromMilliseconds(10), .awake);
187 try future.cancel(io);
188}
lib/std/Io/net/HostName.zig+9-13
......@@ -233,11 +233,12 @@ pub fn connect(
233233 if (result) |stream| {
234234 return stream;
235235 } else |err| switch (err) {
236 error.Canceled => unreachable,
237
236238 error.SystemResources,
237239 error.OptionUnsupported,
238240 error.ProcessFdQuotaExceeded,
239241 error.SystemFdQuotaExceeded,
240 error.Canceled,
241242 => |e| return e,
242243
243244 error.WouldBlock => return error.Unexpected,
......@@ -259,6 +260,8 @@ pub fn connect(
259260/// Asynchronously establishes a connection to all IP addresses associated with
260261/// a host name, adding them to a results queue upon completion.
261262///
263/// `error.Canceled` will never be added to the queue, but other errors may be.
264///
262265/// Closes `results` before return, even on error.
263266///
264267/// Asserts `results` is not closed until this call returns.
......@@ -299,22 +302,15 @@ fn enqueueConnection(
299302 io: Io,
300303 queue: *Io.Queue(IpAddress.ConnectError!Stream),
301304 options: IpAddress.ConnectOptions,
302) void {
303 enqueueConnectionFallible(address, io, queue, options) catch |err| switch (err) {
304 error.Canceled => {},
305 };
306}
307fn enqueueConnectionFallible(
308 address: IpAddress,
309 io: Io,
310 queue: *Io.Queue(IpAddress.ConnectError!Stream),
311 options: IpAddress.ConnectOptions,
312305) Io.Cancelable!void {
313 const result = address.connect(io, options);
306 const result = address.connect(io, options) catch |err| switch (err) {
307 error.Canceled => |e| return e,
308 else => |e| e, // other errors go in the result queue
309 };
314310 errdefer if (result) |s| s.close(io) else |_| {};
315311 queue.putOne(io, result) catch |err| switch (err) {
316 error.Closed => unreachable, // `queue` must not be closed
317312 error.Canceled => |e| return e,
313 error.Closed => unreachable, // `queue` must not be closed
318314 };
319315}
320316
lib/std/Io/test.zig+110-34
......@@ -194,7 +194,7 @@ test "Group" {
194194 group.async(io, count, .{ 1, 10, &results[0] });
195195 group.async(io, count, .{ 20, 30, &results[1] });
196196
197 group.awaitUncancelable(io);
197 try group.await(io);
198198
199199 try testing.expectEqualSlices(usize, &.{ 45, 245 }, &results);
200200}
......@@ -207,49 +207,53 @@ fn count(a: usize, b: usize, result: *usize) void {
207207 result.* = sum;
208208}
209209
210test "Group cancelation" {
211 const io = testing.io;
210test "Group.cancel" {
211 const global = struct {
212 fn sleep(io: Io, result: *usize) Io.Cancelable!void {
213 defer result.* = 1;
214 io.sleep(.fromSeconds(100_000), .awake) catch |err| switch (err) {
215 error.Canceled => |e| return e,
216 else => {},
217 };
218 }
212219
213 var group: Io.Group = .init;
214 var results: [4]usize = .{ 0, 0, 0, 0 };
220 fn sleepRecancel(io: Io, result: *usize) void {
221 io.sleep(.fromSeconds(100_000), .awake) catch |err| switch (err) {
222 error.Canceled => io.recancel(),
223 else => {},
224 };
225 result.* = 1;
226 }
215227
216 // TODO when robust cancelation is available, make the sleep timeouts much
217 // longer so that it causes the unit test to be failed if not canceled.
218 // https://codeberg.org/ziglang/zig/issues/30049
219 group.async(io, sleep, .{ io, &results[0] });
220 group.async(io, sleep, .{ io, &results[1] });
221 group.async(io, sleepUncancelable, .{ io, &results[2] });
222 group.async(io, sleepRecancel, .{ io, &results[3] });
228 fn sleepUncancelable(io: Io, result: *usize) void {
229 const old_prot = io.swapCancelProtection(.blocked);
230 defer _ = io.swapCancelProtection(old_prot);
231 // Short sleep interval, because this one won't be canceled (that's the point!).
232 io.sleep(.fromMilliseconds(50), .awake) catch {};
233 result.* = 1;
234 }
235 };
223236
224 group.cancel(io);
237 const io = testing.io;
225238
226 try testing.expectEqualSlices(usize, &.{ 1, 1, 1, 1 }, &results);
227}
239 var group: Io.Group = .init;
240 var results: [5]usize = @splat(0);
228241
229fn sleep(io: Io, result: *usize) error{Canceled}!void {
230 defer result.* = 1;
231 io.sleep(.fromMilliseconds(1), .awake) catch |err| switch (err) {
232 error.Canceled => |e| return e,
233 else => {},
242 group.concurrent(io, global.sleep, .{ io, &results[0] }) catch |err| switch (err) {
243 error.ConcurrencyUnavailable => return error.SkipZigTest,
234244 };
235}
245 try group.concurrent(io, global.sleep, .{ io, &results[1] });
246 try group.concurrent(io, global.sleepRecancel, .{ io, &results[2] });
247 try group.concurrent(io, global.sleepUncancelable, .{ io, &results[3] });
248 // Because this one doesn't block until canceled, it is safe to run asynchronously.
249 group.async(io, global.sleepUncancelable, .{ io, &results[4] });
236250
237fn sleepUncancelable(io: Io, result: *usize) void {
238 const old_prot = io.swapCancelProtection(.blocked);
239 defer _ = io.swapCancelProtection(old_prot);
240 io.sleep(.fromMilliseconds(1), .awake) catch {};
241 result.* = 1;
242}
251 group.cancel(io);
243252
244fn sleepRecancel(io: Io, result: *usize) void {
245 io.sleep(.fromMilliseconds(1), .awake) catch |err| switch (err) {
246 error.Canceled => io.recancel(),
247 else => {},
248 };
249 result.* = 1;
253 try testing.expectEqualSlices(usize, &.{ 1, 1, 1, 1, 1 }, &results);
250254}
251255
252test "Group concurrent" {
256test "Group.concurrent" {
253257 const io = testing.io;
254258
255259 var group: Io.Group = .init;
......@@ -488,3 +492,75 @@ test "swapCancelProtection" {
488492 // Because it reached the `set`, it should be too late for `sleepThenSet` to see `error.Canceled`.
489493 try set_future.cancel(io);
490494}
495
496test "cancel futex wait" {
497 const global = struct {
498 fn blockUntilCanceled(io: Io) void {
499 while (true) io.futexWait(u32, &0, 0) catch |err| switch (err) {
500 error.Canceled => return,
501 };
502 }
503 };
504
505 const io = std.testing.io;
506
507 var future = io.concurrent(global.blockUntilCanceled, .{io}) catch |err| switch (err) {
508 error.ConcurrencyUnavailable => return error.SkipZigTest,
509 };
510 defer future.cancel(io);
511
512 // Give the task some time to start so that we cancel while it is blocked.
513 try io.sleep(.fromMilliseconds(20), .awake);
514}
515
516test "cancel sleep" {
517 const global = struct {
518 fn blockUntilCanceled(io: Io) void {
519 while (true) io.sleep(.fromSeconds(100_000), .awake) catch |err| switch (err) {
520 error.Canceled => return,
521 error.UnsupportedClock => @panic("unsupported clock"),
522 error.Unexpected => @panic("unexpected"),
523 };
524 }
525 };
526
527 const io = std.testing.io;
528
529 var future = io.concurrent(global.blockUntilCanceled, .{io}) catch |err| switch (err) {
530 error.ConcurrencyUnavailable => return error.SkipZigTest,
531 };
532 defer future.cancel(io);
533
534 // Give the task some time to start so that we cancel while it is blocked.
535 try io.sleep(.fromMilliseconds(20), .awake);
536}
537
538test "tasks spawned in group after Group.cancel are canceled" {
539 const global = struct {
540 fn waitThenSpawn(io: Io, group: *Io.Group) void {
541 _ = io.swapCancelProtection(.blocked);
542 group.concurrent(io, blockUntilCanceled, .{io}) catch {};
543 io.sleep(.fromMilliseconds(10), .awake) catch unreachable;
544 group.concurrent(io, blockUntilCanceled, .{io}) catch {};
545 group.async(io, blockUntilCanceled, .{io});
546 }
547 fn blockUntilCanceled(io: Io) Io.Cancelable!void {
548 while (true) io.sleep(.fromSeconds(100_000), .awake) catch |err| switch (err) {
549 error.Canceled => |e| return e,
550 error.UnsupportedClock => @panic("unsupported clock"),
551 error.Unexpected => @panic("unexpected"),
552 };
553 }
554 };
555
556 const io = std.testing.io;
557
558 var group: Io.Group = .init;
559 defer group.cancel(io);
560
561 group.concurrent(io, global.blockUntilCanceled, .{io}) catch |err| switch (err) {
562 error.ConcurrencyUnavailable => return error.SkipZigTest,
563 };
564 try io.sleep(.fromMilliseconds(10), .awake); // let that first sleep start up
565 try group.concurrent(io, global.waitThenSpawn, .{ io, &group });
566}
lib/std/c.zig+3
......@@ -11399,6 +11399,9 @@ pub const vm_region_flavor_t = darwin.vm_region_flavor_t;
1139911399
1140011400pub const _ksiginfo = netbsd._ksiginfo;
1140111401pub const _lwp_self = netbsd._lwp_self;
11402pub const _lwp_park = netbsd._lwp_park;
11403pub const _lwp_unpark = netbsd._lwp_unpark;
11404pub const _lwp_unpark_all = netbsd._lwp_unpark_all;
1140211405pub const lwpid_t = netbsd.lwpid_t;
1140311406
1140411407pub const lwp_gettid = dragonfly.lwp_gettid;
lib/std/c/netbsd.zig+19-1
......@@ -1,17 +1,35 @@
11const std = @import("../std.zig");
22const clock_t = std.c.clock_t;
3const clockid_t = std.c.clockid_t;
34const pid_t = std.c.pid_t;
45const pthread_t = std.c.pthread_t;
56const sigval_t = std.c.sigval_t;
67const uid_t = std.c.uid_t;
8const timespec = std.c.timespec;
79
810pub extern "c" fn ptrace(request: c_int, pid: pid_t, addr: ?*anyopaque, data: c_int) c_int;
911
1012pub const lwpid_t = i32;
1113
12pub extern "c" fn _lwp_self() lwpid_t;
1314pub extern "c" fn pthread_setname_np(thread: pthread_t, name: [*:0]const u8, arg: ?*anyopaque) c_int;
1415
16pub extern "c" fn _lwp_self() lwpid_t;
17
18pub extern "c" fn _lwp_park(
19 clock_id: clockid_t,
20 flags: packed struct(u32) {
21 ABSTIME: bool = false,
22 unused: u31 = 0,
23 },
24 ts: ?*timespec,
25 unpark: lwpid_t,
26 hint: ?*const anyopaque,
27 unpark_hint: ?*const anyopaque,
28) c_int;
29
30pub extern "c" fn _lwp_unpark(lwp: lwpid_t, hint: ?*const anyopaque) c_int;
31pub extern "c" fn _lwp_unpark_all(targets: [*]const lwpid_t, ntargets: usize, hint: ?*const anyopaque) c_int;
32
1533pub const TCIFLUSH = 1;
1634pub const TCOFLUSH = 2;
1735pub const TCIOFLUSH = 3;
lib/std/debug/SelfInfo/Windows.zig+1-2
......@@ -315,8 +315,7 @@ const Module = struct {
315315 );
316316 if (len == 0) return error.MissingDebugInfo;
317317 const name_w = name_buffer[0 .. len + 4 :0];
318 // TODO eliminate the reference to Io.Threaded.global_single_threaded here
319 const coff_file = Io.Threaded.global_single_threaded.dirOpenFileWtf16(null, name_w, .{}) catch |err| switch (err) {
318 const coff_file = Io.Threaded.dirOpenFileWtf16(null, name_w, .{}) catch |err| switch (err) {
320319 error.Canceled => |e| return e,
321320 error.Unexpected => |e| return e,
322321 error.FileNotFound => return error.MissingDebugInfo,
lib/std/http/test.zig+16-3
......@@ -1139,13 +1139,26 @@ fn createTestServer(io: Io, S: type) !*TestServer {
11391139 }
11401140
11411141 const address = try net.IpAddress.parse("127.0.0.1", 0);
1142 const test_server = try std.testing.allocator.create(TestServer);
1142
1143 const gpa = std.testing.allocator;
1144
1145 const test_server = try gpa.create(TestServer);
1146 errdefer gpa.destroy(test_server);
1147
1148 var net_server = try address.listen(io, .{ .reuse_address = true });
1149 errdefer net_server.deinit(io);
1150
1151 // populate `test_server` first so `S.run` can use it
11431152 test_server.* = .{
11441153 .io = io,
1145 .net_server = try address.listen(io, .{ .reuse_address = true }),
1154 .net_server = net_server,
11461155 .shutting_down = false,
1147 .server_thread = try std.Thread.spawn(.{}, S.run, .{test_server}),
1156 .server_thread = undefined, // set below
11481157 };
1158
1159 test_server.server_thread = try .spawn(.{}, S.run, .{test_server});
1160 errdefer comptime unreachable;
1161
11491162 return test_server;
11501163}
11511164
lib/std/os/windows.zig+6-66
......@@ -2253,7 +2253,7 @@ pub fn GetProcessHeap() ?*HEAP {
22532253pub const OBJECT_ATTRIBUTES = extern struct {
22542254 Length: ULONG,
22552255 RootDirectory: ?HANDLE,
2256 ObjectName: *UNICODE_STRING,
2256 ObjectName: ?*UNICODE_STRING,
22572257 Attributes: ATTRIBUTES,
22582258 SecurityDescriptor: ?*anyopaque,
22592259 SecurityQualityOfService: ?*anyopaque,
......@@ -2306,6 +2306,7 @@ pub const OpenError = error{
23062306 NetworkNotFound,
23072307 AntivirusInterference,
23082308 BadPathName,
2309 OperationCanceled,
23092310};
23102311
23112312pub const OpenFileOptions = struct {
......@@ -2405,6 +2406,7 @@ pub fn OpenFile(sub_path_w: []const u16, options: OpenFileOptions) OpenError!HAN
24052406 continue;
24062407 },
24072408 .VIRUS_INFECTED, .VIRUS_DELETED => return error.AntivirusInterference,
2409 .CANCELLED => return error.OperationCanceled,
24082410 else => return unexpectedStatus(rc),
24092411 }
24102412 }
......@@ -2985,6 +2987,7 @@ pub const ReadLinkError = error{
29852987 AntivirusInterference,
29862988 UnsupportedReparsePointType,
29872989 NotLink,
2990 OperationCanceled,
29882991};
29892992
29902993/// `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
30153018 const rc = DeviceIoControl(result_handle, FSCTL.GET_REPARSE_POINT, .{ .out = reparse_buf[0..] });
30163019 switch (rc) {
30173020 .SUCCESS => {},
3021 .CANCELLED => return error.OperationCanceled,
30183022 .NOT_A_REPARSE_POINT => return error.NotLink,
30193023 else => return unexpectedStatus(rc),
30203024 }
......@@ -3339,71 +3343,6 @@ pub fn GetStdHandle(handle_id: DWORD) GetStdHandleError!HANDLE {
33393343 return handle;
33403344}
33413345
3342pub const SetFilePointerError = error{
3343 Unseekable,
3344 Unexpected,
3345};
3346
3347/// The SetFilePointerEx function with the `dwMoveMethod` parameter set to `FILE_BEGIN`.
3348pub fn SetFilePointerEx_BEGIN(handle: HANDLE, offset: u64) SetFilePointerError!void {
3349 // "The starting point is zero or the beginning of the file. If [FILE_BEGIN]
3350 // is specified, then the liDistanceToMove parameter is interpreted as an unsigned value."
3351 // https://docs.microsoft.com/en-us/windows/desktop/api/fileapi/nf-fileapi-setfilepointerex
3352 const ipos = @as(LARGE_INTEGER, @bitCast(offset));
3353 if (kernel32.SetFilePointerEx(handle, ipos, null, FILE_BEGIN) == 0) {
3354 switch (GetLastError()) {
3355 .INVALID_FUNCTION => return error.Unseekable,
3356 .NEGATIVE_SEEK => return error.Unseekable,
3357 .INVALID_PARAMETER => unreachable,
3358 .INVALID_HANDLE => unreachable,
3359 else => |err| return unexpectedError(err),
3360 }
3361 }
3362}
3363
3364/// The SetFilePointerEx function with the `dwMoveMethod` parameter set to `FILE_CURRENT`.
3365pub fn SetFilePointerEx_CURRENT(handle: HANDLE, offset: i64) SetFilePointerError!void {
3366 if (kernel32.SetFilePointerEx(handle, offset, null, FILE_CURRENT) == 0) {
3367 switch (GetLastError()) {
3368 .INVALID_FUNCTION => return error.Unseekable,
3369 .NEGATIVE_SEEK => return error.Unseekable,
3370 .INVALID_PARAMETER => unreachable,
3371 .INVALID_HANDLE => unreachable,
3372 else => |err| return unexpectedError(err),
3373 }
3374 }
3375}
3376
3377/// The SetFilePointerEx function with the `dwMoveMethod` parameter set to `FILE_END`.
3378pub fn SetFilePointerEx_END(handle: HANDLE, offset: i64) SetFilePointerError!void {
3379 if (kernel32.SetFilePointerEx(handle, offset, null, FILE_END) == 0) {
3380 switch (GetLastError()) {
3381 .INVALID_FUNCTION => return error.Unseekable,
3382 .NEGATIVE_SEEK => return error.Unseekable,
3383 .INVALID_PARAMETER => unreachable,
3384 .INVALID_HANDLE => unreachable,
3385 else => |err| return unexpectedError(err),
3386 }
3387 }
3388}
3389
3390/// The SetFilePointerEx function with parameters to get the current offset.
3391pub fn SetFilePointerEx_CURRENT_get(handle: HANDLE) SetFilePointerError!u64 {
3392 var result: LARGE_INTEGER = undefined;
3393 if (kernel32.SetFilePointerEx(handle, 0, &result, FILE_CURRENT) == 0) {
3394 switch (GetLastError()) {
3395 .INVALID_FUNCTION => return error.Unseekable,
3396 .NEGATIVE_SEEK => return error.Unseekable,
3397 .INVALID_PARAMETER => unreachable,
3398 .INVALID_HANDLE => unreachable,
3399 else => |err| return unexpectedError(err),
3400 }
3401 }
3402 // Based on the docs for FILE_BEGIN, it seems that the returned signed integer
3403 // should be interpreted as an unsigned integer.
3404 return @as(u64, @bitCast(result));
3405}
3406
34073346pub const QueryObjectNameError = error{
34083347 AccessDenied,
34093348 InvalidHandle,
......@@ -3562,6 +3501,7 @@ pub fn GetFinalPathNameByHandle(
35623501 error.NetworkNotFound => return error.Unexpected,
35633502 error.AntivirusInterference => return error.Unexpected,
35643503 error.BadPathName => return error.Unexpected,
3504 error.OperationCanceled => @panic("TODO: better integrate cancelation"),
35653505 else => |e| return e,
35663506 };
35673507 defer CloseHandle(mgmt_handle);
lib/std/os/windows/ntdll.zig+27
......@@ -554,3 +554,30 @@ pub extern "ntdll" fn RtlWakeConditionVariable(
554554pub extern "ntdll" fn RtlWakeAllConditionVariable(
555555 ConditionVariable: *CONDITION_VARIABLE,
556556) callconv(.winapi) void;
557
558pub extern "ntdll" fn NtWaitForAlertByThreadId(
559 Address: ?*const anyopaque,
560 Timeout: ?*const LARGE_INTEGER,
561) callconv(.winapi) NTSTATUS;
562pub extern "ntdll" fn NtAlertThreadByThreadId(
563 ThreadId: DWORD,
564) callconv(.winapi) NTSTATUS;
565pub extern "ntdll" fn NtAlertMultipleThreadByThreadId(
566 ThreadIds: [*]const ULONG_PTR,
567 ThreadCount: ULONG,
568 Unknown1: ?*const anyopaque,
569 Unknown2: ?*const anyopaque,
570) callconv(.winapi) NTSTATUS;
571
572pub extern "ntdll" fn NtOpenThread(
573 ThreadHandle: *HANDLE,
574 DesiredAccess: ACCESS_MASK,
575 ObjectAttributes: *const OBJECT_ATTRIBUTES,
576 ClientId: *const windows.CLIENT_ID,
577) callconv(.winapi) NTSTATUS;
578
579pub extern "ntdll" fn NtCancelSynchronousIoFile(
580 ThreadHandle: HANDLE,
581 RequestToCancel: ?*IO_STATUS_BLOCK,
582 IoStatusBlock: *IO_STATUS_BLOCK,
583) callconv(.winapi) NTSTATUS;
lib/std/posix.zig+1
......@@ -1124,6 +1124,7 @@ pub fn mkdirW(dir_path_w: []const u16, mode: mode_t) MakeDirError!void {
11241124 error.NoDevice => return error.Unexpected,
11251125 error.WouldBlock => return error.Unexpected,
11261126 error.AntivirusInterference => return error.Unexpected,
1127 error.OperationCanceled => return error.Unexpected,
11271128 else => |e| return e,
11281129 };
11291130 windows.CloseHandle(sub_dir_handle);
lib/std/process/Child.zig+2-2
......@@ -778,6 +778,7 @@ fn spawnWindows(self: *Child, io: Io) SpawnError!void {
778778 error.WouldBlock => return error.Unexpected, // not possible for "NUL"
779779 error.NetworkNotFound => return error.Unexpected, // not possible for "NUL"
780780 error.AntivirusInterference => return error.Unexpected, // not possible for "NUL"
781 error.OperationCanceled => return error.Unexpected, // we're not canceling the operation
781782 else => |e| return e,
782783 }
783784 else
......@@ -1129,8 +1130,7 @@ fn windowsCreateProcessPathExt(
11291130 defer dir_buf.shrinkRetainingCapacity(dir_path_len);
11301131 const dir_path_z = dir_buf.items[0 .. dir_buf.items.len - 1 :0];
11311132 const prefixed_path = try windows.wToPrefixedFileW(null, dir_path_z);
1132 // TODO eliminate this reference
1133 break :dir Io.Threaded.global_single_threaded.dirOpenDirWindows(.cwd(), prefixed_path.span(), .{
1133 break :dir Io.Threaded.dirOpenDirWindows(.cwd(), prefixed_path.span(), .{
11341134 .iterate = true,
11351135 }) catch return error.FileNotFound;
11361136 };
src/codegen/wasm/CodeGen.zig+13-2
......@@ -6973,9 +6973,20 @@ fn airShlSat(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void {
69736973 return cg.fail("TODO: Saturating shifting left for integers with bitsize '{d}'", .{int_info.bits});
69746974 }
69756975
6976 const lhs = try cg.resolveInst(bin_op.lhs);
6977 const rhs = try cg.resolveInst(bin_op.rhs);
69786976 const wasm_bits = toWasmBits(int_info.bits).?;
6977
6978 const lhs = try cg.resolveInst(bin_op.lhs);
6979 const rhs = rhs: {
6980 const rhs = try cg.resolveInst(bin_op.rhs);
6981 const rhs_ty = cg.typeOf(bin_op.rhs);
6982 // The type of `rhs` is the log2 int of the type of `lhs`, but WASM wants the lhs and rhs types to match.
6983 if (toWasmBits(@intCast(rhs_ty.bitSize(zcu))).? == wasm_bits) {
6984 break :rhs rhs; // the WASM types match, so no cast necessary
6985 }
6986 const casted = try cg.intcast(rhs, rhs_ty, ty);
6987 break :rhs try casted.toLocal(cg, ty);
6988 };
6989
69796990 const result = try cg.allocLocal(ty);
69806991
69816992 if (wasm_bits == int_info.bits) {
tools/incr-check.zig+51-43
......@@ -6,6 +6,27 @@ const Cache = std.Build.Cache;
66
77const usage = "usage: incr-check <zig binary path> <input file> [--zig-lib-dir lib] [--debug-log foo] [--preserve-tmp] [--zig-cc-binary /path/to/zig]";
88
9pub const std_options: std.Options = .{
10 .logFn = logImpl,
11};
12var log_cur_update: ?struct { *const Case.Target, *const Case.Update } = null;
13fn logImpl(
14 comptime level: std.log.Level,
15 comptime scope: @EnumLiteral(),
16 comptime format: []const u8,
17 args: anytype,
18) void {
19 const target, const update = log_cur_update orelse {
20 return std.log.defaultLog(level, scope, format, args);
21 };
22 std.log.defaultLog(
23 level,
24 scope,
25 "[{s}-{t} '{s}'] " ++ format,
26 .{ target.query, target.backend, update.name } ++ args,
27 );
28}
29
930pub fn main() !void {
1031 const fatal = std.process.fatal;
1132
......@@ -225,6 +246,9 @@ pub fn main() !void {
225246 std.log.scoped(.status).info("update: '{s}'", .{update.name});
226247 }
227248
249 log_cur_update = .{ &target, &update };
250 defer log_cur_update = null;
251
228252 eval.write(update);
229253 try eval.requestUpdate();
230254 try eval.check(&poller, update, update_node);
......@@ -295,9 +319,9 @@ const Eval = struct {
295319 if (stderr.bufferedLen() > 0) {
296320 const stderr_data = try poller.toOwnedSlice(.stderr);
297321 if (eval.allow_stderr) {
298 std.log.info("error_bundle included stderr:\n{s}", .{stderr_data});
322 std.log.info("error_bundle stderr:\n{s}", .{stderr_data});
299323 } else {
300 eval.fatal("error_bundle included unexpected stderr:\n{s}", .{stderr_data});
324 eval.fatal("error_bundle unexpected stderr:\n{s}", .{stderr_data});
301325 }
302326 }
303327 if (result_error_bundle.errorMessageCount() != 0) {
......@@ -312,9 +336,9 @@ const Eval = struct {
312336 if (stderr.bufferedLen() > 0) {
313337 const stderr_data = try poller.toOwnedSlice(.stderr);
314338 if (eval.allow_stderr) {
315 std.log.info("emit_digest included stderr:\n{s}", .{stderr_data});
339 std.log.info("emit_digest stderr:\n{s}", .{stderr_data});
316340 } else {
317 eval.fatal("emit_digest included unexpected stderr:\n{s}", .{stderr_data});
341 eval.fatal("emit_digest unexpected stderr:\n{s}", .{stderr_data});
318342 }
319343 }
320344
......@@ -344,14 +368,14 @@ const Eval = struct {
344368
345369 if (stderr.bufferedLen() > 0) {
346370 if (eval.allow_stderr) {
347 std.log.info("update '{s}' included stderr:\n{s}", .{ update.name, stderr.buffered() });
371 std.log.info("stderr:\n{s}", .{stderr.buffered()});
348372 } else {
349 eval.fatal("update '{s}' failed:\n{s}", .{ update.name, stderr.buffered() });
373 eval.fatal("unexpected stderr:\n{s}", .{stderr.buffered()});
350374 }
351375 }
352376
353377 waitChild(eval.child, eval);
354 eval.fatal("update '{s}': compiler failed to send error_bundle or emit_bin_path", .{update.name});
378 eval.fatal("compiler failed to send error_bundle or emit_bin_path", .{});
355379 }
356380
357381 fn checkErrorOutcome(eval: *Eval, update: Case.Update, error_bundle: std.zig.ErrorBundle) !void {
......@@ -361,7 +385,7 @@ const Eval = struct {
361385 .compile_errors => |ce| ce,
362386 .stdout, .exit_code => {
363387 try error_bundle.renderToStderr(io, .{}, .auto);
364 eval.fatal("update '{s}': unexpected compile errors", .{update.name});
388 eval.fatal("unexpected compile errors", .{});
365389 },
366390 };
367391
......@@ -370,30 +394,29 @@ const Eval = struct {
370394 for (error_bundle.getMessages()) |err_idx| {
371395 if (expected_idx == expected.errors.len) {
372396 try error_bundle.renderToStderr(io, .{}, .auto);
373 eval.fatal("update '{s}': more errors than expected", .{update.name});
397 eval.fatal("more errors than expected", .{});
374398 }
375 try eval.checkOneError(update, error_bundle, expected.errors[expected_idx], false, err_idx);
399 try eval.checkOneError(error_bundle, expected.errors[expected_idx], false, err_idx);
376400 expected_idx += 1;
377401
378402 for (error_bundle.getNotes(err_idx)) |note_idx| {
379403 if (expected_idx == expected.errors.len) {
380404 try error_bundle.renderToStderr(io, .{}, .auto);
381 eval.fatal("update '{s}': more error notes than expected", .{update.name});
405 eval.fatal("more error notes than expected", .{});
382406 }
383 try eval.checkOneError(update, error_bundle, expected.errors[expected_idx], true, note_idx);
407 try eval.checkOneError(error_bundle, expected.errors[expected_idx], true, note_idx);
384408 expected_idx += 1;
385409 }
386410 }
387411
388412 if (!std.mem.eql(u8, error_bundle.getCompileLogOutput(), expected.compile_log_output)) {
389413 try error_bundle.renderToStderr(io, .{}, .auto);
390 eval.fatal("update '{s}': unexpected compile log output", .{update.name});
414 eval.fatal("unexpected compile log output", .{});
391415 }
392416 }
393417
394418 fn checkOneError(
395419 eval: *Eval,
396 update: Case.Update,
397420 eb: std.zig.ErrorBundle,
398421 expected: Case.ExpectedError,
399422 is_note: bool,
......@@ -423,7 +446,7 @@ const Eval = struct {
423446 !std.mem.eql(u8, expected.msg, msg))
424447 {
425448 eb.renderToStderr(io, .{}, .auto) catch {};
426 eval.fatal("update '{s}': compile error did not match expected error", .{update.name});
449 eval.fatal("compile error did not match expected error", .{});
427450 }
428451 }
429452
......@@ -444,7 +467,7 @@ const Eval = struct {
444467 .cbe => bin: {
445468 const rand_int = std.crypto.random.int(u64);
446469 const out_bin_name = "./out_" ++ std.fmt.hex(rand_int);
447 try eval.buildCOutput(update, emitted_path, out_bin_name, prog_node);
470 try eval.buildCOutput(emitted_path, out_bin_name, prog_node);
448471 break :bin out_bin_name;
449472 },
450473 };
......@@ -521,8 +544,7 @@ const Eval = struct {
521544 if (is_foreign) {
522545 // Chances are the foreign executor isn't available. Skip this evaluation.
523546 if (eval.allow_stderr) {
524 std.log.warn("update '{s}': skipping execution of '{s}' via executor for foreign target '{s}': {t}", .{
525 update.name,
547 std.log.warn("skipping execution of '{s}' via executor for foreign target '{s}': {t}", .{
526548 binary_path,
527549 try eval.target.resolved.zigTriple(eval.arena),
528550 err,
......@@ -530,16 +552,14 @@ const Eval = struct {
530552 }
531553 return;
532554 }
533 eval.fatal("update '{s}': failed to run the generated executable '{s}': {t}", .{
534 update.name, binary_path, err,
535 });
555 eval.fatal("failed to run the generated executable '{s}': {t}", .{ binary_path, err });
536556 };
537557
538558 // Some executors (looking at you, Wine) like throwing some stderr in, just for fun.
539559 // Therefore, we'll ignore stderr when using a foreign executor.
540560 if (!is_foreign and result.stderr.len != 0) {
541 std.log.err("update '{s}': generated executable '{s}' had unexpected stderr:\n{s}", .{
542 update.name, binary_path, result.stderr,
561 std.log.err("generated executable '{s}' had unexpected stderr:\n{s}", .{
562 binary_path, result.stderr,
543563 });
544564 }
545565
......@@ -548,18 +568,14 @@ const Eval = struct {
548568 .unknown, .compile_errors => unreachable,
549569 .stdout => |expected_stdout| {
550570 if (code != 0) {
551 eval.fatal("update '{s}': generated executable '{s}' failed with code {d}", .{
552 update.name, binary_path, code,
553 });
571 eval.fatal("generated executable '{s}' failed with code {d}", .{ binary_path, code });
554572 }
555573 try std.testing.expectEqualStrings(expected_stdout, result.stdout);
556574 },
557575 .exit_code => |expected_code| try std.testing.expectEqual(expected_code, result.term.Exited),
558576 },
559577 .Signal, .Stopped, .Unknown => {
560 eval.fatal("update '{s}': generated executable '{s}' terminated unexpectedly", .{
561 update.name, binary_path,
562 });
578 eval.fatal("generated executable '{s}' terminated unexpectedly", .{binary_path});
563579 },
564580 }
565581
......@@ -597,7 +613,7 @@ const Eval = struct {
597613 }
598614 }
599615
600 fn buildCOutput(eval: *Eval, update: Case.Update, c_path: []const u8, out_path: []const u8, prog_node: std.Progress.Node) !void {
616 fn buildCOutput(eval: *Eval, c_path: []const u8, out_path: []const u8, prog_node: std.Progress.Node) !void {
601617 std.debug.assert(eval.cc_child_args.items.len > 0);
602618
603619 const child_prog_node = prog_node.start("build cbe output", 0);
......@@ -612,28 +628,20 @@ const Eval = struct {
612628 .cwd = eval.tmp_dir_path,
613629 .progress_node = child_prog_node,
614630 }) catch |err| {
615 eval.fatal("update '{s}': failed to spawn zig cc for '{s}': {t}", .{ update.name, c_path, err });
631 eval.fatal("failed to spawn zig cc for '{s}': {t}", .{ c_path, err });
616632 };
617633 switch (result.term) {
618634 .Exited => |code| if (code != 0) {
619635 if (result.stderr.len != 0) {
620 std.log.err("update '{s}': zig cc stderr:\n{s}", .{
621 update.name, result.stderr,
622 });
636 std.log.err("zig cc stderr:\n{s}", .{result.stderr});
623637 }
624 eval.fatal("update '{s}': zig cc for '{s}' failed with code {d}", .{
625 update.name, c_path, code,
626 });
638 eval.fatal("zig cc for '{s}' failed with code {d}", .{ c_path, code });
627639 },
628640 .Signal, .Stopped, .Unknown => {
629641 if (result.stderr.len != 0) {
630 std.log.err("update '{s}': zig cc stderr:\n{s}", .{
631 update.name, result.stderr,
632 });
642 std.log.err("zig cc stderr:\n{s}", .{result.stderr});
633643 }
634 eval.fatal("update '{s}': zig cc for '{s}' terminated unexpectedly", .{
635 update.name, c_path,
636 });
644 eval.fatal("zig cc for '{s}' terminated unexpectedly", .{c_path});
637645 },
638646 }
639647 }