| ... | ... | @@ -4955,7 +4955,10 @@ pub fn dirOpenFileWtf16( |
| 4955 | 4955 | // kernel bug with retry attempts. |
| 4956 | 4956 | syscall.finish(); |
| 4957 | 4957 | if (max_attempts - attempt == 0) return error.FileBusy; |
| 4958 | | try parking_sleep.windowsRetrySleep((@as(u32, 1) << attempt) >> 1); |
| 4958 | try parking_sleep.sleep(.{ .duration = .{ |
| 4959 | .raw = .fromMilliseconds((@as(u32, 1) << attempt) >> 1), |
| 4960 | .clock = .awake, |
| 4961 | } }); |
| 4959 | 4962 | attempt += 1; |
| 4960 | 4963 | syscall = try .start(); |
| 4961 | 4964 | continue; |
| ... | ... | @@ -4977,7 +4980,10 @@ pub fn dirOpenFileWtf16( |
| 4977 | 4980 | // fixed by sleeping and retrying until the error goes away. |
| 4978 | 4981 | syscall.finish(); |
| 4979 | 4982 | if (max_attempts - attempt == 0) return error.FileBusy; |
| 4980 | | try parking_sleep.windowsRetrySleep((@as(u32, 1) << attempt) >> 1); |
| 4983 | try parking_sleep.sleep(.{ .duration = .{ |
| 4984 | .raw = .fromMilliseconds((@as(u32, 1) << attempt) >> 1), |
| 4985 | .clock = .awake, |
| 4986 | } }); |
| 4981 | 4987 | attempt += 1; |
| 4982 | 4988 | syscall = try .start(); |
| 4983 | 4989 | continue; |
| ... | ... | @@ -10823,9 +10829,6 @@ fn nowPosix(clock: Io.Clock) Io.Timestamp { |
| 10823 | 10829 | fn now(userdata: ?*anyopaque, clock: Io.Clock) Io.Timestamp { |
| 10824 | 10830 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 10825 | 10831 | _ = t; |
| 10826 | | return nowInner(clock); |
| 10827 | | } |
| 10828 | | fn nowInner(clock: Io.Clock) Io.Timestamp { |
| 10829 | 10832 | return switch (native_os) { |
| 10830 | 10833 | .windows => nowWindows(clock), |
| 10831 | 10834 | .wasi => nowWasi(clock), |
| ... | ... | @@ -15582,7 +15585,10 @@ fn getNulHandle(t: *Threaded) !windows.HANDLE { |
| 15582 | 15585 | // this other than retrying the creation after the OS finishes |
| 15583 | 15586 | // the deletion. |
| 15584 | 15587 | syscall.finish(); |
| 15585 | | try parking_sleep.windowsRetrySleep(1); |
| 15588 | try parking_sleep.sleep(.{ .duration = .{ |
| 15589 | .raw = .fromMilliseconds(1), |
| 15590 | .clock = .awake, |
| 15591 | } }); |
| 15586 | 15592 | syscall = try .start(); |
| 15587 | 15593 | continue; |
| 15588 | 15594 | }, |
| ... | ... | @@ -16955,13 +16961,9 @@ const parking_futex = struct { |
| 16955 | 16961 | /// |
| 16956 | 16962 | /// * Removing the `Waiter` from `Bucket.waiters` |
| 16957 | 16963 | /// * Decrementing `Bucket.num_waiters` |
| 16958 | | /// * Atomically setting `done` (after this, the `Waiter` may go out of scope at any time, |
| 16959 | | /// so must not be referenced again) |
| 16960 | | /// * Unparking the thread (last, so that the unparked thread definitely sees `done`) |
| 16964 | /// * Unparking the thread (*after* the above, so that the `Waiter` does not go out of scope |
| 16965 | /// while it is still in the `Bucket`). |
| 16961 | 16966 | thread_status: *std.atomic.Value(Thread.Status), |
| 16962 | | /// Initially `false`. Whoever updates `thread_status` to `.none`/`.canceling` will update |
| 16963 | | /// this to `true` once they are done with the `Waiter`, just before unparking `tid`. |
| 16964 | | done: std.atomic.Value(bool), |
| 16965 | 16967 | }; |
| 16966 | 16968 | |
| 16967 | 16969 | fn bucketForAddress(address: usize) *Bucket { |
| ... | ... | @@ -17000,7 +17002,6 @@ const parking_futex = struct { |
| 17000 | 17002 | .address = @intFromPtr(ptr), |
| 17001 | 17003 | .tid = self_tid, |
| 17002 | 17004 | .thread_status = undefined, // populated in critical section |
| 17003 | | .done = .init(false), |
| 17004 | 17005 | }; |
| 17005 | 17006 | |
| 17006 | 17007 | var status_buf: std.atomic.Value(Thread.Status) = undefined; |
| ... | ... | @@ -17058,44 +17059,41 @@ const parking_futex = struct { |
| 17058 | 17059 | bucket.waiters.append(&waiter.node); |
| 17059 | 17060 | } |
| 17060 | 17061 | |
| 17061 | | const deadline: ?Io.Clock.Timestamp = switch (timeout) { |
| 17062 | | .none => null, |
| 17063 | | .duration => |d| .{ |
| 17064 | | .raw = nowInner(d.clock).addDuration(d.raw), |
| 17065 | | .clock = d.clock, |
| 17066 | | }, |
| 17067 | | .deadline => |d| d, |
| 17068 | | }; |
| 17069 | | while (park(deadline, ptr)) { |
| 17070 | | if (waiter.done.load(.acquire)) return; // all done! |
| 17062 | if (park(timeout, ptr)) { |
| 17063 | // We were unparked by either `wake` or cancelation, so our current status is either |
| 17064 | // `.none` or `.canceling`. In either case, they've already removed `waiter` from |
| 17065 | // `bucket`, so we have nothing more to do! |
| 17071 | 17066 | } else |err| switch (err) { |
| 17072 | | error.Timeout => switch (waiter.thread_status.fetchAnd( |
| 17073 | | .{ .cancelation = @enumFromInt(0b110), .awaitable = .all_ones }, |
| 17074 | | .monotonic, |
| 17075 | | ).cancelation) { |
| 17076 | | .parked => { |
| 17077 | | // We saw a timeout and updated our own status from `.parked` to `.none`. It is |
| 17078 | | // our responsibility to remove `waiter` from `bucket`. |
| 17079 | | mutexLock(&bucket.mutex); |
| 17080 | | defer mutexUnlock(&bucket.mutex); |
| 17081 | | bucket.waiters.remove(&waiter.node); |
| 17082 | | assert(bucket.num_waiters.fetchSub(1, .monotonic) > 0); |
| 17083 | | }, |
| 17084 | | .none, .canceling => { |
| 17085 | | // Race condition: the timeout was reached, then `wake` or a cancelation tried |
| 17086 | | // to update our status. They won the race, so wait for them to do the cleanup. |
| 17087 | | // They'll tell us by setting `waiter.done` and unparking us. |
| 17088 | | while (!waiter.done.load(.acquire)) { |
| 17089 | | park(null, ptr) catch |e| switch (e) { |
| 17067 | error.Timeout => { |
| 17068 | // We're not out of the woods yet: an unpark could race with the timeout. |
| 17069 | const old_status = waiter.thread_status.fetchAnd( |
| 17070 | .{ .cancelation = @enumFromInt(0b110), .awaitable = .all_ones }, |
| 17071 | .monotonic, |
| 17072 | ); |
| 17073 | switch (old_status.cancelation) { |
| 17074 | .parked => { |
| 17075 | // No race. It is our responsibility to remove `waiter` from `bucket`. |
| 17076 | // New status is `.none`. |
| 17077 | bucket.mutex.lock(); |
| 17078 | defer bucket.mutex.unlock(); |
| 17079 | bucket.waiters.remove(&waiter.node); |
| 17080 | assert(bucket.num_waiters.fetchSub(1, .monotonic) > 0); |
| 17081 | }, |
| 17082 | .none, .canceling => { |
| 17083 | // Race condition: the timeout was reached, then `wake` or a canceler tried |
| 17084 | // to unpark us. Whoever did that will remove us from `bucket`. Wait for |
| 17085 | // that (and drop the unpark request in doing so). |
| 17086 | // New status is `.none` or `.canceling` respectively. |
| 17087 | park(.none, ptr) catch |e| switch (e) { |
| 17090 | 17088 | error.Timeout => unreachable, |
| 17091 | 17089 | }; |
| 17092 | | } |
| 17093 | | }, |
| 17094 | | .canceled => unreachable, |
| 17095 | | .blocked => unreachable, |
| 17096 | | .blocked_alertable => unreachable, |
| 17097 | | .blocked_alertable_canceling => unreachable, |
| 17098 | | .blocked_canceling => unreachable, |
| 17090 | }, |
| 17091 | .canceled => unreachable, |
| 17092 | .blocked => unreachable, |
| 17093 | .blocked_alertable => unreachable, |
| 17094 | .blocked_canceling => unreachable, |
| 17095 | .blocked_alertable_canceling => unreachable, |
| 17096 | } |
| 17099 | 17097 | }, |
| 17100 | 17098 | } |
| 17101 | 17099 | } |
| ... | ... | @@ -17144,6 +17142,9 @@ const parking_futex = struct { |
| 17144 | 17142 | waiter.node.next = waking_head; |
| 17145 | 17143 | waking_head = &waiter.node; |
| 17146 | 17144 | num_removed += 1; |
| 17145 | // Signal to `waiter` that they're about to be unparked, in case we're racing with their |
| 17146 | // timeout. See corresponding logic in `wake`. |
| 17147 | waiter.address = 0; |
| 17147 | 17148 | } |
| 17148 | 17149 | |
| 17149 | 17150 | _ = bucket.num_waiters.fetchSub(num_removed, .monotonic); |
| ... | ... | @@ -17158,8 +17159,6 @@ const parking_futex = struct { |
| 17158 | 17159 | const waiter: *Waiter = @fieldParentPtr("node", node); |
| 17159 | 17160 | unpark_buf[unpark_len] = waiter.tid; |
| 17160 | 17161 | unpark_len += 1; |
| 17161 | | waiter.done.store(true, .release); |
| 17162 | | // `waiter.*` is now potentially invalid so must not be referenced again. |
| 17163 | 17162 | if (unpark_len == unpark_buf.len) { |
| 17164 | 17163 | unpark(&unpark_buf, ptr); |
| 17165 | 17164 | unpark_len = 0; |
| ... | ... | @@ -17176,14 +17175,13 @@ const parking_futex = struct { |
| 17176 | 17175 | defer mutexUnlock(&bucket.mutex); |
| 17177 | 17176 | bucket.waiters.remove(&waiter.node); |
| 17178 | 17177 | assert(bucket.num_waiters.fetchSub(1, .monotonic) > 0); |
| 17179 | | waiter.done.store(true, .release); // potentially invalidates `waiter.*` |
| 17180 | 17178 | } |
| 17181 | 17179 | }; |
| 17182 | 17180 | const parking_sleep = struct { |
| 17183 | 17181 | comptime { |
| 17184 | 17182 | assert(use_parking_sleep); |
| 17185 | 17183 | } |
| 17186 | | fn sleep(deadline: ?Io.Clock.Timestamp) Io.Cancelable!void { |
| 17184 | fn sleep(timeout: Io.Timeout) Io.Cancelable!void { |
| 17187 | 17185 | const opt_thread = Thread.current; |
| 17188 | 17186 | cancelable: { |
| 17189 | 17187 | const thread = opt_thread orelse break :cancelable; |
| ... | ... | @@ -17192,90 +17190,87 @@ const parking_sleep = struct { |
| 17192 | 17190 | .unblocked => {}, |
| 17193 | 17191 | } |
| 17194 | 17192 | thread.futex_waiter = null; |
| 17195 | | const orig_status = thread.status.fetchOr( |
| 17196 | | .{ .cancelation = @enumFromInt(0b001), .awaitable = .null }, |
| 17197 | | .release, // release `thread.futex_waiter` |
| 17198 | | ); |
| 17199 | | switch (orig_status.cancelation) { |
| 17200 | | .none => {}, // status is now `.parked` |
| 17201 | | .canceling => return error.Canceled, // status is now `.canceled` |
| 17202 | | .canceled => break :cancelable, // status is still `.canceled` |
| 17203 | | .parked => unreachable, |
| 17204 | | .blocked => unreachable, |
| 17205 | | .blocked_alertable => unreachable, |
| 17206 | | .blocked_alertable_canceling => unreachable, |
| 17207 | | .blocked_canceling => unreachable, |
| 17208 | | } |
| 17209 | | while (park(deadline, null)) { |
| 17210 | | // Either a cancelation or a spurious unpark; let's see which! |
| 17211 | | switch (thread.status.load(.monotonic).cancelation) { |
| 17212 | | .parked => continue, // spurious unpark; keep sleeping |
| 17213 | | .canceling => { |
| 17214 | | // We got canceled; update our state and return. |
| 17215 | | thread.status.store( |
| 17216 | | .{ .cancelation = .canceled, .awaitable = orig_status.awaitable }, |
| 17217 | | .monotonic, |
| 17218 | | ); |
| 17219 | | return error.Canceled; |
| 17220 | | }, |
| 17221 | | .none => unreachable, |
| 17222 | | .canceled => unreachable, |
| 17193 | { |
| 17194 | const old_status = thread.status.fetchOr( |
| 17195 | .{ .cancelation = @enumFromInt(0b001), .awaitable = .null }, |
| 17196 | .release, // release `thread.futex_waiter` |
| 17197 | ); |
| 17198 | switch (old_status.cancelation) { |
| 17199 | .none => {}, // status is now `.parked` |
| 17200 | .canceling => return error.Canceled, // status is now `.canceled` |
| 17201 | .canceled => break :cancelable, // status is still `.canceled` |
| 17202 | .parked => unreachable, |
| 17223 | 17203 | .blocked => unreachable, |
| 17224 | 17204 | .blocked_alertable => unreachable, |
| 17225 | 17205 | .blocked_alertable_canceling => unreachable, |
| 17226 | 17206 | .blocked_canceling => unreachable, |
| 17227 | 17207 | } |
| 17228 | | } else |err| switch (err) { |
| 17229 | | error.Timeout => switch (thread.status.fetchAnd( |
| 17230 | | .{ .cancelation = @enumFromInt(0b110), .awaitable = .all_ones }, |
| 17208 | } |
| 17209 | if (park(timeout, null)) { |
| 17210 | // The only reason this could possibly happen is cancelation. |
| 17211 | const old_status = thread.status.load(.monotonic); |
| 17212 | assert(old_status.cancelation == .canceling); |
| 17213 | thread.status.store( |
| 17214 | .{ .cancelation = .canceled, .awaitable = old_status.awaitable }, |
| 17231 | 17215 | .monotonic, |
| 17232 | | ).cancelation) { |
| 17233 | | // We updated our own status from `.parked` to `.none`. |
| 17234 | | .parked => return, // new status is `.none` |
| 17235 | | .canceling => { |
| 17236 | | // Timeout raced with a cancelation. We don't need to do anything, but |
| 17237 | | // the next `park` on this thread will see a spurious unpark. |
| 17238 | | // Status is still `.canceling`. |
| 17239 | | return; |
| 17240 | | }, |
| 17241 | | .none => unreachable, |
| 17242 | | .canceled => unreachable, |
| 17243 | | .blocked => unreachable, |
| 17244 | | .blocked_alertable => unreachable, |
| 17245 | | .blocked_alertable_canceling => unreachable, |
| 17246 | | .blocked_canceling => unreachable, |
| 17216 | ); |
| 17217 | return error.Canceled; |
| 17218 | } else |err| switch (err) { |
| 17219 | error.Timeout => { |
| 17220 | // We're not out of the woods yet: an unpark could race with the timeout. |
| 17221 | const old_status = thread.status.fetchAnd( |
| 17222 | .{ .cancelation = @enumFromInt(0b110), .awaitable = .all_ones }, |
| 17223 | .monotonic, |
| 17224 | ); |
| 17225 | switch (old_status.cancelation) { |
| 17226 | .parked => return, // No race; new status is `.none` |
| 17227 | .canceling => { |
| 17228 | // Race condition: the timeout was reached, then someone tried to unpark |
| 17229 | // us for a cancelation. Whoever did that will have called `unpark`, so |
| 17230 | // drop that unpark request by waiting for it. |
| 17231 | // Status is still `.canceling`. |
| 17232 | park(.none, null) catch |e| switch (e) { |
| 17233 | error.Timeout => unreachable, |
| 17234 | }; |
| 17235 | return; |
| 17236 | }, |
| 17237 | .none => unreachable, |
| 17238 | .canceled => unreachable, |
| 17239 | .blocked => unreachable, |
| 17240 | .blocked_alertable => unreachable, |
| 17241 | .blocked_canceling => unreachable, |
| 17242 | .blocked_alertable_canceling => unreachable, |
| 17243 | } |
| 17247 | 17244 | }, |
| 17248 | 17245 | } |
| 17249 | 17246 | } |
| 17250 | | // Uncancelable sleep; this case is very simple. |
| 17251 | | while (park(deadline, null)) { |
| 17252 | | // Definitely spurious; nothing to do. |
| 17247 | // Uncancelable sleep; we expect not to be manually unparked. |
| 17248 | if (park(timeout, null)) { |
| 17249 | unreachable; // unexpected unpark |
| 17253 | 17250 | } else |err| switch (err) { |
| 17254 | 17251 | error.Timeout => return, |
| 17255 | 17252 | } |
| 17256 | 17253 | } |
| 17257 | | /// Sleep for approximately `ms` awake milliseconds in an attempt to work around Windows kernel bugs. |
| 17258 | | fn windowsRetrySleep(ms: u32) (Io.Cancelable || Io.UnexpectedError)!void { |
| 17259 | | const now_timestamp = nowWindows(.awake); // '.awake' is supported on Windows |
| 17260 | | const deadline = now_timestamp.addDuration(.fromMilliseconds(ms)); |
| 17261 | | try parking_sleep.sleep(.{ .raw = deadline, .clock = .awake }); |
| 17262 | | } |
| 17263 | 17254 | }; |
| 17264 | 17255 | |
| 17265 | | /// Spurious wakeups are possible. |
| 17266 | | /// |
| 17267 | 17256 | /// `addr_hint` has no semantic effect, but may allow the OS to optimize this operation. |
| 17268 | | fn park(opt_deadline: ?Io.Clock.Timestamp, addr_hint: ?*const anyopaque) error{Timeout}!void { |
| 17257 | fn park(timeout: Io.Timeout, addr_hint: ?*const anyopaque) error{Timeout}!void { |
| 17269 | 17258 | comptime assert(use_parking_futex or use_parking_sleep); |
| 17270 | 17259 | switch (native_os) { |
| 17271 | 17260 | .windows => { |
| 17272 | 17261 | var timeout_buf: windows.LARGE_INTEGER = undefined; |
| 17273 | | const raw_timeout: ?*windows.LARGE_INTEGER = if (opt_deadline) |deadline| timeout: { |
| 17274 | | const now_timestamp = nowWindows(deadline.clock); |
| 17275 | | const nanoseconds = now_timestamp.durationTo(deadline.raw).nanoseconds; |
| 17276 | | timeout_buf = @intCast(@divTrunc(-nanoseconds, 100)); |
| 17277 | | break :timeout &timeout_buf; |
| 17278 | | } else null; |
| 17262 | const raw_timeout: ?*windows.LARGE_INTEGER = timeout: switch (timeout) { |
| 17263 | .none => null, |
| 17264 | .deadline => |timestamp| continue :timeout .{ .duration = .{ |
| 17265 | .clock = timestamp.clock, |
| 17266 | .raw = (nowWindows(timestamp.clock) catch unreachable).durationTo(timestamp.raw), |
| 17267 | } }, |
| 17268 | .duration => |duration| { |
| 17269 | _ = duration.clock; // Windows only supports monotonic |
| 17270 | timeout_buf = @intCast(@divTrunc(-duration.raw.nanoseconds, 100)); |
| 17271 | break :timeout &timeout_buf; |
| 17272 | }, |
| 17273 | }; |
| 17279 | 17274 | // `RtlWaitOnAddress` passes the futex address in as the first argument to this call, |
| 17280 | 17275 | // but it's unclear what that actually does, especially since `NtAlertThreadByThreadId` |
| 17281 | 17276 | // does *not* accept the address so the kernel can't really be using it as a hint. An |
| ... | ... | @@ -17297,13 +17292,20 @@ fn park(opt_deadline: ?Io.Clock.Timestamp, addr_hint: ?*const anyopaque) error{T |
| 17297 | 17292 | }, |
| 17298 | 17293 | .netbsd => { |
| 17299 | 17294 | var ts_buf: posix.timespec = undefined; |
| 17300 | | const ts: ?*posix.timespec, const clock_real: bool = if (opt_deadline) |deadline| timeout: { |
| 17301 | | ts_buf = timestampToPosix(deadline.raw.nanoseconds); |
| 17302 | | break :timeout .{ &ts_buf, deadline.clock == .real }; |
| 17303 | | } else .{ null, true }; |
| 17295 | const ts: ?*posix.timespec, const abstime: bool, const clock_real: bool = switch (timeout) { |
| 17296 | .none => .{ null, false, false }, |
| 17297 | .deadline => |timestamp| timeout: { |
| 17298 | ts_buf = timestampToPosix(timestamp.raw.nanoseconds); |
| 17299 | break :timeout .{ &ts_buf, true, timestamp.clock == .real }; |
| 17300 | }, |
| 17301 | .duration => |duration| timeout: { |
| 17302 | ts_buf = timestampToPosix(duration.raw.nanoseconds); |
| 17303 | break :timeout .{ &ts_buf, false, duration.clock == .real }; |
| 17304 | }, |
| 17305 | }; |
| 17304 | 17306 | switch (posix.errno(std.c._lwp_park( |
| 17305 | 17307 | if (clock_real) .REALTIME else .MONOTONIC, |
| 17306 | | .{ .ABSTIME = true }, |
| 17308 | .{ .ABSTIME = abstime }, |
| 17307 | 17309 | ts, |
| 17308 | 17310 | 0, |
| 17309 | 17311 | addr_hint, |