| ... | ... | @@ -29,8 +29,8 @@ const ws2_32 = std.os.windows.ws2_32; |
| 29 | 29 | /// * scanning environment variables on some targets |
| 30 | 30 | /// * memory-mapping when mmap or equivalent is not available |
| 31 | 31 | allocator: Allocator, |
| 32 | | mutex: Mutex = .init, |
| 33 | | cond: Condition = .init, |
| 32 | mutex: Io.Mutex = .init, |
| 33 | cond: Io.Condition = .init, |
| 34 | 34 | run_queue: std.SinglyLinkedList = .{}, |
| 35 | 35 | join_requested: bool = false, |
| 36 | 36 | stack_size: usize, |
| ... | ... | @@ -1505,8 +1505,8 @@ var global_single_threaded_instance: Threaded = .init_single_threaded; |
| 1505 | 1505 | pub const global_single_threaded: *Threaded = &global_single_threaded_instance; |
| 1506 | 1506 | |
| 1507 | 1507 | pub fn setAsyncLimit(t: *Threaded, new_limit: Io.Limit) void { |
| 1508 | | mutexLockInternal(&t.mutex); |
| 1509 | | defer mutexUnlockInternal(&t.mutex); |
| 1508 | mutexLock(&t.mutex); |
| 1509 | defer mutexUnlock(&t.mutex); |
| 1510 | 1510 | t.async_limit = new_limit; |
| 1511 | 1511 | } |
| 1512 | 1512 | |
| ... | ... | @@ -1527,8 +1527,8 @@ pub fn deinit(t: *Threaded) void { |
| 1527 | 1527 | fn join(t: *Threaded) void { |
| 1528 | 1528 | if (builtin.single_threaded) return; |
| 1529 | 1529 | { |
| 1530 | | mutexLockInternal(&t.mutex); |
| 1531 | | defer mutexUnlockInternal(&t.mutex); |
| 1530 | mutexLock(&t.mutex); |
| 1531 | defer mutexUnlock(&t.mutex); |
| 1532 | 1532 | t.join_requested = true; |
| 1533 | 1533 | } |
| 1534 | 1534 | condBroadcast(&t.cond); |
| ... | ... | @@ -1593,16 +1593,16 @@ fn worker(t: *Threaded) void { |
| 1593 | 1593 | |
| 1594 | 1594 | defer t.wait_group.finish(); |
| 1595 | 1595 | |
| 1596 | | mutexLockInternal(&t.mutex); |
| 1597 | | defer mutexUnlockInternal(&t.mutex); |
| 1596 | mutexLock(&t.mutex); |
| 1597 | defer mutexUnlock(&t.mutex); |
| 1598 | 1598 | |
| 1599 | 1599 | while (true) { |
| 1600 | 1600 | while (t.run_queue.popFirst()) |runnable_node| { |
| 1601 | | mutexUnlockInternal(&t.mutex); |
| 1601 | mutexUnlock(&t.mutex); |
| 1602 | 1602 | thread.cancel_protection = .unblocked; |
| 1603 | 1603 | const runnable: *Runnable = @fieldParentPtr("node", runnable_node); |
| 1604 | 1604 | runnable.startFn(runnable, &thread, t); |
| 1605 | | mutexLockInternal(&t.mutex); |
| 1605 | mutexLock(&t.mutex); |
| 1606 | 1606 | t.busy_count -= 1; |
| 1607 | 1607 | } |
| 1608 | 1608 | if (t.join_requested) break; |
| ... | ... | @@ -2025,12 +2025,12 @@ fn async( |
| 2025 | 2025 | }, |
| 2026 | 2026 | }; |
| 2027 | 2027 | |
| 2028 | | mutexLockInternal(&t.mutex); |
| 2028 | mutexLock(&t.mutex); |
| 2029 | 2029 | |
| 2030 | 2030 | const busy_count = t.busy_count; |
| 2031 | 2031 | |
| 2032 | 2032 | if (busy_count >= @intFromEnum(t.async_limit)) { |
| 2033 | | mutexUnlockInternal(&t.mutex); |
| 2033 | mutexUnlock(&t.mutex); |
| 2034 | 2034 | future.destroy(gpa); |
| 2035 | 2035 | start(context.ptr, result.ptr); |
| 2036 | 2036 | return null; |
| ... | ... | @@ -2044,7 +2044,7 @@ fn async( |
| 2044 | 2044 | const thread = std.Thread.spawn(.{ .stack_size = t.stack_size }, worker, .{t}) catch { |
| 2045 | 2045 | t.wait_group.finish(); |
| 2046 | 2046 | t.busy_count = busy_count; |
| 2047 | | mutexUnlockInternal(&t.mutex); |
| 2047 | mutexUnlock(&t.mutex); |
| 2048 | 2048 | future.destroy(gpa); |
| 2049 | 2049 | start(context.ptr, result.ptr); |
| 2050 | 2050 | return null; |
| ... | ... | @@ -2054,7 +2054,7 @@ fn async( |
| 2054 | 2054 | |
| 2055 | 2055 | t.run_queue.prepend(&future.runnable.node); |
| 2056 | 2056 | |
| 2057 | | mutexUnlockInternal(&t.mutex); |
| 2057 | mutexUnlock(&t.mutex); |
| 2058 | 2058 | condSignal(&t.cond); |
| 2059 | 2059 | return @ptrCast(future); |
| 2060 | 2060 | } |
| ... | ... | @@ -2077,8 +2077,8 @@ fn concurrent( |
| 2077 | 2077 | }; |
| 2078 | 2078 | errdefer future.destroy(gpa); |
| 2079 | 2079 | |
| 2080 | | mutexLockInternal(&t.mutex); |
| 2081 | | defer mutexUnlockInternal(&t.mutex); |
| 2080 | mutexLock(&t.mutex); |
| 2081 | defer mutexUnlock(&t.mutex); |
| 2082 | 2082 | |
| 2083 | 2083 | const busy_count = t.busy_count; |
| 2084 | 2084 | |
| ... | ... | @@ -2122,12 +2122,12 @@ fn groupAsync( |
| 2122 | 2122 | error.OutOfMemory => return groupAsyncEager(start, context.ptr), |
| 2123 | 2123 | }; |
| 2124 | 2124 | |
| 2125 | | mutexLockInternal(&t.mutex); |
| 2125 | mutexLock(&t.mutex); |
| 2126 | 2126 | |
| 2127 | 2127 | const busy_count = t.busy_count; |
| 2128 | 2128 | |
| 2129 | 2129 | if (busy_count >= @intFromEnum(t.async_limit)) { |
| 2130 | | mutexUnlockInternal(&t.mutex); |
| 2130 | mutexUnlock(&t.mutex); |
| 2131 | 2131 | task.destroy(gpa); |
| 2132 | 2132 | return groupAsyncEager(start, context.ptr); |
| 2133 | 2133 | } |
| ... | ... | @@ -2140,7 +2140,7 @@ fn groupAsync( |
| 2140 | 2140 | const thread = std.Thread.spawn(.{ .stack_size = t.stack_size }, worker, .{t}) catch { |
| 2141 | 2141 | t.wait_group.finish(); |
| 2142 | 2142 | t.busy_count = busy_count; |
| 2143 | | mutexUnlockInternal(&t.mutex); |
| 2143 | mutexUnlock(&t.mutex); |
| 2144 | 2144 | task.destroy(gpa); |
| 2145 | 2145 | return groupAsyncEager(start, context.ptr); |
| 2146 | 2146 | }; |
| ... | ... | @@ -2157,7 +2157,7 @@ fn groupAsync( |
| 2157 | 2157 | }, .monotonic); |
| 2158 | 2158 | t.run_queue.prepend(&task.runnable.node); |
| 2159 | 2159 | |
| 2160 | | mutexUnlockInternal(&t.mutex); |
| 2160 | mutexUnlock(&t.mutex); |
| 2161 | 2161 | condSignal(&t.cond); |
| 2162 | 2162 | } |
| 2163 | 2163 | fn groupAsyncEager( |
| ... | ... | @@ -2222,8 +2222,8 @@ fn groupConcurrent( |
| 2222 | 2222 | }; |
| 2223 | 2223 | errdefer task.destroy(gpa); |
| 2224 | 2224 | |
| 2225 | | mutexLockInternal(&t.mutex); |
| 2226 | | defer mutexUnlockInternal(&t.mutex); |
| 2225 | mutexLock(&t.mutex); |
| 2226 | defer mutexUnlock(&t.mutex); |
| 2227 | 2227 | |
| 2228 | 2228 | const busy_count = t.busy_count; |
| 2229 | 2229 | |
| ... | ... | @@ -2662,7 +2662,7 @@ fn batchAwaitConcurrent(userdata: ?*anyopaque, b: *Io.Batch, timeout: Io.Timeout |
| 2662 | 2662 | while (b.pending.head != .none and b.completions.head == .none) { |
| 2663 | 2663 | var delay_interval: windows.LARGE_INTEGER = interval: { |
| 2664 | 2664 | const d = deadline orelse break :interval std.math.minInt(windows.LARGE_INTEGER); |
| 2665 | | break :interval t.deadlineToWindowsInterval(d); |
| 2665 | break :interval timeoutToWindowsInterval(.{ .deadline = d }).?; |
| 2666 | 2666 | }; |
| 2667 | 2667 | const alertable_syscall = try AlertableSyscall.start(); |
| 2668 | 2668 | const delay_rc = windows.ntdll.NtDelayExecution(windows.TRUE, &delay_interval); |
| ... | ... | @@ -3847,8 +3847,8 @@ fn fileStatWindows(userdata: ?*anyopaque, file: File) File.StatError!File.Stat { |
| 3847 | 3847 | |
| 3848 | 3848 | fn systemBasicInformation(t: *Threaded) ?*const windows.SYSTEM_BASIC_INFORMATION { |
| 3849 | 3849 | if (!t.system_basic_information.initialized.load(.acquire)) { |
| 3850 | | mutexLockInternal(&t.mutex); |
| 3851 | | defer mutexUnlockInternal(&t.mutex); |
| 3850 | mutexLock(&t.mutex); |
| 3851 | defer mutexUnlock(&t.mutex); |
| 3852 | 3852 | |
| 3853 | 3853 | switch (windows.ntdll.NtQuerySystemInformation( |
| 3854 | 3854 | .SystemBasicInformation, |
| ... | ... | @@ -4339,7 +4339,10 @@ fn dirCreateFileWindows( |
| 4339 | 4339 | // kernel bug with retry attempts. |
| 4340 | 4340 | syscall.finish(); |
| 4341 | 4341 | if (max_attempts - attempt == 0) return error.FileBusy; |
| 4342 | | try parking_sleep.windowsRetrySleep((@as(u32, 1) << attempt) >> 1); |
| 4342 | try parking_sleep.sleep(.{ .duration = .{ |
| 4343 | .raw = .fromMilliseconds((@as(u32, 1) << attempt) >> 1), |
| 4344 | .clock = .awake, |
| 4345 | } }); |
| 4343 | 4346 | attempt += 1; |
| 4344 | 4347 | syscall = try .start(); |
| 4345 | 4348 | continue; |
| ... | ... | @@ -4352,7 +4355,10 @@ fn dirCreateFileWindows( |
| 4352 | 4355 | // fixed by sleeping and retrying until the error goes away. |
| 4353 | 4356 | syscall.finish(); |
| 4354 | 4357 | if (max_attempts - attempt == 0) return error.FileBusy; |
| 4355 | | try parking_sleep.windowsRetrySleep((@as(u32, 1) << attempt) >> 1); |
| 4358 | try parking_sleep.sleep(.{ .duration = .{ |
| 4359 | .raw = .fromMilliseconds((@as(u32, 1) << attempt) >> 1), |
| 4360 | .clock = .awake, |
| 4361 | } }); |
| 4356 | 4362 | attempt += 1; |
| 4357 | 4363 | syscall = try .start(); |
| 4358 | 4364 | continue; |
| ... | ... | @@ -4955,7 +4961,10 @@ pub fn dirOpenFileWtf16( |
| 4955 | 4961 | // kernel bug with retry attempts. |
| 4956 | 4962 | syscall.finish(); |
| 4957 | 4963 | if (max_attempts - attempt == 0) return error.FileBusy; |
| 4958 | | try parking_sleep.windowsRetrySleep((@as(u32, 1) << attempt) >> 1); |
| 4964 | try parking_sleep.sleep(.{ .duration = .{ |
| 4965 | .raw = .fromMilliseconds((@as(u32, 1) << attempt) >> 1), |
| 4966 | .clock = .awake, |
| 4967 | } }); |
| 4959 | 4968 | attempt += 1; |
| 4960 | 4969 | syscall = try .start(); |
| 4961 | 4970 | continue; |
| ... | ... | @@ -4977,7 +4986,10 @@ pub fn dirOpenFileWtf16( |
| 4977 | 4986 | // fixed by sleeping and retrying until the error goes away. |
| 4978 | 4987 | syscall.finish(); |
| 4979 | 4988 | if (max_attempts - attempt == 0) return error.FileBusy; |
| 4980 | | try parking_sleep.windowsRetrySleep((@as(u32, 1) << attempt) >> 1); |
| 4989 | try parking_sleep.sleep(.{ .duration = .{ |
| 4990 | .raw = .fromMilliseconds((@as(u32, 1) << attempt) >> 1), |
| 4991 | .clock = .awake, |
| 4992 | } }); |
| 4981 | 4993 | attempt += 1; |
| 4982 | 4994 | syscall = try .start(); |
| 4983 | 4995 | continue; |
| ... | ... | @@ -7376,7 +7388,10 @@ fn dirReadLinkWindows(dir: Dir, sub_path: []const u8, buffer: []u8) Dir.ReadLink |
| 7376 | 7388 | // kernel bug with retry attempts. |
| 7377 | 7389 | syscall.finish(); |
| 7378 | 7390 | if (max_attempts - attempt == 0) return error.FileBusy; |
| 7379 | | try parking_sleep.windowsRetrySleep((@as(u32, 1) << attempt) >> 1); |
| 7391 | try parking_sleep.sleep(.{ .duration = .{ |
| 7392 | .raw = .fromMilliseconds((@as(u32, 1) << attempt) >> 1), |
| 7393 | .clock = .awake, |
| 7394 | } }); |
| 7380 | 7395 | attempt += 1; |
| 7381 | 7396 | syscall = try .start(); |
| 7382 | 7397 | continue; |
| ... | ... | @@ -7389,7 +7404,10 @@ fn dirReadLinkWindows(dir: Dir, sub_path: []const u8, buffer: []u8) Dir.ReadLink |
| 7389 | 7404 | // fixed by sleeping and retrying until the error goes away. |
| 7390 | 7405 | syscall.finish(); |
| 7391 | 7406 | if (max_attempts - attempt == 0) return error.FileBusy; |
| 7392 | | try parking_sleep.windowsRetrySleep((@as(u32, 1) << attempt) >> 1); |
| 7407 | try parking_sleep.sleep(.{ .duration = .{ |
| 7408 | .raw = .fromMilliseconds((@as(u32, 1) << attempt) >> 1), |
| 7409 | .clock = .awake, |
| 7410 | } }); |
| 7393 | 7411 | attempt += 1; |
| 7394 | 7412 | syscall = try .start(); |
| 7395 | 7413 | continue; |
| ... | ... | @@ -10823,9 +10841,6 @@ fn nowPosix(clock: Io.Clock) Io.Timestamp { |
| 10823 | 10841 | fn now(userdata: ?*anyopaque, clock: Io.Clock) Io.Timestamp { |
| 10824 | 10842 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 10825 | 10843 | _ = t; |
| 10826 | | return nowInner(clock); |
| 10827 | | } |
| 10828 | | fn nowInner(clock: Io.Clock) Io.Timestamp { |
| 10829 | 10844 | return switch (native_os) { |
| 10830 | 10845 | .windows => nowWindows(clock), |
| 10831 | 10846 | .wasi => nowWasi(clock), |
| ... | ... | @@ -10955,7 +10970,7 @@ fn nowWasi(clock: Io.Clock) Io.Timestamp { |
| 10955 | 10970 | fn sleep(userdata: ?*anyopaque, timeout: Io.Timeout) Io.Cancelable!void { |
| 10956 | 10971 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 10957 | 10972 | if (timeout == .none) return; |
| 10958 | | if (use_parking_sleep) return parking_sleep.sleep(timeout.toTimestamp(ioBasic(t))); |
| 10973 | if (use_parking_sleep) return parking_sleep.sleep(timeout); |
| 10959 | 10974 | if (native_os == .wasi) return sleepWasi(t, timeout); |
| 10960 | 10975 | if (@TypeOf(posix.system.clock_nanosleep) != void) return sleepPosix(timeout); |
| 10961 | 10976 | return sleepNanosleep(t, timeout); |
| ... | ... | @@ -14361,10 +14376,9 @@ const Wsa = struct { |
| 14361 | 14376 | }; |
| 14362 | 14377 | |
| 14363 | 14378 | fn initializeWsa(t: *Threaded) error{ NetworkDown, Canceled }!void { |
| 14364 | | const t_io = io(t); |
| 14365 | 14379 | const wsa = &t.wsa; |
| 14366 | | try wsa.mutex.lock(t_io); |
| 14367 | | defer wsa.mutex.unlock(t_io); |
| 14380 | mutexLock(&wsa.mutex); |
| 14381 | defer mutexUnlock(&wsa.mutex); |
| 14368 | 14382 | switch (wsa.status) { |
| 14369 | 14383 | .uninitialized => { |
| 14370 | 14384 | var wsa_data: ws2_32.WSADATA = undefined; |
| ... | ... | @@ -14435,8 +14449,8 @@ const WindowsEnvironStrings = struct { |
| 14435 | 14449 | }; |
| 14436 | 14450 | |
| 14437 | 14451 | fn scanEnviron(t: *Threaded) void { |
| 14438 | | mutexLockInternal(&t.mutex); |
| 14439 | | defer mutexUnlockInternal(&t.mutex); |
| 14452 | mutexLock(&t.mutex); |
| 14453 | defer mutexUnlock(&t.mutex); |
| 14440 | 14454 | |
| 14441 | 14455 | if (t.environ.initialized) return; |
| 14442 | 14456 | t.environ.initialized = true; |
| ... | ... | @@ -14791,8 +14805,8 @@ fn spawnPosix(t: *Threaded, options: process.SpawnOptions) process.SpawnError!Sp |
| 14791 | 14805 | |
| 14792 | 14806 | fn getDevNullFd(t: *Threaded) !posix.fd_t { |
| 14793 | 14807 | { |
| 14794 | | mutexLockInternal(&t.mutex); |
| 14795 | | defer mutexUnlockInternal(&t.mutex); |
| 14808 | mutexLock(&t.mutex); |
| 14809 | defer mutexUnlock(&t.mutex); |
| 14796 | 14810 | if (t.null_file.fd != -1) return t.null_file.fd; |
| 14797 | 14811 | } |
| 14798 | 14812 | const mode: u32 = 0; |
| ... | ... | @@ -14803,8 +14817,8 @@ fn getDevNullFd(t: *Threaded) !posix.fd_t { |
| 14803 | 14817 | .SUCCESS => { |
| 14804 | 14818 | syscall.finish(); |
| 14805 | 14819 | const fresh_fd: posix.fd_t = @intCast(rc); |
| 14806 | | mutexLockInternal(&t.mutex); // Another thread might have won the race. |
| 14807 | | defer mutexUnlockInternal(&t.mutex); |
| 14820 | mutexLock(&t.mutex); // Another thread might have won the race. |
| 14821 | defer mutexUnlock(&t.mutex); |
| 14808 | 14822 | if (t.null_file.fd != -1) { |
| 14809 | 14823 | posix.close(fresh_fd); |
| 14810 | 14824 | return t.null_file.fd; |
| ... | ... | @@ -15464,8 +15478,8 @@ fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) pro |
| 15464 | 15478 | |
| 15465 | 15479 | fn getCngHandle(t: *Threaded) Io.RandomSecureError!windows.HANDLE { |
| 15466 | 15480 | { |
| 15467 | | mutexLockInternal(&t.mutex); |
| 15468 | | defer mutexUnlockInternal(&t.mutex); |
| 15481 | mutexLock(&t.mutex); |
| 15482 | defer mutexUnlock(&t.mutex); |
| 15469 | 15483 | if (t.random_file.handle) |handle| return handle; |
| 15470 | 15484 | } |
| 15471 | 15485 | |
| ... | ... | @@ -15499,8 +15513,8 @@ fn getCngHandle(t: *Threaded) Io.RandomSecureError!windows.HANDLE { |
| 15499 | 15513 | )) { |
| 15500 | 15514 | .SUCCESS => { |
| 15501 | 15515 | syscall.finish(); |
| 15502 | | mutexLockInternal(&t.mutex); // Another thread might have won the race. |
| 15503 | | defer mutexUnlockInternal(&t.mutex); |
| 15516 | mutexLock(&t.mutex); // Another thread might have won the race. |
| 15517 | defer mutexUnlock(&t.mutex); |
| 15504 | 15518 | if (t.random_file.handle) |prev_handle| { |
| 15505 | 15519 | windows.CloseHandle(fresh_handle); |
| 15506 | 15520 | return prev_handle; |
| ... | ... | @@ -15520,8 +15534,8 @@ fn getCngHandle(t: *Threaded) Io.RandomSecureError!windows.HANDLE { |
| 15520 | 15534 | |
| 15521 | 15535 | fn getNulHandle(t: *Threaded) !windows.HANDLE { |
| 15522 | 15536 | { |
| 15523 | | mutexLockInternal(&t.mutex); |
| 15524 | | defer mutexUnlockInternal(&t.mutex); |
| 15537 | mutexLock(&t.mutex); |
| 15538 | defer mutexUnlock(&t.mutex); |
| 15525 | 15539 | if (t.null_file.handle) |handle| return handle; |
| 15526 | 15540 | } |
| 15527 | 15541 | |
| ... | ... | @@ -15567,8 +15581,8 @@ fn getNulHandle(t: *Threaded) !windows.HANDLE { |
| 15567 | 15581 | )) { |
| 15568 | 15582 | .SUCCESS => { |
| 15569 | 15583 | syscall.finish(); |
| 15570 | | mutexLockInternal(&t.mutex); // Another thread might have won the race. |
| 15571 | | defer mutexUnlockInternal(&t.mutex); |
| 15584 | mutexLock(&t.mutex); // Another thread might have won the race. |
| 15585 | defer mutexUnlock(&t.mutex); |
| 15572 | 15586 | if (t.null_file.handle) |prev_handle| { |
| 15573 | 15587 | windows.CloseHandle(fresh_handle); |
| 15574 | 15588 | return prev_handle; |
| ... | ... | @@ -15585,7 +15599,10 @@ fn getNulHandle(t: *Threaded) !windows.HANDLE { |
| 15585 | 15599 | // this other than retrying the creation after the OS finishes |
| 15586 | 15600 | // the deletion. |
| 15587 | 15601 | syscall.finish(); |
| 15588 | | try parking_sleep.windowsRetrySleep(1); |
| 15602 | try parking_sleep.sleep(.{ .duration = .{ |
| 15603 | .raw = .fromMilliseconds(1), |
| 15604 | .clock = .awake, |
| 15605 | } }); |
| 15589 | 15606 | syscall = try .start(); |
| 15590 | 15607 | continue; |
| 15591 | 15608 | }, |
| ... | ... | @@ -16613,15 +16630,15 @@ fn random(userdata: ?*anyopaque, buffer: []u8) void { |
| 16613 | 16630 | } |
| 16614 | 16631 | |
| 16615 | 16632 | fn randomMainThread(t: *Threaded, buffer: []u8) void { |
| 16616 | | mutexLockInternal(&t.mutex); |
| 16617 | | defer mutexUnlockInternal(&t.mutex); |
| 16633 | mutexLock(&t.mutex); |
| 16634 | defer mutexUnlock(&t.mutex); |
| 16618 | 16635 | |
| 16619 | 16636 | if (!t.csprng.isInitialized()) { |
| 16620 | 16637 | @branchHint(.unlikely); |
| 16621 | 16638 | var seed: [Csprng.seed_len]u8 = undefined; |
| 16622 | 16639 | { |
| 16623 | | mutexUnlockInternal(&t.mutex); |
| 16624 | | defer mutexLockInternal(&t.mutex); |
| 16640 | mutexUnlock(&t.mutex); |
| 16641 | defer mutexLock(&t.mutex); |
| 16625 | 16642 | |
| 16626 | 16643 | const prev = swapCancelProtection(t, .blocked); |
| 16627 | 16644 | defer _ = swapCancelProtection(t, prev); |
| ... | ... | @@ -16806,8 +16823,8 @@ fn randomSecure(userdata: ?*anyopaque, buffer: []u8) Io.RandomSecureError!void { |
| 16806 | 16823 | |
| 16807 | 16824 | fn getRandomFd(t: *Threaded) Io.RandomSecureError!posix.fd_t { |
| 16808 | 16825 | { |
| 16809 | | mutexLockInternal(&t.mutex); |
| 16810 | | defer mutexUnlockInternal(&t.mutex); |
| 16826 | mutexLock(&t.mutex); |
| 16827 | defer mutexUnlock(&t.mutex); |
| 16811 | 16828 | |
| 16812 | 16829 | if (t.random_file.fd == -2) return error.EntropyUnavailable; |
| 16813 | 16830 | if (t.random_file.fd != -1) return t.random_file.fd; |
| ... | ... | @@ -16847,8 +16864,8 @@ fn getRandomFd(t: *Threaded) Io.RandomSecureError!posix.fd_t { |
| 16847 | 16864 | .SUCCESS => { |
| 16848 | 16865 | syscall.finish(); |
| 16849 | 16866 | if (!statx.mask.TYPE) return error.EntropyUnavailable; |
| 16850 | | mutexLockInternal(&t.mutex); // Another thread might have won the race. |
| 16851 | | defer mutexUnlockInternal(&t.mutex); |
| 16867 | mutexLock(&t.mutex); // Another thread might have won the race. |
| 16868 | defer mutexUnlock(&t.mutex); |
| 16852 | 16869 | if (t.random_file.fd >= 0) { |
| 16853 | 16870 | posix.close(fd); |
| 16854 | 16871 | return t.random_file.fd; |
| ... | ... | @@ -16875,8 +16892,8 @@ fn getRandomFd(t: *Threaded) Io.RandomSecureError!posix.fd_t { |
| 16875 | 16892 | switch (posix.errno(fstat_sym(fd, &stat))) { |
| 16876 | 16893 | .SUCCESS => { |
| 16877 | 16894 | syscall.finish(); |
| 16878 | | mutexLockInternal(&t.mutex); // Another thread might have won the race. |
| 16879 | | defer mutexUnlockInternal(&t.mutex); |
| 16895 | mutexLock(&t.mutex); // Another thread might have won the race. |
| 16896 | defer mutexUnlock(&t.mutex); |
| 16880 | 16897 | if (t.random_file.fd >= 0) { |
| 16881 | 16898 | posix.close(fd); |
| 16882 | 16899 | return t.random_file.fd; |
| ... | ... | @@ -16940,7 +16957,7 @@ const parking_futex = struct { |
| 16940 | 16957 | /// avoid a race. |
| 16941 | 16958 | num_waiters: std.atomic.Value(u32), |
| 16942 | 16959 | /// Protects `waiters`. |
| 16943 | | mutex: Mutex, |
| 16960 | mutex: ParkingMutex, |
| 16944 | 16961 | waiters: std.DoublyLinkedList, |
| 16945 | 16962 | |
| 16946 | 16963 | /// Prevent false sharing between buckets. |
| ... | ... | @@ -16958,13 +16975,9 @@ const parking_futex = struct { |
| 16958 | 16975 | /// |
| 16959 | 16976 | /// * Removing the `Waiter` from `Bucket.waiters` |
| 16960 | 16977 | /// * Decrementing `Bucket.num_waiters` |
| 16961 | | /// * Atomically setting `done` (after this, the `Waiter` may go out of scope at any time, |
| 16962 | | /// so must not be referenced again) |
| 16963 | | /// * Unparking the thread (last, so that the unparked thread definitely sees `done`) |
| 16978 | /// * Unparking the thread (*after* the above, so that the `Waiter` does not go out of scope |
| 16979 | /// while it is still in the `Bucket`). |
| 16964 | 16980 | thread_status: *std.atomic.Value(Thread.Status), |
| 16965 | | /// Initially `false`. Whoever updates `thread_status` to `.none`/`.canceling` will update |
| 16966 | | /// this to `true` once they are done with the `Waiter`, just before unparking `tid`. |
| 16967 | | done: std.atomic.Value(bool), |
| 16968 | 16981 | }; |
| 16969 | 16982 | |
| 16970 | 16983 | fn bucketForAddress(address: usize) *Bucket { |
| ... | ... | @@ -17003,14 +17016,13 @@ const parking_futex = struct { |
| 17003 | 17016 | .address = @intFromPtr(ptr), |
| 17004 | 17017 | .tid = self_tid, |
| 17005 | 17018 | .thread_status = undefined, // populated in critical section |
| 17006 | | .done = .init(false), |
| 17007 | 17019 | }; |
| 17008 | 17020 | |
| 17009 | 17021 | var status_buf: std.atomic.Value(Thread.Status) = undefined; |
| 17010 | 17022 | |
| 17011 | 17023 | { |
| 17012 | | mutexLockInternal(&bucket.mutex); |
| 17013 | | defer mutexUnlockInternal(&bucket.mutex); |
| 17024 | bucket.mutex.lock(); |
| 17025 | defer bucket.mutex.unlock(); |
| 17014 | 17026 | |
| 17015 | 17027 | _ = bucket.num_waiters.fetchAdd(1, .acquire); |
| 17016 | 17028 | |
| ... | ... | @@ -17061,44 +17073,41 @@ const parking_futex = struct { |
| 17061 | 17073 | bucket.waiters.append(&waiter.node); |
| 17062 | 17074 | } |
| 17063 | 17075 | |
| 17064 | | const deadline: ?Io.Clock.Timestamp = switch (timeout) { |
| 17065 | | .none => null, |
| 17066 | | .duration => |d| .{ |
| 17067 | | .raw = nowInner(d.clock).addDuration(d.raw), |
| 17068 | | .clock = d.clock, |
| 17069 | | }, |
| 17070 | | .deadline => |d| d, |
| 17071 | | }; |
| 17072 | | while (park(deadline, ptr)) { |
| 17073 | | if (waiter.done.load(.acquire)) return; // all done! |
| 17076 | if (park(timeout, ptr, waiter.thread_status)) { |
| 17077 | // We were unparked by either `wake` or cancelation, so our current status is either |
| 17078 | // `.none` or `.canceling`. In either case, they've already removed `waiter` from |
| 17079 | // `bucket`, so we have nothing more to do! |
| 17074 | 17080 | } else |err| switch (err) { |
| 17075 | | error.Timeout => switch (waiter.thread_status.fetchAnd( |
| 17076 | | .{ .cancelation = @enumFromInt(0b110), .awaitable = .all_ones }, |
| 17077 | | .monotonic, |
| 17078 | | ).cancelation) { |
| 17079 | | .parked => { |
| 17080 | | // We saw a timeout and updated our own status from `.parked` to `.none`. It is |
| 17081 | | // our responsibility to remove `waiter` from `bucket`. |
| 17082 | | mutexLockInternal(&bucket.mutex); |
| 17083 | | defer mutexUnlockInternal(&bucket.mutex); |
| 17084 | | bucket.waiters.remove(&waiter.node); |
| 17085 | | assert(bucket.num_waiters.fetchSub(1, .monotonic) > 0); |
| 17086 | | }, |
| 17087 | | .none, .canceling => { |
| 17088 | | // Race condition: the timeout was reached, then `wake` or a cancelation tried |
| 17089 | | // to update our status. They won the race, so wait for them to do the cleanup. |
| 17090 | | // They'll tell us by setting `waiter.done` and unparking us. |
| 17091 | | while (!waiter.done.load(.acquire)) { |
| 17092 | | park(null, ptr) catch |e| switch (e) { |
| 17081 | error.Timeout => { |
| 17082 | // We're not out of the woods yet: an unpark could race with the timeout. |
| 17083 | const old_status = waiter.thread_status.fetchAnd( |
| 17084 | .{ .cancelation = @enumFromInt(0b110), .awaitable = .all_ones }, |
| 17085 | .monotonic, |
| 17086 | ); |
| 17087 | switch (old_status.cancelation) { |
| 17088 | .parked => { |
| 17089 | // No race. It is our responsibility to remove `waiter` from `bucket`. |
| 17090 | // New status is `.none`. |
| 17091 | bucket.mutex.lock(); |
| 17092 | defer bucket.mutex.unlock(); |
| 17093 | bucket.waiters.remove(&waiter.node); |
| 17094 | assert(bucket.num_waiters.fetchSub(1, .monotonic) > 0); |
| 17095 | }, |
| 17096 | .none, .canceling => { |
| 17097 | // Race condition: the timeout was reached, then `wake` or a canceler tried |
| 17098 | // to unpark us. Whoever did that will remove us from `bucket`. Wait for |
| 17099 | // that (and drop the unpark request in doing so). |
| 17100 | // New status is `.none` or `.canceling` respectively. |
| 17101 | park(.none, ptr, waiter.thread_status) catch |e| switch (e) { |
| 17093 | 17102 | error.Timeout => unreachable, |
| 17094 | 17103 | }; |
| 17095 | | } |
| 17096 | | }, |
| 17097 | | .canceled => unreachable, |
| 17098 | | .blocked => unreachable, |
| 17099 | | .blocked_alertable => unreachable, |
| 17100 | | .blocked_alertable_canceling => unreachable, |
| 17101 | | .blocked_canceling => unreachable, |
| 17104 | }, |
| 17105 | .canceled => unreachable, |
| 17106 | .blocked => unreachable, |
| 17107 | .blocked_alertable => unreachable, |
| 17108 | .blocked_canceling => unreachable, |
| 17109 | .blocked_alertable_canceling => unreachable, |
| 17110 | } |
| 17102 | 17111 | }, |
| 17103 | 17112 | } |
| 17104 | 17113 | } |
| ... | ... | @@ -17119,8 +17128,8 @@ const parking_futex = struct { |
| 17119 | 17128 | // of the critical section. This forms a singly-linked list of waiters using `Waiter.node.next`. |
| 17120 | 17129 | var waking_head: ?*std.DoublyLinkedList.Node = null; |
| 17121 | 17130 | { |
| 17122 | | mutexLockInternal(&bucket.mutex); |
| 17123 | | defer mutexUnlockInternal(&bucket.mutex); |
| 17131 | bucket.mutex.lock(); |
| 17132 | defer bucket.mutex.unlock(); |
| 17124 | 17133 | |
| 17125 | 17134 | var num_removed: u32 = 0; |
| 17126 | 17135 | var it = bucket.waiters.first; |
| ... | ... | @@ -17147,6 +17156,9 @@ const parking_futex = struct { |
| 17147 | 17156 | waiter.node.next = waking_head; |
| 17148 | 17157 | waking_head = &waiter.node; |
| 17149 | 17158 | num_removed += 1; |
| 17159 | // Signal to `waiter` that they're about to be unparked, in case we're racing with their |
| 17160 | // timeout. See corresponding logic in `wake`. |
| 17161 | waiter.address = 0; |
| 17150 | 17162 | } |
| 17151 | 17163 | |
| 17152 | 17164 | _ = bucket.num_waiters.fetchSub(num_removed, .monotonic); |
| ... | ... | @@ -17161,8 +17173,6 @@ const parking_futex = struct { |
| 17161 | 17173 | const waiter: *Waiter = @fieldParentPtr("node", node); |
| 17162 | 17174 | unpark_buf[unpark_len] = waiter.tid; |
| 17163 | 17175 | unpark_len += 1; |
| 17164 | | waiter.done.store(true, .release); |
| 17165 | | // `waiter.*` is now potentially invalid so must not be referenced again. |
| 17166 | 17176 | if (unpark_len == unpark_buf.len) { |
| 17167 | 17177 | unpark(&unpark_buf, ptr); |
| 17168 | 17178 | unpark_len = 0; |
| ... | ... | @@ -17175,18 +17185,17 @@ const parking_futex = struct { |
| 17175 | 17185 | |
| 17176 | 17186 | fn removeCanceledWaiter(waiter: *Waiter) void { |
| 17177 | 17187 | const bucket = bucketForAddress(waiter.address); |
| 17178 | | mutexLockInternal(&bucket.mutex); |
| 17179 | | defer mutexUnlockInternal(&bucket.mutex); |
| 17188 | bucket.mutex.lock(); |
| 17189 | defer bucket.mutex.unlock(); |
| 17180 | 17190 | bucket.waiters.remove(&waiter.node); |
| 17181 | 17191 | assert(bucket.num_waiters.fetchSub(1, .monotonic) > 0); |
| 17182 | | waiter.done.store(true, .release); // potentially invalidates `waiter.*` |
| 17183 | 17192 | } |
| 17184 | 17193 | }; |
| 17185 | 17194 | const parking_sleep = struct { |
| 17186 | 17195 | comptime { |
| 17187 | 17196 | assert(use_parking_sleep); |
| 17188 | 17197 | } |
| 17189 | | fn sleep(deadline: ?Io.Clock.Timestamp) Io.Cancelable!void { |
| 17198 | fn sleep(timeout: Io.Timeout) Io.Cancelable!void { |
| 17190 | 17199 | const opt_thread = Thread.current; |
| 17191 | 17200 | cancelable: { |
| 17192 | 17201 | const thread = opt_thread orelse break :cancelable; |
| ... | ... | @@ -17195,90 +17204,238 @@ const parking_sleep = struct { |
| 17195 | 17204 | .unblocked => {}, |
| 17196 | 17205 | } |
| 17197 | 17206 | thread.futex_waiter = null; |
| 17198 | | const orig_status = thread.status.fetchOr( |
| 17199 | | .{ .cancelation = @enumFromInt(0b001), .awaitable = .null }, |
| 17200 | | .release, // release `thread.futex_waiter` |
| 17201 | | ); |
| 17202 | | switch (orig_status.cancelation) { |
| 17203 | | .none => {}, // status is now `.parked` |
| 17204 | | .canceling => return error.Canceled, // status is now `.canceled` |
| 17205 | | .canceled => break :cancelable, // status is still `.canceled` |
| 17206 | | .parked => unreachable, |
| 17207 | | .blocked => unreachable, |
| 17208 | | .blocked_alertable => unreachable, |
| 17209 | | .blocked_alertable_canceling => unreachable, |
| 17210 | | .blocked_canceling => unreachable, |
| 17211 | | } |
| 17212 | | while (park(deadline, null)) { |
| 17213 | | // Either a cancelation or a spurious unpark; let's see which! |
| 17214 | | switch (thread.status.load(.monotonic).cancelation) { |
| 17215 | | .parked => continue, // spurious unpark; keep sleeping |
| 17216 | | .canceling => { |
| 17217 | | // We got canceled; update our state and return. |
| 17218 | | thread.status.store( |
| 17219 | | .{ .cancelation = .canceled, .awaitable = orig_status.awaitable }, |
| 17220 | | .monotonic, |
| 17221 | | ); |
| 17222 | | return error.Canceled; |
| 17223 | | }, |
| 17224 | | .none => unreachable, |
| 17225 | | .canceled => unreachable, |
| 17207 | { |
| 17208 | const old_status = thread.status.fetchOr( |
| 17209 | .{ .cancelation = @enumFromInt(0b001), .awaitable = .null }, |
| 17210 | .release, // release `thread.futex_waiter` |
| 17211 | ); |
| 17212 | switch (old_status.cancelation) { |
| 17213 | .none => {}, // status is now `.parked` |
| 17214 | .canceling => return error.Canceled, // status is now `.canceled` |
| 17215 | .canceled => break :cancelable, // status is still `.canceled` |
| 17216 | .parked => unreachable, |
| 17226 | 17217 | .blocked => unreachable, |
| 17227 | 17218 | .blocked_alertable => unreachable, |
| 17228 | 17219 | .blocked_alertable_canceling => unreachable, |
| 17229 | 17220 | .blocked_canceling => unreachable, |
| 17230 | 17221 | } |
| 17231 | | } else |err| switch (err) { |
| 17232 | | error.Timeout => switch (thread.status.fetchAnd( |
| 17233 | | .{ .cancelation = @enumFromInt(0b110), .awaitable = .all_ones }, |
| 17222 | } |
| 17223 | if (park(timeout, null, &thread.status)) { |
| 17224 | // The only reason this could possibly happen is cancelation. |
| 17225 | const old_status = thread.status.load(.monotonic); |
| 17226 | assert(old_status.cancelation == .canceling); |
| 17227 | thread.status.store( |
| 17228 | .{ .cancelation = .canceled, .awaitable = old_status.awaitable }, |
| 17234 | 17229 | .monotonic, |
| 17235 | | ).cancelation) { |
| 17236 | | // We updated our own status from `.parked` to `.none`. |
| 17237 | | .parked => return, // new status is `.none` |
| 17238 | | .canceling => { |
| 17239 | | // Timeout raced with a cancelation. We don't need to do anything, but |
| 17240 | | // the next `park` on this thread will see a spurious unpark. |
| 17241 | | // Status is still `.canceling`. |
| 17242 | | return; |
| 17243 | | }, |
| 17244 | | .none => unreachable, |
| 17245 | | .canceled => unreachable, |
| 17246 | | .blocked => unreachable, |
| 17247 | | .blocked_alertable => unreachable, |
| 17248 | | .blocked_alertable_canceling => unreachable, |
| 17249 | | .blocked_canceling => unreachable, |
| 17230 | ); |
| 17231 | return error.Canceled; |
| 17232 | } else |err| switch (err) { |
| 17233 | error.Timeout => { |
| 17234 | // We're not out of the woods yet: an unpark could race with the timeout. |
| 17235 | const old_status = thread.status.fetchAnd( |
| 17236 | .{ .cancelation = @enumFromInt(0b110), .awaitable = .all_ones }, |
| 17237 | .monotonic, |
| 17238 | ); |
| 17239 | switch (old_status.cancelation) { |
| 17240 | .parked => return, // No race; new status is `.none` |
| 17241 | .canceling => { |
| 17242 | // Race condition: the timeout was reached, then someone tried to unpark |
| 17243 | // us for a cancelation. Whoever did that will have called `unpark`, so |
| 17244 | // drop that unpark request by waiting for it. |
| 17245 | // Status is still `.canceling`. |
| 17246 | park(.none, null, &thread.status) catch |e| switch (e) { |
| 17247 | error.Timeout => unreachable, |
| 17248 | }; |
| 17249 | return; |
| 17250 | }, |
| 17251 | .none => unreachable, |
| 17252 | .canceled => unreachable, |
| 17253 | .blocked => unreachable, |
| 17254 | .blocked_alertable => unreachable, |
| 17255 | .blocked_canceling => unreachable, |
| 17256 | .blocked_alertable_canceling => unreachable, |
| 17257 | } |
| 17250 | 17258 | }, |
| 17251 | 17259 | } |
| 17252 | 17260 | } |
| 17253 | | // Uncancelable sleep; this case is very simple. |
| 17254 | | while (park(deadline, null)) { |
| 17255 | | // Definitely spurious; nothing to do. |
| 17261 | // Uncancelable sleep; we expect not to be manually unparked. |
| 17262 | var dummy_status: std.atomic.Value(Thread.Status) = .init(.{ .cancelation = .parked, .awaitable = .null }); |
| 17263 | if (park(timeout, null, &dummy_status)) { |
| 17264 | unreachable; // unexpected unpark |
| 17256 | 17265 | } else |err| switch (err) { |
| 17257 | 17266 | error.Timeout => return, |
| 17258 | 17267 | } |
| 17259 | 17268 | } |
| 17260 | | /// Sleep for approximately `ms` awake milliseconds in an attempt to work around Windows kernel bugs. |
| 17261 | | fn windowsRetrySleep(ms: u32) (Io.Cancelable || Io.UnexpectedError)!void { |
| 17262 | | const now_timestamp = nowWindows(.awake); // '.awake' is supported on Windows |
| 17263 | | const deadline = now_timestamp.addDuration(.fromMilliseconds(ms)); |
| 17264 | | try parking_sleep.sleep(.{ .raw = deadline, .clock = .awake }); |
| 17269 | }; |
| 17270 | const ParkingMutex = struct { |
| 17271 | state: std.atomic.Value(State), |
| 17272 | |
| 17273 | const init: ParkingMutex = .{ .state = .init(.unlocked) }; |
| 17274 | |
| 17275 | comptime { |
| 17276 | assert(use_parking_futex); |
| 17277 | } |
| 17278 | |
| 17279 | const State = enum(usize) { |
| 17280 | unlocked = 1, |
| 17281 | /// This value is intentionally 0 so that `waiter` returns `null`. |
| 17282 | locked_once = 0, |
| 17283 | /// Contended; value is a `*Waiter`. |
| 17284 | _, |
| 17285 | /// Returns the head of the waiter list. Illegal to call if `s == .unlocked`. |
| 17286 | fn waiter(s: State) ?*Waiter { |
| 17287 | return @ptrFromInt(@intFromEnum(s)); |
| 17288 | } |
| 17289 | /// Returns a locked state where `w` is contending the lock. |
| 17290 | /// If `w` is `null`, returns `.locked_once`. |
| 17291 | fn fromWaiter(w: ?*Waiter) State { |
| 17292 | return @enumFromInt(@intFromPtr(w)); |
| 17293 | } |
| 17294 | }; |
| 17295 | const Waiter = struct { |
| 17296 | status: std.atomic.Value(Thread.Status), |
| 17297 | /// Never modified once the `Waiter` is in the linked list. |
| 17298 | next: ?*Waiter, |
| 17299 | /// Never modified once the `Waiter` is in the linked list. |
| 17300 | tid: std.Thread.Id, |
| 17301 | }; |
| 17302 | fn lock(m: *ParkingMutex) void { |
| 17303 | state: switch (State.unlocked) { // assume 'unlocked' to optimize for uncontended case |
| 17304 | .unlocked => continue :state m.state.cmpxchgWeak( |
| 17305 | .unlocked, |
| 17306 | .locked_once, |
| 17307 | .acquire, // acquire lock |
| 17308 | .monotonic, |
| 17309 | ) orelse { |
| 17310 | @branchHint(.likely); |
| 17311 | return; |
| 17312 | }, |
| 17313 | |
| 17314 | .locked_once, _ => |last_state| { |
| 17315 | const old_waiter = last_state.waiter(); |
| 17316 | const self_tid = if (Thread.current) |t| t.id else std.Thread.getCurrentId(); |
| 17317 | var waiter: Waiter = .{ |
| 17318 | .next = old_waiter, |
| 17319 | .status = .init(.{ .cancelation = .parked, .awaitable = .null }), |
| 17320 | .tid = self_tid, |
| 17321 | }; |
| 17322 | if (m.state.cmpxchgWeak( |
| 17323 | .fromWaiter(old_waiter), |
| 17324 | .fromWaiter(&waiter), |
| 17325 | .release, // release `waiter` |
| 17326 | .monotonic, |
| 17327 | )) |new_state| { |
| 17328 | continue :state new_state; |
| 17329 | } |
| 17330 | // We're now in the list of waiters---park until we're given the lock. |
| 17331 | park(.none, m, &waiter.status) catch |err| switch (err) { |
| 17332 | error.Timeout => unreachable, |
| 17333 | }; |
| 17334 | // We now hold the lock. |
| 17335 | assert(waiter.status.load(.monotonic).cancelation == .none); |
| 17336 | return; |
| 17337 | }, |
| 17338 | } |
| 17339 | } |
| 17340 | fn unlock(m: *ParkingMutex) void { |
| 17341 | state: switch (State.locked_once) { // assume 'locked_once' to optimize for uncontended case |
| 17342 | .unlocked => unreachable, // we hold the lock |
| 17343 | |
| 17344 | .locked_once => continue :state m.state.cmpxchgWeak( |
| 17345 | .locked_once, |
| 17346 | .unlocked, |
| 17347 | .release, // release lock |
| 17348 | .acquire, // acquire any `Waiter` memory |
| 17349 | ) orelse { |
| 17350 | @branchHint(.likely); |
| 17351 | return; |
| 17352 | }, |
| 17353 | |
| 17354 | _ => |last_state| { |
| 17355 | // The logic here does not have ABA problems, and does some accesses non-atomically, |
| 17356 | // because `Waiter.next` is owned by the lock holder (that's us!) once the waiter is |
| 17357 | // in the linked list, up until we set `Waiter.status` to `.none`. |
| 17358 | |
| 17359 | // Run through the waiter list to the end to ensure fairness. This is obviously not |
| 17360 | // ideal, but it shouldn't be a big deal in practice provided the critical section |
| 17361 | // is fairly small (so we won't get too many threads contending the mutex at once). |
| 17362 | // There's a *chance* we could get away with a LIFO queue for our use case, but I |
| 17363 | // don't wanna risk that. |
| 17364 | var parent: ?*Waiter = null; |
| 17365 | var waiter: *Waiter = last_state.waiter().?; |
| 17366 | while (waiter.next) |next| { |
| 17367 | parent = waiter; |
| 17368 | waiter = next; |
| 17369 | } |
| 17370 | // `waiter` is next in line for the lock. Remove them from the list. |
| 17371 | if (parent) |p| { |
| 17372 | assert(p.next == waiter); |
| 17373 | p.next = null; |
| 17374 | } else { |
| 17375 | // We're waking the last waiter, so clear the list head. |
| 17376 | if (m.state.cmpxchgWeak( |
| 17377 | .fromWaiter(last_state.waiter().?), |
| 17378 | .locked_once, |
| 17379 | .acquire, |
| 17380 | .acquire, // acquire any new `Waiter` memory |
| 17381 | )) |new_state| { |
| 17382 | continue :state new_state; |
| 17383 | } |
| 17384 | } |
| 17385 | // Now we're ready to actually hand the lock over to them. |
| 17386 | const tid = waiter.tid; // load this before the store below potentially invalidates `waiter` |
| 17387 | waiter.status.store(.{ .cancelation = .none, .awaitable = .null }, .release); // release lock |
| 17388 | unpark(&.{tid}, m); |
| 17389 | return; |
| 17390 | }, |
| 17391 | } |
| 17265 | 17392 | } |
| 17266 | 17393 | }; |
| 17267 | 17394 | |
| 17268 | | /// Spurious wakeups are possible. |
| 17269 | | /// |
| 17270 | | /// `addr_hint` has no semantic effect, but may allow the OS to optimize this operation. |
| 17271 | | fn park(opt_deadline: ?Io.Clock.Timestamp, addr_hint: ?*const anyopaque) error{Timeout}!void { |
| 17395 | fn timeoutToWindowsInterval(timeout: Io.Timeout) ?windows.LARGE_INTEGER { |
| 17396 | // ntdll only supports two combinations: |
| 17397 | // * real-time (`.real`) sleeps with absolute deadlines |
| 17398 | // * monotonic (`.awake`/`.boot`) sleeps with relative durations |
| 17399 | const clock = switch (timeout) { |
| 17400 | .none => return null, |
| 17401 | .duration => |d| d.clock, |
| 17402 | .deadline => |d| d.clock, |
| 17403 | }; |
| 17404 | switch (clock) { |
| 17405 | .cpu_process, .cpu_thread => unreachable, // cannot sleep for CPU time |
| 17406 | .real => { |
| 17407 | const deadline = switch (timeout) { |
| 17408 | .none => unreachable, |
| 17409 | .duration => |d| nowWindows(clock).addDuration(d.raw), |
| 17410 | .deadline => |d| d.raw, |
| 17411 | }; |
| 17412 | return @intCast(@max(@divTrunc(deadline.nanoseconds, 100), 0)); |
| 17413 | }, |
| 17414 | .awake, .boot => { |
| 17415 | const duration = switch (timeout) { |
| 17416 | .none => unreachable, |
| 17417 | .duration => |d| d.raw, |
| 17418 | .deadline => |d| nowWindows(clock).durationTo(d.raw), |
| 17419 | }; |
| 17420 | return @intCast(@min(@divTrunc(-duration.nanoseconds, 100), -1)); |
| 17421 | }, |
| 17422 | } |
| 17423 | } |
| 17424 | |
| 17425 | fn park( |
| 17426 | timeout: Io.Timeout, |
| 17427 | /// This value has no semantic effect, but may allow the OS to optimize the operation. |
| 17428 | addr_hint: ?*const anyopaque, |
| 17429 | /// The API on NetBSD and Illumos sucks and can unpark spuriously (well, it *can't*, but signals |
| 17430 | /// cause an indistinguishable unblock, and libpthread really likes to leave unparks pending). |
| 17431 | /// As such, on these targets only, this `status` is checked to determine if an unpark is real. |
| 17432 | /// no way to differentiate |
| 17433 | status: *std.atomic.Value(Thread.Status), |
| 17434 | ) error{Timeout}!void { |
| 17272 | 17435 | comptime assert(use_parking_futex or use_parking_sleep); |
| 17273 | 17436 | switch (native_os) { |
| 17274 | 17437 | .windows => { |
| 17275 | | var timeout_buf: windows.LARGE_INTEGER = undefined; |
| 17276 | | const raw_timeout: ?*windows.LARGE_INTEGER = if (opt_deadline) |deadline| timeout: { |
| 17277 | | const now_timestamp = nowWindows(deadline.clock); |
| 17278 | | const nanoseconds = now_timestamp.durationTo(deadline.raw).nanoseconds; |
| 17279 | | timeout_buf = @intCast(@divTrunc(-nanoseconds, 100)); |
| 17280 | | break :timeout &timeout_buf; |
| 17281 | | } else null; |
| 17438 | const raw_timeout = timeoutToWindowsInterval(timeout); |
| 17282 | 17439 | // `RtlWaitOnAddress` passes the futex address in as the first argument to this call, |
| 17283 | 17440 | // but it's unclear what that actually does, especially since `NtAlertThreadByThreadId` |
| 17284 | 17441 | // does *not* accept the address so the kernel can't really be using it as a hint. An |
| ... | ... | @@ -17292,7 +17449,10 @@ fn park(opt_deadline: ?Io.Clock.Timestamp, addr_hint: ?*const anyopaque) error{T |
| 17292 | 17449 | // this parameter). However, to err on the side of caution, let's match the behavior of |
| 17293 | 17450 | // `RtlWaitOnAddress` and pass the pointer, in case the kernel ever does something |
| 17294 | 17451 | // stupid such as trying to dereference it. |
| 17295 | | switch (windows.ntdll.NtWaitForAlertByThreadId(addr_hint, raw_timeout)) { |
| 17452 | switch (windows.ntdll.NtWaitForAlertByThreadId( |
| 17453 | addr_hint, |
| 17454 | if (raw_timeout) |*t| t else null, |
| 17455 | )) { |
| 17296 | 17456 | .ALERTED => return, |
| 17297 | 17457 | .TIMEOUT => return error.Timeout, |
| 17298 | 17458 | else => unreachable, |
| ... | ... | @@ -17300,23 +17460,34 @@ fn park(opt_deadline: ?Io.Clock.Timestamp, addr_hint: ?*const anyopaque) error{T |
| 17300 | 17460 | }, |
| 17301 | 17461 | .netbsd => { |
| 17302 | 17462 | var ts_buf: posix.timespec = undefined; |
| 17303 | | const ts: ?*posix.timespec, const clock_real: bool = if (opt_deadline) |deadline| timeout: { |
| 17304 | | ts_buf = timestampToPosix(deadline.raw.nanoseconds); |
| 17305 | | break :timeout .{ &ts_buf, deadline.clock == .real }; |
| 17306 | | } else .{ null, true }; |
| 17307 | | switch (posix.errno(std.c._lwp_park( |
| 17308 | | if (clock_real) .REALTIME else .MONOTONIC, |
| 17309 | | .{ .ABSTIME = true }, |
| 17310 | | ts, |
| 17311 | | 0, |
| 17312 | | addr_hint, |
| 17313 | | null, |
| 17314 | | ))) { |
| 17315 | | .SUCCESS, .ALREADY, .INTR => return, |
| 17316 | | .TIMEDOUT => return error.Timeout, |
| 17317 | | .INVAL => unreachable, |
| 17318 | | .SRCH => unreachable, |
| 17319 | | else => unreachable, |
| 17463 | const ts: ?*posix.timespec, const abstime: bool, const clock_real: bool = switch (timeout) { |
| 17464 | .none => .{ null, false, false }, |
| 17465 | .deadline => |timestamp| timeout: { |
| 17466 | ts_buf = timestampToPosix(timestamp.raw.nanoseconds); |
| 17467 | break :timeout .{ &ts_buf, true, timestamp.clock == .real }; |
| 17468 | }, |
| 17469 | .duration => |duration| timeout: { |
| 17470 | ts_buf = timestampToPosix(duration.raw.nanoseconds); |
| 17471 | break :timeout .{ &ts_buf, false, duration.clock == .real }; |
| 17472 | }, |
| 17473 | }; |
| 17474 | // It's okay to pass the same timeout in a loop. If it's a duration, the OS actually |
| 17475 | // writes the remaining time into the buffer when the syscall returns. |
| 17476 | while (status.load(.monotonic).cancelation == .parked) { |
| 17477 | switch (posix.errno(std.c._lwp_park( |
| 17478 | if (clock_real) .REALTIME else .MONOTONIC, |
| 17479 | .{ .ABSTIME = abstime }, |
| 17480 | ts, |
| 17481 | 0, |
| 17482 | addr_hint, |
| 17483 | null, |
| 17484 | ))) { |
| 17485 | .SUCCESS, .ALREADY, .INTR => {}, |
| 17486 | .TIMEDOUT => return error.Timeout, |
| 17487 | .INVAL => unreachable, |
| 17488 | .SRCH => unreachable, |
| 17489 | else => unreachable, |
| 17490 | } |
| 17320 | 17491 | } |
| 17321 | 17492 | }, |
| 17322 | 17493 | .illumos => @panic("TODO: illumos lwp_park"), |
| ... | ... | @@ -17324,24 +17495,8 @@ fn park(opt_deadline: ?Io.Clock.Timestamp, addr_hint: ?*const anyopaque) error{T |
| 17324 | 17495 | } |
| 17325 | 17496 | } |
| 17326 | 17497 | |
| 17327 | | fn deadlineToWindowsInterval(t: *Io.Threaded, deadline: Io.Clock.Timestamp) windows.LARGE_INTEGER { |
| 17328 | | // ntdll only supports two combinations: |
| 17329 | | // * real-time (`.real`) sleeps with absolute deadlines |
| 17330 | | // * monotonic (`.awake`/`.boot`) sleeps with relative durations |
| 17331 | | switch (deadline.clock) { |
| 17332 | | .cpu_process, .cpu_thread => return 0, |
| 17333 | | .real => { |
| 17334 | | return @intCast(@max(@divTrunc(deadline.raw.nanoseconds, 100), 0)); |
| 17335 | | }, |
| 17336 | | .awake, .boot => { |
| 17337 | | const duration = deadline.durationFromNow(ioBasic(t)); |
| 17338 | | return @intCast(@min(@divTrunc(-duration.raw.nanoseconds, 100), -1)); |
| 17339 | | }, |
| 17340 | | } |
| 17341 | | } |
| 17342 | | |
| 17343 | 17498 | const UnparkTid = switch (native_os) { |
| 17344 | | // `NtAlertMultipleThreadByThreadId` is weird and wants 64-bit thread handles? |
| 17499 | // `NtAlertMultipleThreadByThreadId` is weird and wants 64-bit thread IDs? |
| 17345 | 17500 | .windows => usize, |
| 17346 | 17501 | else => std.Thread.Id, |
| 17347 | 17502 | }; |
| ... | ... | @@ -18162,14 +18317,8 @@ fn eventSet(event: *Io.Event) void { |
| 18162 | 18317 | } |
| 18163 | 18318 | } |
| 18164 | 18319 | |
| 18165 | | const Condition = if (!is_windows) Io.Condition else struct { |
| 18166 | | condition: windows.CONDITION_VARIABLE, |
| 18167 | | const init: @This() = .{ .condition = .{} }; |
| 18168 | | }; |
| 18169 | | |
| 18170 | 18320 | /// Same as `Io.Condition.broadcast` but avoids the VTable. |
| 18171 | | fn condBroadcast(cond: *Condition) void { |
| 18172 | | if (is_windows) return windows.ntdll.RtlWakeAllConditionVariable(&cond.condition); |
| 18321 | fn condBroadcast(cond: *Io.Condition) void { |
| 18173 | 18322 | var prev_state = cond.state.load(.monotonic); |
| 18174 | 18323 | while (prev_state.waiters > prev_state.signals) { |
| 18175 | 18324 | @branchHint(.unlikely); |
| ... | ... | @@ -18189,8 +18338,7 @@ fn condBroadcast(cond: *Condition) void { |
| 18189 | 18338 | } |
| 18190 | 18339 | |
| 18191 | 18340 | /// Same as `Io.Condition.signal` but avoids the VTable. |
| 18192 | | fn condSignal(cond: *Condition) void { |
| 18193 | | if (is_windows) return windows.ntdll.RtlWakeConditionVariable(&cond.condition); |
| 18341 | fn condSignal(cond: *Io.Condition) void { |
| 18194 | 18342 | var prev_state = cond.state.load(.monotonic); |
| 18195 | 18343 | while (prev_state.waiters > prev_state.signals) { |
| 18196 | 18344 | @branchHint(.unlikely); |
| ... | ... | @@ -18210,11 +18358,7 @@ fn condSignal(cond: *Condition) void { |
| 18210 | 18358 | } |
| 18211 | 18359 | |
| 18212 | 18360 | /// Same as `Io.Condition.waitUncancelable` but avoids the VTable. |
| 18213 | | fn condWait(cond: *Condition, mutex: *Mutex) void { |
| 18214 | | if (is_windows) { |
| 18215 | | _ = windows.kernel32.SleepConditionVariableSRW(&cond.condition, &mutex.srwlock, windows.INFINITE, 0); |
| 18216 | | return; |
| 18217 | | } |
| 18361 | fn condWait(cond: *Io.Condition, mutex: *Io.Mutex) void { |
| 18218 | 18362 | var epoch = cond.epoch.load(.acquire); // `.acquire` to ensure ordered before state load |
| 18219 | 18363 | |
| 18220 | 18364 | { |
| ... | ... | @@ -18222,8 +18366,8 @@ fn condWait(cond: *Condition, mutex: *Mutex) void { |
| 18222 | 18366 | assert(prev_state.waiters < std.math.maxInt(u16)); // overflow caused by too many waiters |
| 18223 | 18367 | } |
| 18224 | 18368 | |
| 18225 | | mutexUnlockInternal(mutex); |
| 18226 | | defer mutexLockInternal(mutex); |
| 18369 | mutexUnlock(mutex); |
| 18370 | defer mutexLock(mutex); |
| 18227 | 18371 | |
| 18228 | 18372 | while (true) { |
| 18229 | 18373 | Thread.futexWaitUncancelable(&cond.epoch.raw, epoch, null); |
| ... | ... | @@ -18243,16 +18387,6 @@ fn condWait(cond: *Condition, mutex: *Mutex) void { |
| 18243 | 18387 | } |
| 18244 | 18388 | } |
| 18245 | 18389 | |
| 18246 | | const Mutex = if (!is_windows) Io.Mutex else struct { |
| 18247 | | srwlock: windows.SRWLOCK, |
| 18248 | | const init: @This() = .{ .srwlock = .{} }; |
| 18249 | | }; |
| 18250 | | |
| 18251 | | fn mutexLockInternal(m: *Mutex) void { |
| 18252 | | if (is_windows) return windows.ntdll.RtlAcquireSRWLockExclusive(&m.srwlock); |
| 18253 | | return mutexLock(m); |
| 18254 | | } |
| 18255 | | |
| 18256 | 18390 | /// Same as `Io.Mutex.lockUncancelable` but avoids the VTable. |
| 18257 | 18391 | pub fn mutexLock(m: *Io.Mutex) void { |
| 18258 | 18392 | const initial_state = m.state.cmpxchgWeak( |
| ... | ... | @@ -18272,11 +18406,6 @@ pub fn mutexLock(m: *Io.Mutex) void { |
| 18272 | 18406 | } |
| 18273 | 18407 | } |
| 18274 | 18408 | |
| 18275 | | fn mutexUnlockInternal(m: *Mutex) void { |
| 18276 | | if (is_windows) return windows.ntdll.RtlReleaseSRWLockExclusive(&m.srwlock); |
| 18277 | | return mutexUnlock(m); |
| 18278 | | } |
| 18279 | | |
| 18280 | 18409 | /// Same as `Io.Mutex.unlock` but avoids the VTable. |
| 18281 | 18410 | pub fn mutexUnlock(m: *Io.Mutex) void { |
| 18282 | 18411 | switch (m.state.swap(.unlocked, .release)) { |