authorgravatar for jacobly@ziglang.orgJacob Young <jacobly@ziglang.org> 2026-09-25 17:40:22-04:00
committergravatar for jacobly@ziglang.orgJacob Young <jacobly@ziglang.org> 2026-09-26 19:08:50-04:00
log2cdf284af96ea016a5553ae9f6516b9b2c8f3f8e
treed90d6c7747c55be094282790f26dbe7b37637b5a
parent4dd645fdddec2c56f8eaabce9f358f95b54f8eec

Threaded: implement new process spawning API for windows


5 files changed, 249 insertions(+), 141 deletions(-)

lib/std/Io/Threaded.zig+192-105
......@@ -85,6 +85,8 @@ csprng: Csprng = .uninitialized,
8585
8686system_basic_information: SystemBasicInformation = .{},
8787
88process_spawn_mutex: if (is_windows) Io.Mutex else void = if (is_windows) .init else {},
89
8890const SystemBasicInformation = if (!is_windows) struct {} else struct {
8991 buffer: windows.SYSTEM.BASIC_INFORMATION = undefined,
9092 initialized: std.atomic.Value(bool) = .{ .raw = false },
......@@ -1706,7 +1708,7 @@ var global_single_threaded_instance: Threaded = .init_single_threaded;
17061708pub const global_single_threaded: *Threaded = &global_single_threaded_instance;
17071709
17081710pub fn setAsyncLimit(t: *Threaded, new_limit: Io.Limit) void {
1709 mutexLock(&t.mutex);
1711 mutexLockUncancelable(&t.mutex);
17101712 defer mutexUnlock(&t.mutex);
17111713 t.async_limit = new_limit;
17121714}
......@@ -1727,7 +1729,7 @@ pub fn deinit(t: *Threaded) void {
17271729fn join(t: *Threaded) void {
17281730 if (builtin.single_threaded) return;
17291731 {
1730 mutexLock(&t.mutex);
1732 mutexLockUncancelable(&t.mutex);
17311733 defer mutexUnlock(&t.mutex);
17321734 t.join_requested = true;
17331735 }
......@@ -1789,7 +1791,7 @@ fn worker(t: *Threaded) void {
17891791
17901792 defer t.wait_group.finish();
17911793
1792 mutexLock(&t.mutex);
1794 mutexLockUncancelable(&t.mutex);
17931795 defer mutexUnlock(&t.mutex);
17941796
17951797 while (true) {
......@@ -1798,7 +1800,7 @@ fn worker(t: *Threaded) void {
17981800 thread.cancel_protection = .unblocked;
17991801 const runnable: *Runnable = @fieldParentPtr("node", runnable_node);
18001802 runnable.startFn(runnable, &thread, t);
1801 mutexLock(&t.mutex);
1803 mutexLockUncancelable(&t.mutex);
18021804 t.busy_count -= 1;
18031805 }
18041806 if (t.join_requested) break;
......@@ -2088,7 +2090,7 @@ fn async(
20882090 },
20892091 };
20902092
2091 mutexLock(&t.mutex);
2093 mutexLockUncancelable(&t.mutex);
20922094
20932095 const busy_count = t.busy_count;
20942096
......@@ -2140,7 +2142,7 @@ fn concurrent(
21402142 };
21412143 errdefer future.destroy(gpa);
21422144
2143 mutexLock(&t.mutex);
2145 mutexLockUncancelable(&t.mutex);
21442146 defer mutexUnlock(&t.mutex);
21452147
21462148 const busy_count = t.busy_count;
......@@ -2185,7 +2187,7 @@ fn groupAsync(
21852187 error.OutOfMemory => return groupAsyncEager(start, context.ptr),
21862188 };
21872189
2188 mutexLock(&t.mutex);
2190 mutexLockUncancelable(&t.mutex);
21892191
21902192 const busy_count = t.busy_count;
21912193
......@@ -2248,7 +2250,7 @@ fn groupConcurrent(
22482250 };
22492251 errdefer task.destroy(gpa);
22502252
2251 mutexLock(&t.mutex);
2253 mutexLockUncancelable(&t.mutex);
22522254 defer mutexUnlock(&t.mutex);
22532255
22542256 const busy_count = t.busy_count;
......@@ -4118,7 +4120,7 @@ fn fileStatWindows(userdata: ?*anyopaque, file: File) File.StatError!File.Stat {
41184120
41194121fn systemBasicInformation(t: *Threaded) ?*const windows.SYSTEM.BASIC_INFORMATION {
41204122 if (!t.system_basic_information.initialized.load(.acquire)) {
4121 mutexLock(&t.mutex);
4123 mutexLockUncancelable(&t.mutex);
41224124 defer mutexUnlock(&t.mutex);
41234125
41244126 switch (windows.ntdll.NtQuerySystemInformation(
......@@ -10011,7 +10013,7 @@ fn ntReadFileResult(io_status_block: *const windows.IO_STATUS_BLOCK) !usize {
1001110013 .PENDING => unreachable,
1001210014 .CANCELLED => unreachable,
1001310015 .SUCCESS => return io_status_block.Information,
10014 .END_OF_FILE, .PIPE_BROKEN => return error.EndOfStream,
10016 .END_OF_FILE, .PIPE_BROKEN, .PIPE_CLOSING => return error.EndOfStream,
1001510017 .INVALID_HANDLE => return error.NotOpenForReading,
1001610018 .INVALID_DEVICE_REQUEST => return error.IsDir,
1001710019 .FILE_LOCK_CONFLICT => return error.LockViolation,
......@@ -10028,7 +10030,7 @@ fn ntWriteFileResult(io_status_block: *const windows.IO_STATUS_BLOCK) !usize {
1002810030 .INVALID_USER_BUFFER => return error.SystemResources,
1002910031 .NO_MEMORY => return error.SystemResources,
1003010032 .QUOTA_EXCEEDED => return error.SystemResources,
10031 .PIPE_BROKEN => return error.BrokenPipe,
10033 .PIPE_BROKEN, .PIPE_CLOSING => return error.BrokenPipe,
1003210034 .INVALID_HANDLE => return error.NotOpenForWriting,
1003310035 .FILE_LOCK_CONFLICT => return error.LockViolation,
1003410036 .ACCESS_DENIED => return error.AccessDenied,
......@@ -14091,7 +14093,7 @@ fn lockStderr(userdata: ?*anyopaque, terminal_mode: ?Io.Terminal.Mode) Io.Cancel
1409114093 const current_thread_id = Thread.currentId();
1409214094
1409314095 if (@atomicLoad(std.Thread.Id, &t.stderr_mutex_locker, .unordered) != current_thread_id) {
14094 mutexLock(&t.stderr_mutex);
14096 try mutexLock(&t.stderr_mutex);
1409514097 assert(t.stderr_mutex_lock_count == 0);
1409614098 @atomicStore(std.Thread.Id, &t.stderr_mutex_locker, current_thread_id, .unordered);
1409714099 }
......@@ -15117,7 +15119,7 @@ const WindowsEnvironStrings = struct {
1511715119};
1511815120
1511915121fn scanEnviron(t: *Threaded) void {
15120 mutexLock(&t.mutex);
15122 mutexLockUncancelable(&t.mutex);
1512115123 defer mutexUnlock(&t.mutex);
1512215124 if (t.environ_initialized) return;
1512315125 t.environ.scan(t.allocator);
......@@ -15416,7 +15418,7 @@ fn spawnPosix(t: *Threaded, options: process.SpawnOptions) process.SpawnError!Sp
1541615418
1541715419fn getDevNullFd(t: *Threaded) !posix.fd_t {
1541815420 {
15419 mutexLock(&t.mutex);
15421 try mutexLock(&t.mutex);
1542015422 defer mutexUnlock(&t.mutex);
1542115423 if (t.null_file.fd != -1) return t.null_file.fd;
1542215424 }
......@@ -15428,7 +15430,7 @@ fn getDevNullFd(t: *Threaded) !posix.fd_t {
1542815430 .SUCCESS => {
1542915431 syscall.finish();
1543015432 const fresh_fd: posix.fd_t = @intCast(rc);
15431 mutexLock(&t.mutex); // Another thread might have won the race.
15433 mutexLockUncancelable(&t.mutex); // Another thread might have won the race.
1543215434 defer mutexUnlock(&t.mutex);
1543315435 if (t.null_file.fd != -1) {
1543415436 closeFd(fresh_fd);
......@@ -15856,6 +15858,84 @@ fn processSpawnDarwin(userdata: ?*anyopaque, options: process.SpawnOptions) proc
1585615858fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) process.SpawnError!process.Child {
1585715859 const t: *Threaded = @ptrCast(@alignCast(userdata));
1585815860
15861 var arena_allocator = std.heap.ArenaAllocator.init(t.allocator);
15862 defer arena_allocator.deinit();
15863 const arena = arena_allocator.allocator();
15864
15865 const cwd_w = cwd_w: {
15866 switch (options.cwd) {
15867 .inherit => break :cwd_w null,
15868 .dir => |cwd_dir| {
15869 var dir_path_buffer = try arena.alloc(u16, windows.PATH_MAX_WIDE + 1);
15870 const dir_path = try GetFinalPathNameByHandle(
15871 cwd_dir.handle,
15872 .{},
15873 dir_path_buffer[0..windows.PATH_MAX_WIDE],
15874 );
15875 dir_path_buffer[dir_path.len] = 0;
15876 // Shrink the allocation down to just the path buffer + sentinel
15877 dir_path_buffer = try arena.realloc(dir_path_buffer, dir_path.len + 1);
15878 break :cwd_w dir_path_buffer[0..dir_path.len :0];
15879 },
15880 .path => |cwd| {
15881 break :cwd_w try std.unicode.wtf8ToWtf16LeAllocZ(arena, cwd);
15882 },
15883 }
15884 };
15885 const cwd_w_ptr = if (cwd_w) |cwd| cwd.ptr else null;
15886
15887 // If the app name has more than just a filename, then we need to separate
15888 // that into the basename and dirname and use the dirname as an addition to
15889 // the cwd path. This is because NtQueryDirectoryFile cannot accept
15890 // FileName params with path separators.
15891 var app_w_buf: [windows.PATH_MAX_WIDE]u16 = undefined;
15892 // If the app name is absolute, then the cwd will already have the app's dirname in it,
15893 // so only populate app_dir if app name is a relative path with > 0 path separators.
15894 const app_path_w, const app_dir_w, const app_name_w = app: {
15895 const path = switch (options.exe) {
15896 .detect, .search, .path => options.argv[0],
15897 .file => |file| {
15898 const path_w = try GetFinalPathNameByHandle(file.handle, .{}, &app_w_buf);
15899 var path_it: Dir.path.ComponentIterator(.windows, u16) = .init(path_w);
15900 const base = path_it.last().?;
15901 break :app .{
15902 null,
15903 if (path_it.previous()) |dir| dir.path else path_it.root(),
15904 base.name,
15905 };
15906 },
15907 .explicit => |explicit| explicit.path,
15908 };
15909 break :app .{
15910 if (Dir.path.isAbsolute(path)) null else switch (options.exe) {
15911 // The cwd provided by options is in effect when choosing the executable
15912 // path to match POSIX semantics.
15913 .detect, .search => cwd_w,
15914 .path => |dir| try GetFinalPathNameByHandle(dir.handle, .{}, &app_w_buf),
15915 .file => unreachable,
15916 .explicit => |explicit| try GetFinalPathNameByHandle(
15917 explicit.dir.handle,
15918 .{},
15919 &app_w_buf,
15920 ),
15921 },
15922 if (Dir.path.dirname(path)) |app_dir|
15923 try std.unicode.wtf8ToWtf16LeAllocZ(arena, app_dir)
15924 else
15925 null,
15926 try std.unicode.wtf8ToWtf16LeAllocZ(arena, Dir.path.basename(path)),
15927 };
15928 };
15929
15930 const flags: windows.CreateProcessFlags = .{
15931 .create_suspended = options.start_suspended,
15932 .create_unicode_environment = true,
15933 .create_no_window = options.create_no_window,
15934 };
15935
15936 try mutexLock(&t.process_spawn_mutex);
15937 defer mutexUnlock(&t.process_spawn_mutex);
15938
1585915939 const any_ignore =
1586015940 options.stdin == .ignore or
1586115941 options.stdout == .ignore or
......@@ -15897,6 +15977,43 @@ fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) pro
1589715977 }) else undefined;
1589815978 errdefer if (options.progress_node.index != .none) for (prog_pipe) |handle| windows.CloseHandle(handle);
1589915979
15980 defer for (options.inherit_dirs) |inherit_dir| switch (windows.ntdll.NtSetInformationObject(
15981 inherit_dir.handle,
15982 .HandleFlag,
15983 &windows.OBJECT.HANDLE_FLAG{ .INHERIT = false },
15984 @sizeOf(windows.OBJECT.HANDLE_FLAG),
15985 )) {
15986 .SUCCESS => {},
15987 else => |status| windows.unexpectedStatus(status) catch {},
15988 };
15989 for (options.inherit_dirs) |inherit_dir| switch (windows.ntdll.NtSetInformationObject(
15990 inherit_dir.handle,
15991 .HandleFlag,
15992 &windows.OBJECT.HANDLE_FLAG{ .INHERIT = true },
15993 @sizeOf(windows.OBJECT.HANDLE_FLAG),
15994 )) {
15995 .SUCCESS => {},
15996 else => |status| return windows.unexpectedStatus(status),
15997 };
15998 defer for (options.inherit_files) |inherit_file| switch (windows.ntdll.NtSetInformationObject(
15999 inherit_file.handle,
16000 .HandleFlag,
16001 &windows.OBJECT.HANDLE_FLAG{ .INHERIT = false },
16002 @sizeOf(windows.OBJECT.HANDLE_FLAG),
16003 )) {
16004 .SUCCESS => {},
16005 else => |status| windows.unexpectedStatus(status) catch {},
16006 };
16007 for (options.inherit_files) |inherit_file| switch (windows.ntdll.NtSetInformationObject(
16008 inherit_file.handle,
16009 .HandleFlag,
16010 &windows.OBJECT.HANDLE_FLAG{ .INHERIT = true },
16011 @sizeOf(windows.OBJECT.HANDLE_FLAG),
16012 )) {
16013 .SUCCESS => {},
16014 else => |status| return windows.unexpectedStatus(status),
16015 };
16016
1590016017 var siStartInfo: windows.STARTUPINFOW = .{
1590116018 .cb = @sizeOf(windows.STARTUPINFOW),
1590216019 .dwFlags = windows.STARTF_USESTDHANDLES,
......@@ -15974,32 +16091,6 @@ fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) pro
1597416091 };
1597516092 var piProcInfo: windows.PROCESS.INFORMATION = undefined;
1597616093
15977 var arena_allocator = std.heap.ArenaAllocator.init(t.allocator);
15978 defer arena_allocator.deinit();
15979 const arena = arena_allocator.allocator();
15980
15981 const cwd_w = cwd_w: {
15982 switch (options.cwd) {
15983 .inherit => break :cwd_w null,
15984 .dir => |cwd_dir| {
15985 var dir_path_buffer = try arena.alloc(u16, windows.PATH_MAX_WIDE + 1);
15986 const dir_path = try GetFinalPathNameByHandle(
15987 cwd_dir.handle,
15988 .{},
15989 dir_path_buffer[0..windows.PATH_MAX_WIDE],
15990 );
15991 dir_path_buffer[dir_path.len] = 0;
15992 // Shrink the allocation down to just the path buffer + sentinel
15993 dir_path_buffer = try arena.realloc(dir_path_buffer, dir_path.len + 1);
15994 break :cwd_w dir_path_buffer[0..dir_path.len :0];
15995 },
15996 .path => |cwd| {
15997 break :cwd_w try std.unicode.wtf8ToWtf16LeAllocZ(arena, cwd);
15998 },
15999 }
16000 };
16001 const cwd_w_ptr = if (cwd_w) |cwd| cwd.ptr else null;
16002
1600316094 const env_block = env_block: {
1600416095 const prog_handle = if (options.progress_node.index != .none)
1600516096 prog_pipe[1]
......@@ -16013,45 +16104,6 @@ fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) pro
1601316104 });
1601416105 };
1601516106
16016 const app_name_wtf8 = options.argv[0];
16017 const app_name_is_absolute = Dir.path.isAbsolute(app_name_wtf8);
16018
16019 // The cwd provided by options is in effect when choosing the executable
16020 // path to match POSIX semantics.
16021 const cwd_path_w = x: {
16022 // If the app name is absolute, then we need to use its dirname as the cwd
16023 if (app_name_is_absolute) {
16024 const dir = Dir.path.dirname(app_name_wtf8).?;
16025 break :x try std.unicode.wtf8ToWtf16LeAllocZ(arena, dir);
16026 } else if (cwd_w) |cwd| {
16027 break :x cwd;
16028 } else {
16029 break :x &[_:0]u16{}; // empty for cwd
16030 }
16031 };
16032
16033 // If the app name has more than just a filename, then we need to separate
16034 // that into the basename and dirname and use the dirname as an addition to
16035 // the cwd path. This is because NtQueryDirectoryFile cannot accept
16036 // FileName params with path separators.
16037 const app_basename_wtf8 = Dir.path.basename(app_name_wtf8);
16038 // If the app name is absolute, then the cwd will already have the app's dirname in it,
16039 // so only populate app_dirname if app name is a relative path with > 0 path separators.
16040 const maybe_app_dirname_wtf8 = if (!app_name_is_absolute) Dir.path.dirname(app_name_wtf8) else null;
16041 const app_dirname_w: ?[:0]u16 = x: {
16042 if (maybe_app_dirname_wtf8) |app_dirname_wtf8| {
16043 break :x try std.unicode.wtf8ToWtf16LeAllocZ(arena, app_dirname_wtf8);
16044 }
16045 break :x null;
16046 };
16047 const app_name_w = try std.unicode.wtf8ToWtf16LeAllocZ(arena, app_basename_wtf8);
16048
16049 const flags: windows.CreateProcessFlags = .{
16050 .create_suspended = options.start_suspended,
16051 .create_unicode_environment = true,
16052 .create_no_window = options.create_no_window,
16053 };
16054
1605516107 run: {
1605616108 // We have to scan each time because the PEB environment pointer is not stable.
1605716109 const env_strings: WindowsEnvironStrings = .scan();
......@@ -16072,10 +16124,8 @@ fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) pro
1607216124
1607316125 var dir_buf: std.ArrayList(u16) = .empty;
1607416126
16075 if (cwd_path_w.len > 0) {
16076 try dir_buf.appendSlice(arena, cwd_path_w);
16077 }
16078 if (app_dirname_w) |app_dir| {
16127 if (app_path_w) |app_path| try dir_buf.appendSlice(arena, app_path);
16128 if (app_dir_w) |app_dir| {
1607916129 if (dir_buf.items.len > 0) try dir_buf.append(arena, Dir.path.sep);
1608016130 try dir_buf.appendSlice(arena, app_dir);
1608116131 }
......@@ -16100,15 +16150,18 @@ fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) pro
1610016150 else => |e| return e,
1610116151 };
1610216152
16103 // If the app name had path separators, that disallows PATH searching,
16104 // and there's no need to search the PATH if the app name is absolute.
1610516153 // We still search the path if the cwd is absolute because of the
1610616154 // "cwd provided by options is in effect when choosing the executable path
1610716155 // to match posix semantics" behavior--we don't want to skip searching
1610816156 // the PATH just because we were trying to set the cwd of the child process.
16109 if (app_dirname_w != null or app_name_is_absolute) {
16110 return original_err;
16111 }
16157 if (switch (options.exe) {
16158 // If the app name had path separators, that disallows PATH searching.
16159 .detect => app_dir_w != null,
16160 // There's no need to search the PATH if the app name is absolute.
16161 .search => app_path_w == null,
16162 // PATH searching is disallowed.
16163 .path, .file, .explicit => true,
16164 }) return original_err;
1611216165
1611316166 var it = std.mem.tokenizeScalar(u16, PATH, ';');
1611416167 while (it.next()) |search_path| {
......@@ -16186,11 +16239,9 @@ fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) pro
1618616239 };
1618716240}
1618816241
16189fn inheritFile() windows.HANDLE {}
16190
1619116242fn getCngDevice(t: *Threaded) Io.RandomSecureError!windows.HANDLE {
1619216243 {
16193 mutexLock(&t.mutex);
16244 try mutexLock(&t.mutex);
1619416245 defer mutexUnlock(&t.mutex);
1619516246 if (t.random_file.handle) |handle| return handle;
1619616247 }
......@@ -16213,7 +16264,7 @@ fn getCngDevice(t: *Threaded) Io.RandomSecureError!windows.HANDLE {
1621316264 )) {
1621416265 .SUCCESS => {
1621516266 syscall.finish();
16216 mutexLock(&t.mutex); // Another thread might have won the race.
16267 mutexLockUncancelable(&t.mutex); // Another thread might have won the race.
1621716268 defer mutexUnlock(&t.mutex);
1621816269 if (t.random_file.handle) |prev_handle| {
1621916270 windows.CloseHandle(fresh_handle);
......@@ -16234,7 +16285,7 @@ fn getCngDevice(t: *Threaded) Io.RandomSecureError!windows.HANDLE {
1623416285
1623516286fn getNulDevice(t: *Threaded) !windows.HANDLE {
1623616287 {
16237 mutexLock(&t.mutex);
16288 try mutexLock(&t.mutex);
1623816289 defer mutexUnlock(&t.mutex);
1623916290 if (t.null_file.handle) |handle| return handle;
1624016291 }
......@@ -16260,7 +16311,7 @@ fn getNulDevice(t: *Threaded) !windows.HANDLE {
1626016311 )) {
1626116312 .SUCCESS => {
1626216313 syscall.finish();
16263 mutexLock(&t.mutex); // Another thread might have won the race.
16314 mutexLockUncancelable(&t.mutex); // Another thread might have won the race.
1626416315 defer mutexUnlock(&t.mutex);
1626516316 if (t.null_file.handle) |prev_handle| {
1626616317 windows.CloseHandle(fresh_handle);
......@@ -16293,7 +16344,7 @@ fn getNulDevice(t: *Threaded) !windows.HANDLE {
1629316344
1629416345fn getNamedPipeDevice(t: *Threaded) !windows.HANDLE {
1629516346 {
16296 mutexLock(&t.mutex);
16347 try mutexLock(&t.mutex);
1629716348 defer mutexUnlock(&t.mutex);
1629816349 if (t.pipe_file.handle) |handle| return handle;
1629916350 }
......@@ -16315,7 +16366,7 @@ fn getNamedPipeDevice(t: *Threaded) !windows.HANDLE {
1631516366 )) {
1631616367 .SUCCESS => {
1631716368 syscall.finish();
16318 mutexLock(&t.mutex); // Another thread might have won the race.
16369 mutexLockUncancelable(&t.mutex); // Another thread might have won the race.
1631916370 defer mutexUnlock(&t.mutex);
1632016371 if (t.pipe_file.handle) |prev_handle| {
1632116372 windows.CloseHandle(fresh_handle);
......@@ -16600,7 +16651,15 @@ fn windowsCreateProcessPathExt(
1660016651 else
1660116652 full_app_name;
1660216653
16603 if (windowsCreateProcess(app_name_w.ptr, cmd_line_w.ptr, env_block, cwd_ptr, flags, lpStartupInfo, lpProcessInformation)) |_| {
16654 if (windowsCreateProcess(
16655 app_name_w.ptr,
16656 cmd_line_w.ptr,
16657 env_block,
16658 cwd_ptr,
16659 flags,
16660 lpStartupInfo,
16661 lpProcessInformation,
16662 )) |_| {
1660416663 return;
1660516664 } else |err| switch (err) {
1660616665 error.FileNotFound => continue,
......@@ -17716,6 +17775,15 @@ fn inheritParentHandle(handle: posix.fd_t) Io.InheritParentHandleError!void {
1771617775 .visionos,
1771717776 .watchos,
1771817777 => return setCloexec(handle),
17778 .windows => switch (windows.ntdll.NtSetInformationObject(
17779 handle,
17780 .HandleFlag,
17781 &windows.OBJECT.HANDLE_FLAG{ .INHERIT = false },
17782 @sizeOf(windows.OBJECT.HANDLE_FLAG),
17783 )) {
17784 .SUCCESS => return,
17785 else => |status| return windows.unexpectedStatus(status),
17786 },
1771917787 else => return error.UnsupportedOperation,
1772017788 }
1772117789}
......@@ -17756,7 +17824,7 @@ fn random(userdata: ?*anyopaque, buffer: []u8) void {
1775617824}
1775717825
1775817826fn randomMainThread(t: *Threaded, buffer: []u8) void {
17759 mutexLock(&t.mutex);
17827 mutexLockUncancelable(&t.mutex);
1776017828 defer mutexUnlock(&t.mutex);
1776117829
1776217830 if (!t.csprng.isInitialized()) {
......@@ -17764,7 +17832,7 @@ fn randomMainThread(t: *Threaded, buffer: []u8) void {
1776417832 var seed: [Csprng.seed_len]u8 = undefined;
1776517833 {
1776617834 mutexUnlock(&t.mutex);
17767 defer mutexLock(&t.mutex);
17835 defer mutexLockUncancelable(&t.mutex);
1776817836
1776917837 const prev = swapCancelProtection(t, .blocked);
1777017838 defer _ = swapCancelProtection(t, prev);
......@@ -17951,7 +18019,7 @@ fn randomSecure(userdata: ?*anyopaque, buffer: []u8) Io.RandomSecureError!void {
1795118019
1795218020fn getRandomFd(t: *Threaded) Io.RandomSecureError!posix.fd_t {
1795318021 {
17954 mutexLock(&t.mutex);
18022 try mutexLock(&t.mutex);
1795518023 defer mutexUnlock(&t.mutex);
1795618024
1795718025 if (t.random_file.fd == -2) return error.EntropyUnavailable;
......@@ -17992,7 +18060,7 @@ fn getRandomFd(t: *Threaded) Io.RandomSecureError!posix.fd_t {
1799218060 .SUCCESS => {
1799318061 syscall.finish();
1799418062 if (!statx.mask.TYPE) return error.EntropyUnavailable;
17995 mutexLock(&t.mutex); // Another thread might have won the race.
18063 mutexLockUncancelable(&t.mutex); // Another thread might have won the race.
1799618064 defer mutexUnlock(&t.mutex);
1799718065 if (t.random_file.fd >= 0) {
1799818066 closeFd(fd);
......@@ -18020,7 +18088,7 @@ fn getRandomFd(t: *Threaded) Io.RandomSecureError!posix.fd_t {
1802018088 switch (posix.errno(fstat_sym(fd, &stat))) {
1802118089 .SUCCESS => {
1802218090 syscall.finish();
18023 mutexLock(&t.mutex); // Another thread might have won the race.
18091 mutexLockUncancelable(&t.mutex); // Another thread might have won the race.
1802418092 defer mutexUnlock(&t.mutex);
1802518093 if (t.random_file.fd >= 0) {
1802618094 closeFd(fd);
......@@ -19576,7 +19644,7 @@ fn condWait(cond: *Io.Condition, mutex: *Io.Mutex) void {
1957619644 }
1957719645
1957819646 mutexUnlock(mutex);
19579 defer mutexLock(mutex);
19647 defer mutexLockUncancelable(mutex);
1958019648
1958119649 while (true) {
1958219650 Thread.futexWaitUncancelable(&cond.epoch.raw, epoch, null);
......@@ -19596,8 +19664,27 @@ fn condWait(cond: *Io.Condition, mutex: *Io.Mutex) void {
1959619664 }
1959719665}
1959819666
19667/// Same as `Io.Mutex.lock` but avoids the VTable.
19668pub fn mutexLock(m: *Io.Mutex) Io.Cancelable!void {
19669 const initial_state = m.state.cmpxchgStrong(
19670 .unlocked,
19671 .locked_once,
19672 .acquire,
19673 .monotonic,
19674 ) orelse {
19675 @branchHint(.likely);
19676 return;
19677 };
19678 if (initial_state == .contended) {
19679 try Thread.futexWait(@ptrCast(&m.state.raw), @backingInt(Io.Mutex.State.contended), null);
19680 }
19681 while (m.state.swap(.contended, .acquire) != .unlocked) {
19682 try Thread.futexWait(@ptrCast(&m.state.raw), @backingInt(Io.Mutex.State.contended), null);
19683 }
19684}
19685
1959919686/// Same as `Io.Mutex.lockUncancelable` but avoids the VTable.
19600pub fn mutexLock(m: *Io.Mutex) void {
19687pub fn mutexLockUncancelable(m: *Io.Mutex) void {
1960119688 const initial_state = m.state.cmpxchgStrong(
1960219689 .unlocked,
1960319690 .locked_once,
lib/std/fs/path.zig+32-32
......@@ -1438,7 +1438,7 @@ pub fn dirnamePosix(path: []const u8) ?[]const u8 {
14381438}
14391439
14401440fn dirnameInner(comptime path_type: PathType, path: []const u8) ?[]const u8 {
1441 var it = ComponentIterator(path_type, u8).init(path);
1441 var it: ComponentIterator(path_type, u8) = .init(path);
14421442 _ = it.last() orelse return null;
14431443 const up = it.previous() orelse return it.root();
14441444 return up.path;
......@@ -1539,7 +1539,7 @@ pub fn basenameWindows(path: []const u8) []const u8 {
15391539}
15401540
15411541fn basenameInner(comptime path_type: PathType, path: []const u8) []const u8 {
1542 var it = ComponentIterator(path_type, u8).init(path);
1542 var it: ComponentIterator(path_type, u8) = .init(path);
15431543 const last = it.last() orelse return &[_]u8{};
15441544 return last.name;
15451545}
......@@ -2338,14 +2338,14 @@ pub const NativeComponentIterator = ComponentIterator(switch (native_os) {
23382338}, u8);
23392339
23402340pub fn componentIterator(path: []const u8) NativeComponentIterator {
2341 return NativeComponentIterator.init(path);
2341 return .init(path);
23422342}
23432343
23442344test "ComponentIterator posix" {
23452345 const PosixComponentIterator = ComponentIterator(.posix, u8);
23462346 {
23472347 const path = "a/b/c/";
2348 var it = PosixComponentIterator.init(path);
2348 var it: PosixComponentIterator = .init(path);
23492349 try std.testing.expectEqual(0, it.root_len);
23502350 try std.testing.expectEqual(0, it.root_end_index);
23512351 try std.testing.expect(null == it.root());
......@@ -2393,7 +2393,7 @@ test "ComponentIterator posix" {
23932393
23942394 {
23952395 const path = "/a/b/c/";
2396 var it = PosixComponentIterator.init(path);
2396 var it: PosixComponentIterator = .init(path);
23972397 try std.testing.expectEqual(1, it.root_len);
23982398 try std.testing.expectEqual(1, it.root_end_index);
23992399 try std.testing.expectEqualStrings("/", it.root().?);
......@@ -2441,7 +2441,7 @@ test "ComponentIterator posix" {
24412441
24422442 {
24432443 const path = "////a///b///c////";
2444 var it = PosixComponentIterator.init(path);
2444 var it: PosixComponentIterator = .init(path);
24452445 try std.testing.expectEqual(1, it.root_len);
24462446 try std.testing.expectEqual(4, it.root_end_index);
24472447 try std.testing.expectEqualStrings("/", it.root().?);
......@@ -2489,7 +2489,7 @@ test "ComponentIterator posix" {
24892489
24902490 {
24912491 const path = "/";
2492 var it = PosixComponentIterator.init(path);
2492 var it: PosixComponentIterator = .init(path);
24932493 try std.testing.expectEqual(1, it.root_len);
24942494 try std.testing.expectEqual(1, it.root_end_index);
24952495 try std.testing.expectEqualStrings("/", it.root().?);
......@@ -2507,7 +2507,7 @@ test "ComponentIterator posix" {
25072507
25082508 {
25092509 const path = "";
2510 var it = PosixComponentIterator.init(path);
2510 var it: PosixComponentIterator = .init(path);
25112511 try std.testing.expectEqual(0, it.root_len);
25122512 try std.testing.expectEqual(0, it.root_end_index);
25132513 try std.testing.expect(null == it.root());
......@@ -2528,7 +2528,7 @@ test "ComponentIterator windows" {
25282528 const WindowsComponentIterator = ComponentIterator(.windows, u8);
25292529 {
25302530 const path = "a/b\\c//";
2531 var it = WindowsComponentIterator.init(path);
2531 var it: WindowsComponentIterator = .init(path);
25322532 try std.testing.expectEqual(0, it.root_len);
25332533 try std.testing.expectEqual(0, it.root_end_index);
25342534 try std.testing.expect(null == it.root());
......@@ -2576,7 +2576,7 @@ test "ComponentIterator windows" {
25762576
25772577 {
25782578 const path = "C:\\a/b/c/";
2579 var it = WindowsComponentIterator.init(path);
2579 var it: WindowsComponentIterator = .init(path);
25802580 try std.testing.expectEqual(3, it.root_len);
25812581 try std.testing.expectEqual(3, it.root_end_index);
25822582 try std.testing.expectEqualStrings("C:\\", it.root().?);
......@@ -2614,7 +2614,7 @@ test "ComponentIterator windows" {
26142614
26152615 {
26162616 const path = "C:\\\\//a/\\/\\b///c////";
2617 var it = WindowsComponentIterator.init(path);
2617 var it: WindowsComponentIterator = .init(path);
26182618 try std.testing.expectEqual(3, it.root_len);
26192619 try std.testing.expectEqual(6, it.root_end_index);
26202620 try std.testing.expectEqualStrings("C:\\", it.root().?);
......@@ -2652,7 +2652,7 @@ test "ComponentIterator windows" {
26522652
26532653 {
26542654 const path = "/";
2655 var it = WindowsComponentIterator.init(path);
2655 var it: WindowsComponentIterator = .init(path);
26562656 try std.testing.expectEqual(1, it.root_len);
26572657 try std.testing.expectEqual(1, it.root_end_index);
26582658 try std.testing.expectEqualStrings("/", it.root().?);
......@@ -2670,7 +2670,7 @@ test "ComponentIterator windows" {
26702670
26712671 {
26722672 const path = "";
2673 var it = WindowsComponentIterator.init(path);
2673 var it: WindowsComponentIterator = .init(path);
26742674 try std.testing.expectEqual(0, it.root_len);
26752675 try std.testing.expectEqual(0, it.root_end_index);
26762676 try std.testing.expect(null == it.root());
......@@ -2692,7 +2692,7 @@ test "ComponentIterator windows WTF-16" {
26922692 const L = std.unicode.utf8ToUtf16LeStringLiteral;
26932693
26942694 const path = L("C:\\a/b/c/");
2695 var it = WindowsComponentIterator.init(path);
2695 var it: WindowsComponentIterator = .init(path);
26962696 try std.testing.expectEqual(3, it.root_len);
26972697 try std.testing.expectEqual(3, it.root_end_index);
26982698 try std.testing.expectEqualSlices(u16, L("C:\\"), it.root().?);
......@@ -2731,59 +2731,59 @@ test "ComponentIterator windows WTF-16" {
27312731test "ComponentIterator roots" {
27322732 // UEFI
27332733 {
2734 var it = ComponentIterator(.uefi, u8).init("\\\\a");
2734 var it: ComponentIterator(.uefi, u8) = .init("\\\\a");
27352735 try std.testing.expectEqualStrings("\\", it.root().?);
27362736
2737 it = ComponentIterator(.uefi, u8).init("//a");
2737 it = .init("//a");
27382738 try std.testing.expect(null == it.root());
27392739 }
27402740 // POSIX
27412741 {
2742 var it = ComponentIterator(.posix, u8).init("//a");
2742 var it: ComponentIterator(.posix, u8) = .init("//a");
27432743 try std.testing.expectEqualStrings("/", it.root().?);
27442744
2745 it = ComponentIterator(.posix, u8).init("\\\\a");
2745 it = .init("\\\\a");
27462746 try std.testing.expect(null == it.root());
27472747 }
27482748 // Windows
27492749 {
27502750 // Drive relative
2751 var it = ComponentIterator(.windows, u8).init("C:a");
2751 var it: ComponentIterator(.windows, u8) = .init("C:a");
27522752 try std.testing.expectEqualStrings("C:", it.root().?);
27532753
27542754 // Drive absolute
2755 it = ComponentIterator(.windows, u8).init("C:/a");
2755 it = .init("C:/a");
27562756 try std.testing.expectEqualStrings("C:/", it.root().?);
2757 it = ComponentIterator(.windows, u8).init("C:\\a");
2757 it = .init("C:\\a");
27582758 try std.testing.expectEqualStrings("C:\\", it.root().?);
2759 it = ComponentIterator(.windows, u8).init("C:///a");
2759 it = .init("C:///a");
27602760 try std.testing.expectEqualStrings("C:/", it.root().?);
27612761
27622762 // Rooted
2763 it = ComponentIterator(.windows, u8).init("\\a");
2763 it = .init("\\a");
27642764 try std.testing.expectEqualStrings("\\", it.root().?);
2765 it = ComponentIterator(.windows, u8).init("/a");
2765 it = .init("/a");
27662766 try std.testing.expectEqualStrings("/", it.root().?);
27672767
27682768 // Root local device
2769 it = ComponentIterator(.windows, u8).init("\\\\.");
2769 it = .init("\\\\.");
27702770 try std.testing.expectEqualStrings("\\\\.", it.root().?);
2771 it = ComponentIterator(.windows, u8).init("//?");
2771 it = .init("//?");
27722772 try std.testing.expectEqualStrings("//?", it.root().?);
27732773
27742774 // UNC absolute
2775 it = ComponentIterator(.windows, u8).init("//");
2775 it = .init("//");
27762776 try std.testing.expectEqualStrings("//", it.root().?);
2777 it = ComponentIterator(.windows, u8).init("\\\\a");
2777 it = .init("\\\\a");
27782778 try std.testing.expectEqualStrings("\\\\a", it.root().?);
2779 it = ComponentIterator(.windows, u8).init("\\\\a\\b\\\\c");
2779 it = .init("\\\\a\\b\\\\c");
27802780 try std.testing.expectEqualStrings("\\\\a\\b\\", it.root().?);
2781 it = ComponentIterator(.windows, u8).init("//a");
2781 it = .init("//a");
27822782 try std.testing.expectEqualStrings("//a", it.root().?);
2783 it = ComponentIterator(.windows, u8).init("//a/b//c");
2783 it = .init("//a/b//c");
27842784 try std.testing.expectEqualStrings("//a/b/", it.root().?);
27852785 // Malformed UNC path with empty server name
2786 it = ComponentIterator(.windows, u8).init("\\\\\\a\\b\\c");
2786 it = .init("\\\\\\a\\b\\c");
27872787 try std.testing.expectEqualStrings("\\\\\\a\\", it.root().?);
27882788 }
27892789}
lib/std/heap/debug_allocator.zig+4-4
......@@ -718,7 +718,7 @@ pub fn DebugAllocator(comptime config: Config) type {
718718
719719 fn alloc(context: *anyopaque, len: usize, alignment: mem.Alignment, ret_addr: usize) ?[*]u8 {
720720 const self: *Self = @ptrCast(@alignCast(context));
721 if (have_mutex) std.Io.Threaded.mutexLock(&self.mutex);
721 if (have_mutex) std.Io.Threaded.mutexLockUncancelable(&self.mutex);
722722 defer if (have_mutex) std.Io.Threaded.mutexUnlock(&self.mutex);
723723
724724 if (config.enable_memory_limit) {
......@@ -831,7 +831,7 @@ pub fn DebugAllocator(comptime config: Config) type {
831831 return_address: usize,
832832 ) bool {
833833 const self: *Self = @ptrCast(@alignCast(context));
834 if (have_mutex) std.Io.Threaded.mutexLock(&self.mutex);
834 if (have_mutex) std.Io.Threaded.mutexLockUncancelable(&self.mutex);
835835 defer if (have_mutex) std.Io.Threaded.mutexUnlock(&self.mutex);
836836
837837 const size_class_index: usize = @max(@bitSizeOf(usize) - @clz(memory.len - 1), @backingInt(alignment));
......@@ -850,7 +850,7 @@ pub fn DebugAllocator(comptime config: Config) type {
850850 return_address: usize,
851851 ) ?[*]u8 {
852852 const self: *Self = @ptrCast(@alignCast(context));
853 if (have_mutex) std.Io.Threaded.mutexLock(&self.mutex);
853 if (have_mutex) std.Io.Threaded.mutexLockUncancelable(&self.mutex);
854854 defer if (have_mutex) std.Io.Threaded.mutexUnlock(&self.mutex);
855855
856856 const size_class_index: usize = @max(@bitSizeOf(usize) - @clz(memory.len - 1), @backingInt(alignment));
......@@ -868,7 +868,7 @@ pub fn DebugAllocator(comptime config: Config) type {
868868 return_address: usize,
869869 ) void {
870870 const self: *Self = @ptrCast(@alignCast(context));
871 if (have_mutex) std.Io.Threaded.mutexLock(&self.mutex);
871 if (have_mutex) std.Io.Threaded.mutexLockUncancelable(&self.mutex);
872872 defer if (have_mutex) std.Io.Threaded.mutexUnlock(&self.mutex);
873873
874874 const size_class_index: usize = @max(@bitSizeOf(usize) - @clz(old_memory.len - 1), @backingInt(alignment));
lib/std/os/windows.zig+14
......@@ -147,9 +147,23 @@ pub const OBJECT = struct {
147147 pub const Max: @typeInfo(@This()).@"enum".tag_type = @typeInfo(@This()).@"enum".field_names.len;
148148 };
149149
150 pub const BASIC_INFORMATION = extern struct {
151 Attributes: ATTRIBUTES.Flags,
152 GrantedAccess: ACCESS_MASK,
153 HandleCount: ULONG,
154 PointerCount: ULONG,
155 Reserved: [10]ULONG,
156 };
157
150158 pub const NAME_INFORMATION = extern struct {
151159 Name: UNICODE_STRING,
152160 };
161
162 pub const HANDLE_FLAG = packed struct(USHORT) {
163 INHERIT: bool = false,
164 PROTECT_FROM_CLOSE: bool = false,
165 Reserved1: u14 = 0,
166 };
153167};
154168
155169pub const FILE = struct {
lib/std/os/windows/ntdll.zig+7
......@@ -461,6 +461,13 @@ pub extern "ntdll" fn LdrUnregisterDllNotification(
461461 Cookie: LDR.DLL_NOTIFICATION.COOKIE,
462462) callconv(.winapi) NTSTATUS;
463463
464pub extern "ntdll" fn NtSetInformationObject(
465 Handle: HANDLE,
466 ObjectInformationClass: OBJECT.INFORMATION_CLASS,
467 ObjectInformation: *const anyopaque,
468 ObjectInformationLength: ULONG,
469) callconv(.winapi) NTSTATUS;
470
464471pub extern "ntdll" fn NtQueryAttributesFile(
465472 ObjectAttributes: *const OBJECT.ATTRIBUTES,
466473 FileAttributes: *FILE.BASIC_INFORMATION,