diff options
Diffstat (limited to 'lib/std')
| -rw-r--r-- | lib/std/Thread.zig | 274 | ||||
| -rw-r--r-- | lib/std/c/darwin.zig | 2 | ||||
| -rw-r--r-- | lib/std/c/freebsd.zig | 2 | ||||
| -rw-r--r-- | lib/std/c/linux.zig | 3 | ||||
| -rw-r--r-- | lib/std/c/netbsd.zig | 3 | ||||
| -rw-r--r-- | lib/std/c/openbsd.zig | 3 | ||||
| -rw-r--r-- | lib/std/os/windows.zig | 19 | ||||
| -rw-r--r-- | lib/std/os/windows/kernel32.zig | 5 |
8 files changed, 311 insertions, 0 deletions
diff --git a/lib/std/Thread.zig b/lib/std/Thread.zig index 58a409c64e..2584b38072 100644 --- a/lib/std/Thread.zig +++ b/lib/std/Thread.zig @@ -38,6 +38,192 @@ else impl: Impl, +pub const max_name_len = switch (std.Target.current.os.tag) { + .linux => 15, + .windows => 31, + .macos, .ios, .watchos, .tvos => 63, + .netbsd => 31, + .freebsd => 15, + .openbsd => 31, + else => 0, +}; + +pub const SetNameError = error{ + NameTooLong, + Unsupported, + Unexpected, +} || os.PrctlError || os.WriteError || std.fs.File.OpenError || std.fmt.BufPrintError; + +pub fn setName(self: Thread, name: []const u8) SetNameError!void { + if (name.len > max_name_len) return error.NameTooLong; + + const name_with_terminator = blk: { + var name_buf: [max_name_len:0]u8 = undefined; + std.mem.copy(u8, &name_buf, name); + name_buf[name.len] = 0; + break :blk name_buf[0..name.len :0]; + }; + + switch (std.Target.current.os.tag) { + .linux => if (use_pthreads) { + const err = std.c.pthread_setname_np(self.getHandle(), name_with_terminator.ptr); + return switch (err) { + 0 => {}, + os.ERANGE => unreachable, + else => return os.unexpectedErrno(err), + }; + } else if (use_pthreads and self.getHandle() == std.c.pthread_self()) { + const err = try os.prctl(.SET_NAME, .{@ptrToInt(name_with_terminator.ptr)}); + return switch (err) { + 0 => {}, + else => return os.unexpectedErrno(err), + }; + } else { + var buf: [32]u8 = undefined; + const path = try std.fmt.bufPrint(&buf, "/proc/self/task/{d}/comm", .{self.getHandle()}); + + const file = try std.fs.cwd().openFile(path, .{ .write = true }); + defer file.close(); + + try file.writer().writeAll(name); + }, + .windows => if (std.Target.current.os.isAtLeast(.windows, .win10_rs1)) |res| { + // SetThreadDescription is only available since version 1607, which is 10.0.14393.795 + // See https://en.wikipedia.org/wiki/Microsoft_Windows_SDK + if (!res) { + return error.Unsupported; + } + + var name_buf_w: [max_name_len:0]u16 = undefined; + const length = try std.unicode.utf8ToUtf16Le(&name_buf_w, name); + name_buf_w[length] = 0; + + try os.windows.SetThreadDescription( + self.getHandle(), + @ptrCast(os.windows.LPWSTR, &name_buf_w), + ); + } else { + return error.Unsupported; + }, + .macos, .ios, .watchos, .tvos => if (use_pthreads) { + // There doesn't seem to be a way to set the name for an arbitrary thread, only the current one. + if (self.getHandle() != std.c.pthread_self()) return error.Unsupported; + + const err = std.c.pthread_setname_np(name_with_terminator.ptr); + return switch (err) { + 0 => {}, + else => return os.unexpectedErrno(err), + }; + }, + .netbsd => if (use_pthreads) { + const err = std.c.pthread_setname_np(self.getHandle(), name_with_terminator.ptr, null); + return switch (err) { + 0 => {}, + os.EINVAL => unreachable, + os.ESRCH => unreachable, + os.ENOMEM => unreachable, + else => return os.unexpectedErrno(err), + }; + }, + .freebsd, .openbsd => if (use_pthreads) { + // Use pthread_set_name_np for FreeBSD because pthread_setname_np is FreeBSD 12.2+ only. + // TODO maybe revisit this if depending on FreeBSD 12.2+ is acceptable because pthread_setname_np can return an error. + + std.c.pthread_set_name_np(self.getHandle(), name_with_terminator.ptr); + }, + else => return error.Unsupported, + } +} + +pub const GetNameError = error{ + // For Windows, the name is converted from UTF16 to UTF8 + CodepointTooLarge, + Utf8CannotEncodeSurrogateHalf, + DanglingSurrogateHalf, + ExpectedSecondSurrogateHalf, + UnexpectedSecondSurrogateHalf, + + Unsupported, + Unexpected, +} || os.PrctlError || os.ReadError || std.fs.File.OpenError || std.fmt.BufPrintError; + +pub fn getName(self: Thread, buffer_ptr: *[max_name_len:0]u8) GetNameError!?[]const u8 { + buffer_ptr[max_name_len] = 0; + var buffer = std.mem.span(buffer_ptr); + + switch (std.Target.current.os.tag) { + .linux => if (use_pthreads and comptime std.Target.current.abi.isGnu()) { + const err = std.c.pthread_getname_np(self.getHandle(), buffer.ptr, max_name_len + 1); + return switch (err) { + 0 => std.mem.sliceTo(buffer, 0), + os.ERANGE => unreachable, + else => return os.unexpectedErrno(err), + }; + } else if (use_pthreads and self.getHandle() == std.c.pthread_self()) { + const err = try os.prctl(.GET_NAME, .{@ptrToInt(buffer.ptr)}); + return switch (err) { + 0 => std.mem.sliceTo(buffer, 0), + else => return os.unexpectedErrno(err), + }; + } else if (!use_pthreads) { + var buf: [32]u8 = undefined; + const path = try std.fmt.bufPrint(&buf, "/proc/self/task/{d}/comm", .{self.getHandle()}); + + const file = try std.fs.cwd().openFile(path, .{}); + defer file.close(); + + const data_len = try file.reader().readAll(buffer_ptr[0 .. max_name_len + 1]); + + return if (data_len >= 1) buffer[0 .. data_len - 1] else null; + } else { + // musl doesn't provide pthread_getname_np and there's no way to retrieve the thread id of an arbitrary thread. + return error.Unsupported; + }, + .windows => if (std.Target.current.os.isAtLeast(.windows, .win10_rs1)) |res| { + // GetThreadDescription is only available since version 1607, which is 10.0.14393.795 + // See https://en.wikipedia.org/wiki/Microsoft_Windows_SDK + if (!res) { + return error.Unsupported; + } + + var name_w: os.windows.LPWSTR = undefined; + try os.windows.GetThreadDescription(self.getHandle(), &name_w); + defer os.windows.LocalFree(name_w); + + const data_len = try std.unicode.utf16leToUtf8(buffer, std.mem.sliceTo(name_w, 0)); + + return if (data_len >= 1) buffer[0..data_len] else null; + } else { + return error.Unsupported; + }, + .macos, .ios, .watchos, .tvos => if (use_pthreads) { + const err = std.c.pthread_getname_np(self.getHandle(), buffer.ptr, max_name_len + 1); + return switch (err) { + 0 => std.mem.sliceTo(buffer, 0), + os.ESRCH => unreachable, + else => return os.unexpectedErrno(err), + }; + }, + .netbsd => if (use_pthreads) { + const err = std.c.pthread_getname_np(self.getHandle(), buffer.ptr, max_name_len + 1); + return switch (err) { + 0 => std.mem.sliceTo(buffer, 0), + os.EINVAL => unreachable, + os.ESRCH => unreachable, + else => return os.unexpectedErrno(err), + }; + }, + .freebsd, .openbsd => if (use_pthreads) { + // Use pthread_get_name_np for FreeBSD because pthread_getname_np is FreeBSD 12.2+ only. + // TODO maybe revisit this if depending on FreeBSD 12.2+ is acceptable because pthread_getname_np can return an error. + + std.c.pthread_get_name_np(self.getHandle(), buffer.ptr, max_name_len + 1); + return std.mem.sliceTo(buffer, 0); + }, + else => return error.Unsupported, + } +} + /// Represents a unique ID per thread. pub const Id = u64; @@ -779,6 +965,94 @@ const LinuxThreadImpl = struct { } }; +fn testThreadName(thread: *Thread) !void { + const testCases = &[_][]const u8{ + "mythread", + "b" ** max_name_len, + }; + + inline for (testCases) |tc| { + try thread.setName(tc); + + var name_buffer: [max_name_len:0]u8 = undefined; + + const name = try thread.getName(&name_buffer); + if (name) |value| { + try std.testing.expectEqual(tc.len, value.len); + try std.testing.expectEqualStrings(tc, value); + } + } +} + +test "setName, getName" { + if (std.builtin.single_threaded) return error.SkipZigTest; + + const Context = struct { + start_wait_event: ResetEvent = undefined, + test_done_event: ResetEvent = undefined, + + done: std.atomic.Atomic(bool) = std.atomic.Atomic(bool).init(false), + thread: Thread = undefined, + + fn init(self: *@This()) !void { + try self.start_wait_event.init(); + try self.test_done_event.init(); + } + + pub fn run(ctx: *@This()) !void { + // Wait for the main thread to have set the thread field in the context. + ctx.start_wait_event.wait(); + + switch (std.Target.current.os.tag) { + .windows => testThreadName(&ctx.thread) catch |err| switch (err) { + error.Unsupported => return error.SkipZigTest, + else => return err, + }, + else => try testThreadName(&ctx.thread), + } + + // Signal our test is done + ctx.test_done_event.set(); + + while (!ctx.done.load(.SeqCst)) { + std.time.sleep(5 * std.time.ns_per_ms); + } + } + }; + + var context = Context{}; + try context.init(); + + var thread = try spawn(.{}, Context.run, .{&context}); + context.thread = thread; + context.start_wait_event.set(); + context.test_done_event.wait(); + + switch (std.Target.current.os.tag) { + .macos, .ios, .watchos, .tvos => { + const res = thread.setName("foobar"); + try std.testing.expectError(error.Unsupported, res); + }, + .windows => testThreadName(&thread) catch |err| switch (err) { + error.Unsupported => return error.SkipZigTest, + else => return err, + }, + else => |tag| if (tag == .linux and use_pthreads and comptime std.Target.current.abi.isMusl()) { + try thread.setName("foobar"); + + var name_buffer: [max_name_len:0]u8 = undefined; + const res = thread.getName(&name_buffer); + + try std.testing.expectError(error.Unsupported, res); + } else { + try testThreadName(&thread); + }, + } + + context.done.store(true, .SeqCst); + thread.join(); +} + test "std.Thread" { // Doesn't use testing.refAllDecls() since that would pull in the compileError spinLoopHint. _ = AutoResetEvent; diff --git a/lib/std/c/darwin.zig b/lib/std/c/darwin.zig index ca18b5130a..e216afd643 100644 --- a/lib/std/c/darwin.zig +++ b/lib/std/c/darwin.zig @@ -193,6 +193,8 @@ pub const pthread_attr_t = extern struct { const pthread_t = std.c.pthread_t; pub extern "c" fn pthread_threadid_np(thread: ?pthread_t, thread_id: *u64) c_int; +pub extern "c" fn pthread_setname_np(name: [*:0]const u8) c_int; +pub extern "c" fn pthread_getname_np(thread: std.c.pthread_t, name: [*:0]u8, len: usize) c_int; pub extern "c" fn arc4random_buf(buf: [*]u8, len: usize) void; diff --git a/lib/std/c/freebsd.zig b/lib/std/c/freebsd.zig index 3e4b522685..eb449165d3 100644 --- a/lib/std/c/freebsd.zig +++ b/lib/std/c/freebsd.zig @@ -14,6 +14,8 @@ pub extern "c" fn sigaltstack(ss: ?*stack_t, old_ss: ?*stack_t) c_int; pub extern "c" fn getrandom(buf_ptr: [*]u8, buf_len: usize, flags: c_uint) isize; pub extern "c" fn pthread_getthreadid_np() c_int; +pub extern "c" fn pthread_set_name_np(thread: std.c.pthread_t, name: [*:0]const u8) void; +pub extern "c" fn pthread_get_name_np(thread: std.c.pthread_t, name: [*:0]u8, len: usize) void; pub extern "c" fn pipe2(fds: *[2]fd_t, flags: u32) c_int; pub extern "c" fn posix_memalign(memptr: *?*c_void, alignment: usize, size: usize) c_int; diff --git a/lib/std/c/linux.zig b/lib/std/c/linux.zig index 6e897c3098..694af28904 100644 --- a/lib/std/c/linux.zig +++ b/lib/std/c/linux.zig @@ -186,6 +186,9 @@ const __SIZEOF_PTHREAD_MUTEX_T = if (os_tag == .fuchsia) 40 else switch (abi) { }; const __SIZEOF_SEM_T = 4 * @sizeOf(usize); +pub extern "c" fn pthread_setname_np(thread: std.c.pthread_t, name: [*:0]const u8) c_int; +pub extern "c" fn pthread_getname_np(thread: std.c.pthread_t, name: [*:0]u8, len: usize) c_int; + pub const RTLD_LAZY = 1; pub const RTLD_NOW = 2; pub const RTLD_NOLOAD = 4; diff --git a/lib/std/c/netbsd.zig b/lib/std/c/netbsd.zig index 8e6d556fbc..32b7cc69e3 100644 --- a/lib/std/c/netbsd.zig +++ b/lib/std/c/netbsd.zig @@ -94,3 +94,6 @@ pub const pthread_attr_t = extern struct { }; pub const sem_t = ?*opaque {}; + +pub extern "c" fn pthread_setname_np(thread: std.c.pthread_t, name: [*:0]const u8, arg: ?*c_void) c_int; +pub extern "c" fn pthread_getname_np(thread: std.c.pthread_t, name: [*:0]u8, len: usize) c_int; diff --git a/lib/std/c/openbsd.zig b/lib/std/c/openbsd.zig index cac3df867d..15820fb0a9 100644 --- a/lib/std/c/openbsd.zig +++ b/lib/std/c/openbsd.zig @@ -45,3 +45,6 @@ pub extern "c" fn posix_memalign(memptr: *?*c_void, alignment: usize, size: usiz pub extern "c" fn pledge(promises: ?[*:0]const u8, execpromises: ?[*:0]const u8) c_int; pub extern "c" fn unveil(path: ?[*:0]const u8, permissions: ?[*:0]const u8) c_int; + +pub extern "c" fn pthread_set_name_np(thread: std.c.pthread_t, name: [*:0]const u8) void; +pub extern "c" fn pthread_get_name_np(thread: std.c.pthread_t, name: [*:0]u8, len: usize) void; diff --git a/lib/std/os/windows.zig b/lib/std/os/windows.zig index cbd4ce065b..04ce433758 100644 --- a/lib/std/os/windows.zig +++ b/lib/std/os/windows.zig @@ -1631,6 +1631,10 @@ pub fn HeapDestroy(hHeap: HANDLE) void { assert(kernel32.HeapDestroy(hHeap) != 0); } +pub fn LocalFree(hMem: HLOCAL) void { + assert(kernel32.LocalFree(hMem) == null); +} + pub const GetFileInformationByHandleError = error{Unexpected}; pub fn GetFileInformationByHandle( @@ -2011,6 +2015,21 @@ pub fn unexpectedStatus(status: NTSTATUS) std.os.UnexpectedError { return error.Unexpected; } +pub fn SetThreadDescription(hThread: HANDLE, lpThreadDescription: LPCWSTR) !void { + if (kernel32.SetThreadDescription(hThread, lpThreadDescription) == 0) { + switch (kernel32.GetLastError()) { + else => |err| return unexpectedError(err), + } + } +} +pub fn GetThreadDescription(hThread: HANDLE, ppszThreadDescription: *LPWSTR) !void { + if (kernel32.GetThreadDescription(hThread, ppszThreadDescription) == 0) { + switch (kernel32.GetLastError()) { + else => |err| return unexpectedError(err), + } + } +} + test "" { if (builtin.os.tag == .windows) { _ = @import("windows/test.zig"); diff --git a/lib/std/os/windows/kernel32.zig b/lib/std/os/windows/kernel32.zig index 971273ef3a..a04314324d 100644 --- a/lib/std/os/windows/kernel32.zig +++ b/lib/std/os/windows/kernel32.zig @@ -192,6 +192,8 @@ pub extern "kernel32" fn HeapValidate(hHeap: HANDLE, dwFlags: DWORD, lpMem: ?*co pub extern "kernel32" fn VirtualAlloc(lpAddress: ?LPVOID, dwSize: SIZE_T, flAllocationType: DWORD, flProtect: DWORD) callconv(WINAPI) ?LPVOID; pub extern "kernel32" fn VirtualFree(lpAddress: ?LPVOID, dwSize: SIZE_T, dwFreeType: DWORD) callconv(WINAPI) BOOL; +pub extern "kernel32" fn LocalFree(hMem: HLOCAL) callconv(WINAPI) ?HLOCAL; + pub extern "kernel32" fn MoveFileExW( lpExistingFileName: [*:0]const u16, lpNewFileName: [*:0]const u16, @@ -342,3 +344,6 @@ pub extern "kernel32" fn SleepConditionVariableSRW( pub extern "kernel32" fn TryAcquireSRWLockExclusive(s: *SRWLOCK) callconv(WINAPI) BOOLEAN; pub extern "kernel32" fn AcquireSRWLockExclusive(s: *SRWLOCK) callconv(WINAPI) void; pub extern "kernel32" fn ReleaseSRWLockExclusive(s: *SRWLOCK) callconv(WINAPI) void; + +pub extern "kernel32" fn SetThreadDescription(hThread: HANDLE, lpThreadDescription: LPCWSTR) callconv(WINAPI) HRESULT; +pub extern "kernel32" fn GetThreadDescription(hThread: HANDLE, ppszThreadDescription: *LPWSTR) callconv(WINAPI) HRESULT; |
