Skip to content

Commit 7280f3d

Browse files
committed
Truncate thread names on Linux and Apple targets
These targets have system limits on the thread names, 16 and 64 bytes respectively, and `pthread_setname_np` returns an error if the name is longer. However, we're not in a context that can propagate errors when we call this, and we used to implicitly truncate on Linux with `prctl`, so now we manually truncate these names ahead of time.
1 parent 57e2c06 commit 7280f3d

File tree

1 file changed

+43
-0
lines changed

1 file changed

+43
-0
lines changed

library/std/src/sys/unix/thread.rs

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,8 +132,11 @@ impl Thread {
132132

133133
#[cfg(target_os = "linux")]
134134
pub fn set_name(name: &CStr) {
135+
const TASK_COMM_LEN: usize = 16;
136+
135137
unsafe {
136138
// Available since glibc 2.12, musl 1.1.16, and uClibc 1.0.20.
139+
let name = truncate_cstr(name, TASK_COMM_LEN);
137140
libc::pthread_setname_np(libc::pthread_self(), name.as_ptr());
138141
}
139142
}
@@ -148,6 +151,7 @@ impl Thread {
148151
#[cfg(any(target_os = "macos", target_os = "ios", target_os = "watchos"))]
149152
pub fn set_name(name: &CStr) {
150153
unsafe {
154+
let name = truncate_cstr(name, libc::MAXTHREADNAMESIZE);
151155
libc::pthread_setname_np(name.as_ptr());
152156
}
153157
}
@@ -276,6 +280,20 @@ impl Drop for Thread {
276280
}
277281
}
278282

283+
#[cfg(any(target_os = "linux", target_os = "macos", target_os = "ios", target_os = "watchos"))]
284+
fn truncate_cstr(cstr: &CStr, max_with_nul: usize) -> crate::borrow::Cow<'_, CStr> {
285+
use crate::{borrow::Cow, ffi::CString};
286+
287+
if cstr.to_bytes_with_nul().len() > max_with_nul {
288+
let bytes = cstr.to_bytes()[..max_with_nul - 1].to_vec();
289+
// SAFETY: the non-nul bytes came straight from a CStr.
290+
// (CString will add the terminating nul.)
291+
Cow::Owned(unsafe { CString::from_vec_unchecked(bytes) })
292+
} else {
293+
Cow::Borrowed(cstr)
294+
}
295+
}
296+
279297
pub fn available_parallelism() -> io::Result<NonZeroUsize> {
280298
cfg_if::cfg_if! {
281299
if #[cfg(any(
@@ -902,3 +920,28 @@ fn min_stack_size(_: *const libc::pthread_attr_t) -> usize {
902920
fn min_stack_size(_: *const libc::pthread_attr_t) -> usize {
903921
2048 // just a guess
904922
}
923+
924+
#[test]
925+
#[cfg(any(target_os = "linux", target_os = "macos", target_os = "ios", target_os = "watchos"))]
926+
fn test_named_thread_truncation() {
927+
use crate::thread::{self, Builder};
928+
929+
let long_name = crate::iter::once("test_named_thread_truncation")
930+
.chain(crate::iter::repeat(" yada").take(100))
931+
.collect::<String>();
932+
933+
let result = Builder::new().name(long_name.clone()).spawn(move || {
934+
// Rust remembers the full thread name itself.
935+
assert_eq!(thread::current().name(), Some(long_name.as_str()));
936+
937+
// But the kernel is limited -- make sure we successfully set a truncation.
938+
let mut buf = vec![0u8; long_name.len() + 1];
939+
unsafe {
940+
libc::pthread_getname_np(libc::pthread_self(), buf.as_mut_ptr().cast(), buf.len());
941+
}
942+
let cstr = CStr::from_bytes_until_nul(&buf).unwrap();
943+
assert!(cstr.to_bytes().len() > 0);
944+
assert!(long_name.as_bytes().starts_with(cstr.to_bytes()));
945+
});
946+
result.unwrap().join().unwrap();
947+
}

0 commit comments

Comments
 (0)