|
1 | 1 | //@ignore-target: windows # No pthreads on Windows |
2 | | -use std::ffi::CStr; |
3 | | -#[cfg(not(target_os = "freebsd"))] |
4 | | -use std::ffi::CString; |
| 2 | +use std::ffi::{CStr, CString}; |
5 | 3 | use std::thread; |
6 | 4 |
|
| 5 | +const MAX_THREAD_NAME_LEN: usize = { |
| 6 | + cfg_if::cfg_if! { |
| 7 | + if #[cfg(any(target_os = "linux"))] { |
| 8 | + 16 |
| 9 | + } else if #[cfg(any(target_os = "illumos", target_os = "solaris"))] { |
| 10 | + 32 |
| 11 | + } else if #[cfg(target_os = "macos")] { |
| 12 | + libc::MAXTHREADNAMESIZE // 64, at the time of writing |
| 13 | + } else if #[cfg(target_os = "freebsd")] { |
| 14 | + usize::MAX // as far as I can tell |
| 15 | + } else { |
| 16 | + panic!() |
| 17 | + } |
| 18 | + } |
| 19 | +}; |
| 20 | + |
7 | 21 | fn main() { |
8 | 22 | // The short name should be shorter than 16 bytes which POSIX promises |
9 | 23 | // for thread names. The length includes a null terminator. |
@@ -52,84 +66,117 @@ fn main() { |
52 | 66 | } |
53 | 67 |
|
54 | 68 | thread::Builder::new() |
55 | | - .name(short_name.clone()) |
56 | 69 | .spawn(move || { |
57 | | - // Rust remembers the full thread name itself. |
58 | | - assert_eq!(thread::current().name(), Some(short_name.as_str())); |
| 70 | + // Set short thread name. |
| 71 | + let cstr = CString::new(short_name.clone()).unwrap(); |
| 72 | + assert!(cstr.to_bytes_with_nul().len() <= MAX_THREAD_NAME_LEN); // this should fit |
| 73 | + assert_eq!(set_thread_name(&cstr), 0); |
59 | 74 |
|
60 | | - // Note that glibc requires 15 bytes long buffer exculding a null terminator. |
61 | | - // Otherwise, `pthread_getname_np` returns an error. |
| 75 | + // Now get it again, in various ways. |
| 76 | + |
| 77 | + // POSIX seems to promise at least 15 chars excluding a null terminator. |
62 | 78 | let mut buf = vec![0u8; short_name.len().max(15) + 1]; |
63 | 79 | assert_eq!(get_thread_name(&mut buf), 0); |
64 | | - |
65 | 80 | let cstr = CStr::from_bytes_until_nul(&buf).unwrap(); |
66 | | - // POSIX seems to promise at least 15 chars excluding a null terminator. |
67 | | - assert_eq!(short_name.as_bytes(), cstr.to_bytes()); |
68 | | - |
69 | | - // Also test directly calling pthread_setname to check its return value. |
70 | | - assert_eq!(set_thread_name(&cstr), 0); |
71 | | - |
72 | | - // For glibc used by linux-gnu there should be a failue, |
73 | | - // if a shorter than 16 bytes buffer is provided, even if that would be |
74 | | - // large enough for the thread name. |
75 | | - #[cfg(target_os = "linux")] |
76 | | - assert_eq!(get_thread_name(&mut buf[..15]), libc::ERANGE); |
| 81 | + assert_eq!(cstr.to_bytes(), short_name.as_bytes()); |
| 82 | + |
| 83 | + // Test what happens when the buffer is shorter than 16, but still long enough. |
| 84 | + let res = get_thread_name(&mut buf[..15]); |
| 85 | + cfg_if::cfg_if! { |
| 86 | + if #[cfg(target_os = "linux")] { |
| 87 | + // For glibc used by linux-gnu there should be a failue, |
| 88 | + // if a shorter than 16 bytes buffer is provided, even if that would be |
| 89 | + // large enough for the thread name. |
| 90 | + assert_eq!(res, libc::ERANGE); |
| 91 | + } else { |
| 92 | + // Everywhere else, this should work. |
| 93 | + assert_eq!(res, 0); |
| 94 | + // POSIX seems to promise at least 15 chars excluding a null terminator. |
| 95 | + let cstr = CStr::from_bytes_until_nul(&buf).unwrap(); |
| 96 | + assert_eq!(short_name.as_bytes(), cstr.to_bytes()); |
| 97 | + } |
| 98 | + } |
77 | 99 |
|
78 | | - // Solaris compatible implementations return an error, |
79 | | - // if the buffer is shorter than the thread name. |
80 | | - #[cfg(any(target_os = "illumos", target_os = "solaris"))] |
81 | | - assert_eq!(get_thread_name(&mut buf[..4]), libc::ERANGE); |
82 | | - // On macOS and FreeBSD it's not an error for the buffer to be |
83 | | - // too short for the thread name -- they truncate instead. |
84 | | - #[cfg(any(target_os = "freebsd", target_os = "macos"))] |
85 | | - { |
86 | | - // Ensure that a zero sized buffer returns no error. |
87 | | - assert_eq!(get_thread_name(&mut buf[..0]), 0); |
| 100 | + // Test what happens when the buffer is too short even for the short name. |
| 101 | + let res = get_thread_name(&mut buf[..4]); |
| 102 | + cfg_if::cfg_if! { |
| 103 | + if #[cfg(any(target_os = "freebsd", target_os = "macos"))] { |
| 104 | + // On macOS and FreeBSD it's not an error for the buffer to be |
| 105 | + // too short for the thread name -- they truncate instead. |
| 106 | + assert_eq!(res, 0); |
| 107 | + let cstr = CStr::from_bytes_until_nul(&buf).unwrap(); |
| 108 | + assert_eq!(cstr.to_bytes_with_nul().len(), 4); |
| 109 | + assert!(short_name.as_bytes().starts_with(cstr.to_bytes())); |
| 110 | + } else { |
| 111 | + // The rest should give an error. |
| 112 | + assert_eq!(res, libc::ERANGE); |
| 113 | + } |
| 114 | + } |
88 | 115 |
|
89 | | - // Ensure that a shorter tnan required buffer still returns no error, |
90 | | - // and gives a prefix of the thread name. |
91 | | - assert_eq!(get_thread_name(&mut buf[..4]), 0); |
92 | | - let cstr = CStr::from_bytes_until_nul(&buf).unwrap(); |
93 | | - assert_eq!(cstr.to_bytes_with_nul().len(), 4); |
94 | | - assert!(short_name.as_bytes().starts_with(cstr.to_bytes())); |
| 116 | + // Test zero-sized buffer. |
| 117 | + let res = get_thread_name(&mut []); |
| 118 | + cfg_if::cfg_if! { |
| 119 | + if #[cfg(any(target_os = "freebsd", target_os = "macos"))] { |
| 120 | + // On macOS and FreeBSD it's not an error for the buffer to be |
| 121 | + // too short for the thread name -- even with size 0. |
| 122 | + assert_eq!(res, 0); |
| 123 | + } else { |
| 124 | + // The rest should give an error. |
| 125 | + assert_eq!(res, libc::ERANGE); |
| 126 | + } |
95 | 127 | } |
96 | 128 | }) |
97 | 129 | .unwrap() |
98 | 130 | .join() |
99 | 131 | .unwrap(); |
100 | 132 |
|
101 | 133 | thread::Builder::new() |
102 | | - .name(long_name.clone()) |
103 | 134 | .spawn(move || { |
104 | | - // Rust remembers the full thread name itself. |
105 | | - assert_eq!(thread::current().name(), Some(long_name.as_str())); |
| 135 | + // Set full thread name. |
| 136 | + let cstr = CString::new(long_name.clone()).unwrap(); |
| 137 | + let res = set_thread_name(&cstr); |
| 138 | + cfg_if::cfg_if! { |
| 139 | + if #[cfg(target_os = "freebsd")] { |
| 140 | + // Names of all size are supported. |
| 141 | + assert!(cstr.to_bytes_with_nul().len() <= MAX_THREAD_NAME_LEN); |
| 142 | + assert_eq!(res, 0); |
| 143 | + } else if #[cfg(target_os = "macos")] { |
| 144 | + // Name is too long. |
| 145 | + assert!(cstr.to_bytes_with_nul().len() > MAX_THREAD_NAME_LEN); |
| 146 | + assert_eq!(res, libc::ENAMETOOLONG); |
| 147 | + } else { |
| 148 | + // Name is too long. |
| 149 | + assert!(cstr.to_bytes_with_nul().len() > MAX_THREAD_NAME_LEN); |
| 150 | + assert_eq!(res, libc::ERANGE); |
| 151 | + } |
| 152 | + } |
| 153 | + // Set the longest name we can. |
| 154 | + let truncated_name = &long_name[..long_name.len().min(MAX_THREAD_NAME_LEN - 1)]; |
| 155 | + let cstr = CString::new(truncated_name).unwrap(); |
| 156 | + assert_eq!(set_thread_name(&cstr), 0); |
| 157 | + |
| 158 | + // Now get it again, in various ways. |
106 | 159 |
|
107 | | - // But the system is limited -- make sure we successfully set a truncation. |
108 | | - // Note that there's no specific to glibc buffer requirement, since the value |
109 | | - // `long_name` is longer than 16 bytes including a null terminator. |
| 160 | + // This name should round-trip properly. |
110 | 161 | let mut buf = vec![0u8; long_name.len() + 1]; |
111 | 162 | assert_eq!(get_thread_name(&mut buf), 0); |
112 | | - |
113 | 163 | let cstr = CStr::from_bytes_until_nul(&buf).unwrap(); |
114 | | - // POSIX seems to promise at least 15 chars excluding a null terminator. |
115 | | - assert!( |
116 | | - cstr.to_bytes().len() >= 15, |
117 | | - "name is too short: len={}", |
118 | | - cstr.to_bytes().len() |
119 | | - ); |
120 | | - assert!(long_name.as_bytes().starts_with(cstr.to_bytes())); |
121 | | - |
122 | | - // Also test directly calling pthread_setname to check its return value. |
123 | | - assert_eq!(set_thread_name(&cstr), 0); |
124 | | - |
125 | | - // But with a too long name it should fail (except on FreeBSD where |
126 | | - // names of arbitrary size seem to be supported). |
127 | | - // On macOS, the error code is different. |
128 | | - #[cfg(not(any(target_os = "freebsd", target_os = "macos")))] |
129 | | - assert_eq!(set_thread_name(&CString::new(long_name).unwrap()), libc::ERANGE); |
130 | | - |
131 | | - #[cfg(target_os = "macos")] |
132 | | - assert_eq!(set_thread_name(&CString::new(long_name).unwrap()), libc::ENAMETOOLONG); |
| 164 | + assert_eq!(cstr.to_bytes(), truncated_name.as_bytes()); |
| 165 | + |
| 166 | + // Test what happens when our buffer is just one byte too small. |
| 167 | + let res = get_thread_name(&mut buf[..truncated_name.len()]); |
| 168 | + cfg_if::cfg_if! { |
| 169 | + if #[cfg(any(target_os = "freebsd", target_os = "macos"))] { |
| 170 | + // On macOS and FreeBSD it's not an error for the buffer to be |
| 171 | + // too short for the thread name -- they truncate instead. |
| 172 | + assert_eq!(res, 0); |
| 173 | + let cstr = CStr::from_bytes_until_nul(&buf).unwrap(); |
| 174 | + assert_eq!(cstr.to_bytes(), &truncated_name.as_bytes()[..(truncated_name.len() - 1)]); |
| 175 | + } else { |
| 176 | + // The rest should give an error. |
| 177 | + assert_eq!(res, libc::ERANGE); |
| 178 | + } |
| 179 | + } |
133 | 180 | }) |
134 | 181 | .unwrap() |
135 | 182 | .join() |
|
0 commit comments