authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2021-07-29 13:25:13-04:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2021-07-29 13:25:13-04:00
log192b5d24cb4651ed2c6b6b1e5fee017d40ea5aa5
treec98d0f66679a0d47ede96ce3acefcb224b1eb172
parented174b7386cb5a6f2008cb6a25c3ff684645d847
parentdf4fe8716374e4cfdf1dee2bbcda7969eb6d057a
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

Merge pull request #8570 from vrischmann/thread-name

add Thread.setName and Thread.getName

8 files changed, 311 insertions(+), 0 deletions(-)

lib/std/Thread.zig+274
...@@ -38,6 +38,192 @@ else...@@ -38,6 +38,192 @@ else
3838
39impl: Impl,39impl: Impl,
4040
41pub const max_name_len = switch (std.Target.current.os.tag) {
42 .linux => 15,
43 .windows => 31,
44 .macos, .ios, .watchos, .tvos => 63,
45 .netbsd => 31,
46 .freebsd => 15,
47 .openbsd => 31,
48 else => 0,
49};
50
51pub const SetNameError = error{
52 NameTooLong,
53 Unsupported,
54 Unexpected,
55} || os.PrctlError || os.WriteError || std.fs.File.OpenError || std.fmt.BufPrintError;
56
57pub fn setName(self: Thread, name: []const u8) SetNameError!void {
58 if (name.len > max_name_len) return error.NameTooLong;
59
60 const name_with_terminator = blk: {
61 var name_buf: [max_name_len:0]u8 = undefined;
62 std.mem.copy(u8, &name_buf, name);
63 name_buf[name.len] = 0;
64 break :blk name_buf[0..name.len :0];
65 };
66
67 switch (std.Target.current.os.tag) {
68 .linux => if (use_pthreads) {
69 const err = std.c.pthread_setname_np(self.getHandle(), name_with_terminator.ptr);
70 return switch (err) {
71 0 => {},
72 os.ERANGE => unreachable,
73 else => return os.unexpectedErrno(err),
74 };
75 } else if (use_pthreads and self.getHandle() == std.c.pthread_self()) {
76 const err = try os.prctl(.SET_NAME, .{@ptrToInt(name_with_terminator.ptr)});
77 return switch (err) {
78 0 => {},
79 else => return os.unexpectedErrno(err),
80 };
81 } else {
82 var buf: [32]u8 = undefined;
83 const path = try std.fmt.bufPrint(&buf, "/proc/self/task/{d}/comm", .{self.getHandle()});
84
85 const file = try std.fs.cwd().openFile(path, .{ .write = true });
86 defer file.close();
87
88 try file.writer().writeAll(name);
89 },
90 .windows => if (std.Target.current.os.isAtLeast(.windows, .win10_rs1)) |res| {
91 // SetThreadDescription is only available since version 1607, which is 10.0.14393.795
92 // See https://en.wikipedia.org/wiki/Microsoft_Windows_SDK
93 if (!res) {
94 return error.Unsupported;
95 }
96
97 var name_buf_w: [max_name_len:0]u16 = undefined;
98 const length = try std.unicode.utf8ToUtf16Le(&name_buf_w, name);
99 name_buf_w[length] = 0;
100
101 try os.windows.SetThreadDescription(
102 self.getHandle(),
103 @ptrCast(os.windows.LPWSTR, &name_buf_w),
104 );
105 } else {
106 return error.Unsupported;
107 },
108 .macos, .ios, .watchos, .tvos => if (use_pthreads) {
109 // There doesn't seem to be a way to set the name for an arbitrary thread, only the current one.
110 if (self.getHandle() != std.c.pthread_self()) return error.Unsupported;
111
112 const err = std.c.pthread_setname_np(name_with_terminator.ptr);
113 return switch (err) {
114 0 => {},
115 else => return os.unexpectedErrno(err),
116 };
117 },
118 .netbsd => if (use_pthreads) {
119 const err = std.c.pthread_setname_np(self.getHandle(), name_with_terminator.ptr, null);
120 return switch (err) {
121 0 => {},
122 os.EINVAL => unreachable,
123 os.ESRCH => unreachable,
124 os.ENOMEM => unreachable,
125 else => return os.unexpectedErrno(err),
126 };
127 },
128 .freebsd, .openbsd => if (use_pthreads) {
129 // Use pthread_set_name_np for FreeBSD because pthread_setname_np is FreeBSD 12.2+ only.
130 // TODO maybe revisit this if depending on FreeBSD 12.2+ is acceptable because pthread_setname_np can return an error.
131
132 std.c.pthread_set_name_np(self.getHandle(), name_with_terminator.ptr);
133 },
134 else => return error.Unsupported,
135 }
136}
137
138pub const GetNameError = error{
139 // For Windows, the name is converted from UTF16 to UTF8
140 CodepointTooLarge,
141 Utf8CannotEncodeSurrogateHalf,
142 DanglingSurrogateHalf,
143 ExpectedSecondSurrogateHalf,
144 UnexpectedSecondSurrogateHalf,
145
146 Unsupported,
147 Unexpected,
148} || os.PrctlError || os.ReadError || std.fs.File.OpenError || std.fmt.BufPrintError;
149
150pub fn getName(self: Thread, buffer_ptr: *[max_name_len:0]u8) GetNameError!?[]const u8 {
151 buffer_ptr[max_name_len] = 0;
152 var buffer = std.mem.span(buffer_ptr);
153
154 switch (std.Target.current.os.tag) {
155 .linux => if (use_pthreads and comptime std.Target.current.abi.isGnu()) {
156 const err = std.c.pthread_getname_np(self.getHandle(), buffer.ptr, max_name_len + 1);
157 return switch (err) {
158 0 => std.mem.sliceTo(buffer, 0),
159 os.ERANGE => unreachable,
160 else => return os.unexpectedErrno(err),
161 };
162 } else if (use_pthreads and self.getHandle() == std.c.pthread_self()) {
163 const err = try os.prctl(.GET_NAME, .{@ptrToInt(buffer.ptr)});
164 return switch (err) {
165 0 => std.mem.sliceTo(buffer, 0),
166 else => return os.unexpectedErrno(err),
167 };
168 } else if (!use_pthreads) {
169 var buf: [32]u8 = undefined;
170 const path = try std.fmt.bufPrint(&buf, "/proc/self/task/{d}/comm", .{self.getHandle()});
171
172 const file = try std.fs.cwd().openFile(path, .{});
173 defer file.close();
174
175 const data_len = try file.reader().readAll(buffer_ptr[0 .. max_name_len + 1]);
176
177 return if (data_len >= 1) buffer[0 .. data_len - 1] else null;
178 } else {
179 // musl doesn't provide pthread_getname_np and there's no way to retrieve the thread id of an arbitrary thread.
180 return error.Unsupported;
181 },
182 .windows => if (std.Target.current.os.isAtLeast(.windows, .win10_rs1)) |res| {
183 // GetThreadDescription is only available since version 1607, which is 10.0.14393.795
184 // See https://en.wikipedia.org/wiki/Microsoft_Windows_SDK
185 if (!res) {
186 return error.Unsupported;
187 }
188
189 var name_w: os.windows.LPWSTR = undefined;
190 try os.windows.GetThreadDescription(self.getHandle(), &name_w);
191 defer os.windows.LocalFree(name_w);
192
193 const data_len = try std.unicode.utf16leToUtf8(buffer, std.mem.sliceTo(name_w, 0));
194
195 return if (data_len >= 1) buffer[0..data_len] else null;
196 } else {
197 return error.Unsupported;
198 },
199 .macos, .ios, .watchos, .tvos => if (use_pthreads) {
200 const err = std.c.pthread_getname_np(self.getHandle(), buffer.ptr, max_name_len + 1);
201 return switch (err) {
202 0 => std.mem.sliceTo(buffer, 0),
203 os.ESRCH => unreachable,
204 else => return os.unexpectedErrno(err),
205 };
206 },
207 .netbsd => if (use_pthreads) {
208 const err = std.c.pthread_getname_np(self.getHandle(), buffer.ptr, max_name_len + 1);
209 return switch (err) {
210 0 => std.mem.sliceTo(buffer, 0),
211 os.EINVAL => unreachable,
212 os.ESRCH => unreachable,
213 else => return os.unexpectedErrno(err),
214 };
215 },
216 .freebsd, .openbsd => if (use_pthreads) {
217 // Use pthread_get_name_np for FreeBSD because pthread_getname_np is FreeBSD 12.2+ only.
218 // TODO maybe revisit this if depending on FreeBSD 12.2+ is acceptable because pthread_getname_np can return an error.
219
220 std.c.pthread_get_name_np(self.getHandle(), buffer.ptr, max_name_len + 1);
221 return std.mem.sliceTo(buffer, 0);
222 },
223 else => return error.Unsupported,
224 }
225}
226
41/// Represents a unique ID per thread.227/// Represents a unique ID per thread.
42pub const Id = u64;228pub const Id = u64;
43229
...@@ -779,6 +965,94 @@ const LinuxThreadImpl = struct {...@@ -779,6 +965,94 @@ const LinuxThreadImpl = struct {
779 }965 }
780};966};
781967
968fn testThreadName(thread: *Thread) !void {
969 const testCases = &[_][]const u8{
970 "mythread",
971 "b" ** max_name_len,
972 };
973
974 inline for (testCases) |tc| {
975 try thread.setName(tc);
976
977 var name_buffer: [max_name_len:0]u8 = undefined;
978
979 const name = try thread.getName(&name_buffer);
980 if (name) |value| {
981 try std.testing.expectEqual(tc.len, value.len);
982 try std.testing.expectEqualStrings(tc, value);
983 }
984 }
985}
986
987test "setName, getName" {
988 if (std.builtin.single_threaded) return error.SkipZigTest;
989
990 const Context = struct {
991 start_wait_event: ResetEvent = undefined,
992 test_done_event: ResetEvent = undefined,
993
994 done: std.atomic.Atomic(bool) = std.atomic.Atomic(bool).init(false),
995 thread: Thread = undefined,
996
997 fn init(self: *@This()) !void {
998 try self.start_wait_event.init();
999 try self.test_done_event.init();
1000 }
1001
1002 pub fn run(ctx: *@This()) !void {
1003 // Wait for the main thread to have set the thread field in the context.
1004 ctx.start_wait_event.wait();
1005
1006 switch (std.Target.current.os.tag) {
1007 .windows => testThreadName(&ctx.thread) catch |err| switch (err) {
1008 error.Unsupported => return error.SkipZigTest,
1009 else => return err,
1010 },
1011 else => try testThreadName(&ctx.thread),
1012 }
1013
1014 // Signal our test is done
1015 ctx.test_done_event.set();
1016
1017 while (!ctx.done.load(.SeqCst)) {
1018 std.time.sleep(5 * std.time.ns_per_ms);
1019 }
1020 }
1021 };
1022
1023 var context = Context{};
1024 try context.init();
1025
1026 var thread = try spawn(.{}, Context.run, .{&context});
1027 context.thread = thread;
1028 context.start_wait_event.set();
1029 context.test_done_event.wait();
1030
1031 switch (std.Target.current.os.tag) {
1032 .macos, .ios, .watchos, .tvos => {
1033 const res = thread.setName("foobar");
1034 try std.testing.expectError(error.Unsupported, res);
1035 },
1036 .windows => testThreadName(&thread) catch |err| switch (err) {
1037 error.Unsupported => return error.SkipZigTest,
1038 else => return err,
1039 },
1040 else => |tag| if (tag == .linux and use_pthreads and comptime std.Target.current.abi.isMusl()) {
1041 try thread.setName("foobar");
1042
1043 var name_buffer: [max_name_len:0]u8 = undefined;
1044 const res = thread.getName(&name_buffer);
1045
1046 try std.testing.expectError(error.Unsupported, res);
1047 } else {
1048 try testThreadName(&thread);
1049 },
1050 }
1051
1052 context.done.store(true, .SeqCst);
1053 thread.join();
1054}
1055
782test "std.Thread" {1056test "std.Thread" {
783 // Doesn't use testing.refAllDecls() since that would pull in the compileError spinLoopHint.1057 // Doesn't use testing.refAllDecls() since that would pull in the compileError spinLoopHint.
784 _ = AutoResetEvent;1058 _ = AutoResetEvent;
lib/std/c/darwin.zig+2
...@@ -193,6 +193,8 @@ pub const pthread_attr_t = extern struct {...@@ -193,6 +193,8 @@ pub const pthread_attr_t = extern struct {
193193
194const pthread_t = std.c.pthread_t;194const pthread_t = std.c.pthread_t;
195pub extern "c" fn pthread_threadid_np(thread: ?pthread_t, thread_id: *u64) c_int;195pub extern "c" fn pthread_threadid_np(thread: ?pthread_t, thread_id: *u64) c_int;
196pub extern "c" fn pthread_setname_np(name: [*:0]const u8) c_int;
197pub extern "c" fn pthread_getname_np(thread: std.c.pthread_t, name: [*:0]u8, len: usize) c_int;
196198
197pub extern "c" fn arc4random_buf(buf: [*]u8, len: usize) void;199pub extern "c" fn arc4random_buf(buf: [*]u8, len: usize) void;
198200
lib/std/c/freebsd.zig+2
...@@ -14,6 +14,8 @@ pub extern "c" fn sigaltstack(ss: ?*stack_t, old_ss: ?*stack_t) c_int;...@@ -14,6 +14,8 @@ pub extern "c" fn sigaltstack(ss: ?*stack_t, old_ss: ?*stack_t) c_int;
14pub extern "c" fn getrandom(buf_ptr: [*]u8, buf_len: usize, flags: c_uint) isize;14pub extern "c" fn getrandom(buf_ptr: [*]u8, buf_len: usize, flags: c_uint) isize;
1515
16pub extern "c" fn pthread_getthreadid_np() c_int;16pub extern "c" fn pthread_getthreadid_np() c_int;
17pub extern "c" fn pthread_set_name_np(thread: std.c.pthread_t, name: [*:0]const u8) void;
18pub extern "c" fn pthread_get_name_np(thread: std.c.pthread_t, name: [*:0]u8, len: usize) void;
17pub extern "c" fn pipe2(fds: *[2]fd_t, flags: u32) c_int;19pub extern "c" fn pipe2(fds: *[2]fd_t, flags: u32) c_int;
1820
19pub extern "c" fn posix_memalign(memptr: *?*c_void, alignment: usize, size: usize) c_int;21pub extern "c" fn posix_memalign(memptr: *?*c_void, alignment: usize, size: usize) c_int;
lib/std/c/linux.zig+3
...@@ -186,6 +186,9 @@ const __SIZEOF_PTHREAD_MUTEX_T = if (os_tag == .fuchsia) 40 else switch (abi) {...@@ -186,6 +186,9 @@ const __SIZEOF_PTHREAD_MUTEX_T = if (os_tag == .fuchsia) 40 else switch (abi) {
186};186};
187const __SIZEOF_SEM_T = 4 * @sizeOf(usize);187const __SIZEOF_SEM_T = 4 * @sizeOf(usize);
188188
189pub extern "c" fn pthread_setname_np(thread: std.c.pthread_t, name: [*:0]const u8) c_int;
190pub extern "c" fn pthread_getname_np(thread: std.c.pthread_t, name: [*:0]u8, len: usize) c_int;
191
189pub const RTLD_LAZY = 1;192pub const RTLD_LAZY = 1;
190pub const RTLD_NOW = 2;193pub const RTLD_NOW = 2;
191pub const RTLD_NOLOAD = 4;194pub const RTLD_NOLOAD = 4;
lib/std/c/netbsd.zig+3
...@@ -94,3 +94,6 @@ pub const pthread_attr_t = extern struct {...@@ -94,3 +94,6 @@ pub const pthread_attr_t = extern struct {
94};94};
9595
96pub const sem_t = ?*opaque {};96pub const sem_t = ?*opaque {};
97
98pub extern "c" fn pthread_setname_np(thread: std.c.pthread_t, name: [*:0]const u8, arg: ?*c_void) c_int;
99pub extern "c" fn pthread_getname_np(thread: std.c.pthread_t, name: [*:0]u8, len: usize) c_int;
lib/std/c/openbsd.zig+3
...@@ -45,3 +45,6 @@ pub extern "c" fn posix_memalign(memptr: *?*c_void, alignment: usize, size: usiz...@@ -45,3 +45,6 @@ pub extern "c" fn posix_memalign(memptr: *?*c_void, alignment: usize, size: usiz
4545
46pub extern "c" fn pledge(promises: ?[*:0]const u8, execpromises: ?[*:0]const u8) c_int;46pub extern "c" fn pledge(promises: ?[*:0]const u8, execpromises: ?[*:0]const u8) c_int;
47pub extern "c" fn unveil(path: ?[*:0]const u8, permissions: ?[*:0]const u8) c_int;47pub extern "c" fn unveil(path: ?[*:0]const u8, permissions: ?[*:0]const u8) c_int;
48
49pub extern "c" fn pthread_set_name_np(thread: std.c.pthread_t, name: [*:0]const u8) void;
50pub extern "c" fn pthread_get_name_np(thread: std.c.pthread_t, name: [*:0]u8, len: usize) void;
lib/std/os/windows.zig+19
...@@ -1631,6 +1631,10 @@ pub fn HeapDestroy(hHeap: HANDLE) void {...@@ -1631,6 +1631,10 @@ pub fn HeapDestroy(hHeap: HANDLE) void {
1631 assert(kernel32.HeapDestroy(hHeap) != 0);1631 assert(kernel32.HeapDestroy(hHeap) != 0);
1632}1632}
16331633
1634pub fn LocalFree(hMem: HLOCAL) void {
1635 assert(kernel32.LocalFree(hMem) == null);
1636}
1637
1634pub const GetFileInformationByHandleError = error{Unexpected};1638pub const GetFileInformationByHandleError = error{Unexpected};
16351639
1636pub fn GetFileInformationByHandle(1640pub fn GetFileInformationByHandle(
...@@ -2011,6 +2015,21 @@ pub fn unexpectedStatus(status: NTSTATUS) std.os.UnexpectedError {...@@ -2011,6 +2015,21 @@ pub fn unexpectedStatus(status: NTSTATUS) std.os.UnexpectedError {
2011 return error.Unexpected;2015 return error.Unexpected;
2012}2016}
20132017
2018pub fn SetThreadDescription(hThread: HANDLE, lpThreadDescription: LPCWSTR) !void {
2019 if (kernel32.SetThreadDescription(hThread, lpThreadDescription) == 0) {
2020 switch (kernel32.GetLastError()) {
2021 else => |err| return unexpectedError(err),
2022 }
2023 }
2024}
2025pub fn GetThreadDescription(hThread: HANDLE, ppszThreadDescription: *LPWSTR) !void {
2026 if (kernel32.GetThreadDescription(hThread, ppszThreadDescription) == 0) {
2027 switch (kernel32.GetLastError()) {
2028 else => |err| return unexpectedError(err),
2029 }
2030 }
2031}
2032
2014test "" {2033test "" {
2015 if (builtin.os.tag == .windows) {2034 if (builtin.os.tag == .windows) {
2016 _ = @import("windows/test.zig");2035 _ = @import("windows/test.zig");
lib/std/os/windows/kernel32.zig+5
...@@ -192,6 +192,8 @@ pub extern "kernel32" fn HeapValidate(hHeap: HANDLE, dwFlags: DWORD, lpMem: ?*co...@@ -192,6 +192,8 @@ pub extern "kernel32" fn HeapValidate(hHeap: HANDLE, dwFlags: DWORD, lpMem: ?*co
192pub extern "kernel32" fn VirtualAlloc(lpAddress: ?LPVOID, dwSize: SIZE_T, flAllocationType: DWORD, flProtect: DWORD) callconv(WINAPI) ?LPVOID;192pub extern "kernel32" fn VirtualAlloc(lpAddress: ?LPVOID, dwSize: SIZE_T, flAllocationType: DWORD, flProtect: DWORD) callconv(WINAPI) ?LPVOID;
193pub extern "kernel32" fn VirtualFree(lpAddress: ?LPVOID, dwSize: SIZE_T, dwFreeType: DWORD) callconv(WINAPI) BOOL;193pub extern "kernel32" fn VirtualFree(lpAddress: ?LPVOID, dwSize: SIZE_T, dwFreeType: DWORD) callconv(WINAPI) BOOL;
194194
195pub extern "kernel32" fn LocalFree(hMem: HLOCAL) callconv(WINAPI) ?HLOCAL;
196
195pub extern "kernel32" fn MoveFileExW(197pub extern "kernel32" fn MoveFileExW(
196 lpExistingFileName: [*:0]const u16,198 lpExistingFileName: [*:0]const u16,
197 lpNewFileName: [*:0]const u16,199 lpNewFileName: [*:0]const u16,
...@@ -342,3 +344,6 @@ pub extern "kernel32" fn SleepConditionVariableSRW(...@@ -342,3 +344,6 @@ pub extern "kernel32" fn SleepConditionVariableSRW(
342pub extern "kernel32" fn TryAcquireSRWLockExclusive(s: *SRWLOCK) callconv(WINAPI) BOOLEAN;344pub extern "kernel32" fn TryAcquireSRWLockExclusive(s: *SRWLOCK) callconv(WINAPI) BOOLEAN;
343pub extern "kernel32" fn AcquireSRWLockExclusive(s: *SRWLOCK) callconv(WINAPI) void;345pub extern "kernel32" fn AcquireSRWLockExclusive(s: *SRWLOCK) callconv(WINAPI) void;
344pub extern "kernel32" fn ReleaseSRWLockExclusive(s: *SRWLOCK) callconv(WINAPI) void;346pub extern "kernel32" fn ReleaseSRWLockExclusive(s: *SRWLOCK) callconv(WINAPI) void;
347
348pub extern "kernel32" fn SetThreadDescription(hThread: HANDLE, lpThreadDescription: LPCWSTR) callconv(WINAPI) HRESULT;
349pub extern "kernel32" fn GetThreadDescription(hThread: HANDLE, ppszThreadDescription: *LPWSTR) callconv(WINAPI) HRESULT;