| ... | @@ -38,6 +38,192 @@ else | ... | @@ -38,6 +38,192 @@ else |
| 38 | | 38 | |
| 39 | impl: Impl, | 39 | impl: Impl, |
| 40 | | 40 | |
| | 41 | pub 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 | |
| | 51 | pub const SetNameError = error{ |
| | 52 | NameTooLong, |
| | 53 | Unsupported, |
| | 54 | Unexpected, |
| | 55 | } || os.PrctlError || os.WriteError || std.fs.File.OpenError || std.fmt.BufPrintError; |
| | 56 | |
| | 57 | pub 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 | |
| | 138 | pub 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 | |
| | 150 | pub 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. |
| 42 | pub const Id = u64; | 228 | pub const Id = u64; |
| 43 | | 229 | |
| ... | @@ -779,6 +965,94 @@ const LinuxThreadImpl = struct { | ... | @@ -779,6 +965,94 @@ const LinuxThreadImpl = struct { |
| 779 | } | 965 | } |
| 780 | }; | 966 | }; |
| 781 | | 967 | |
| | 968 | fn 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 | |
| | 987 | test "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 | |
| 782 | test "std.Thread" { | 1056 | test "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; |