//@ignore-target: windows # No pthreads on Windows //@ignore-target: android # No pthread_{get,set}_name on Android use std::ffi::{CStr, CString}; use std::thread; const MAX_THREAD_NAME_LEN: usize = { cfg_if::cfg_if! { if #[cfg(any(target_os = "linux"))] { 16 } else if #[cfg(any(target_os = "illumos", target_os = "solaris"))] { 32 } else if #[cfg(target_os = "macos")] { libc::MAXTHREADNAMESIZE // 64, at the time of writing } else if #[cfg(target_os = "freebsd")] { usize::MAX // as far as I can tell } else { panic!() } } }; fn main() { // The short name should be shorter than 16 bytes which POSIX promises // for thread names. The length includes a null terminator. let short_name = "test_named".to_owned(); let long_name = std::iter::once("test_named_thread_truncation") .chain(std::iter::repeat(" yada").take(100)) .collect::(); fn set_thread_name(name: &CStr) -> i32 { cfg_if::cfg_if! { if #[cfg(any( target_os = "linux", target_os = "freebsd", target_os = "illumos", target_os = "solaris" ))] { unsafe { libc::pthread_setname_np(libc::pthread_self(), name.as_ptr().cast()) } } else if #[cfg(target_os = "macos")] { unsafe { libc::pthread_setname_np(name.as_ptr().cast()) } } else { compile_error!("set_thread_name not supported for this OS") } } } fn get_thread_name(name: &mut [u8]) -> i32 { cfg_if::cfg_if! { if #[cfg(any( target_os = "linux", target_os = "freebsd", target_os = "illumos", target_os = "solaris", target_os = "macos" ))] { unsafe { libc::pthread_getname_np(libc::pthread_self(), name.as_mut_ptr().cast(), name.len()) } } else { compile_error!("get_thread_name not supported for this OS") } } } // Set name via Rust API, get it via pthreads. let long_name2 = long_name.clone(); thread::Builder::new() .name(long_name.clone()) .spawn(move || { let mut buf = vec![0u8; long_name2.len() + 1]; assert_eq!(get_thread_name(&mut buf), 0); let cstr = CStr::from_bytes_until_nul(&buf).unwrap(); let truncated_name = &long_name2[..long_name2.len().min(MAX_THREAD_NAME_LEN - 1)]; assert_eq!(cstr.to_bytes(), truncated_name.as_bytes()); }) .unwrap() .join() .unwrap(); // Set name via pthread and get it again (short name). thread::Builder::new() .spawn(move || { // Set short thread name. let cstr = CString::new(short_name.clone()).unwrap(); assert!(cstr.to_bytes_with_nul().len() <= MAX_THREAD_NAME_LEN); // this should fit assert_eq!(set_thread_name(&cstr), 0); // Now get it again, in various ways. // POSIX seems to promise at least 15 chars excluding a null terminator. let mut buf = vec![0u8; short_name.len().max(15) + 1]; assert_eq!(get_thread_name(&mut buf), 0); let cstr = CStr::from_bytes_until_nul(&buf).unwrap(); assert_eq!(cstr.to_bytes(), short_name.as_bytes()); // Test what happens when the buffer is shorter than 16, but still long enough. let res = get_thread_name(&mut buf[..15]); cfg_if::cfg_if! { if #[cfg(target_os = "linux")] { // For glibc used by linux-gnu there should be a failue, // if a shorter than 16 bytes buffer is provided, even if that would be // large enough for the thread name. assert_eq!(res, libc::ERANGE); } else { // Everywhere else, this should work. assert_eq!(res, 0); // POSIX seems to promise at least 15 chars excluding a null terminator. let cstr = CStr::from_bytes_until_nul(&buf).unwrap(); assert_eq!(short_name.as_bytes(), cstr.to_bytes()); } } // Test what happens when the buffer is too short even for the short name. let res = get_thread_name(&mut buf[..4]); cfg_if::cfg_if! { if #[cfg(any(target_os = "freebsd", target_os = "macos"))] { // On macOS and FreeBSD it's not an error for the buffer to be // too short for the thread name -- they truncate instead. assert_eq!(res, 0); let cstr = CStr::from_bytes_until_nul(&buf).unwrap(); assert_eq!(cstr.to_bytes_with_nul().len(), 4); assert!(short_name.as_bytes().starts_with(cstr.to_bytes())); } else { // The rest should give an error. assert_eq!(res, libc::ERANGE); } } // Test zero-sized buffer. let res = get_thread_name(&mut []); cfg_if::cfg_if! { if #[cfg(any(target_os = "freebsd", target_os = "macos"))] { // On macOS and FreeBSD it's not an error for the buffer to be // too short for the thread name -- even with size 0. assert_eq!(res, 0); } else { // The rest should give an error. assert_eq!(res, libc::ERANGE); } } }) .unwrap() .join() .unwrap(); // Set name via pthread and get it again (long name). thread::Builder::new() .spawn(move || { // Set full thread name. let cstr = CString::new(long_name.clone()).unwrap(); let res = set_thread_name(&cstr); cfg_if::cfg_if! { if #[cfg(target_os = "freebsd")] { // Names of all size are supported. assert!(cstr.to_bytes_with_nul().len() <= MAX_THREAD_NAME_LEN); assert_eq!(res, 0); } else if #[cfg(target_os = "macos")] { // Name is too long. assert!(cstr.to_bytes_with_nul().len() > MAX_THREAD_NAME_LEN); assert_eq!(res, libc::ENAMETOOLONG); } else { // Name is too long. assert!(cstr.to_bytes_with_nul().len() > MAX_THREAD_NAME_LEN); assert_eq!(res, libc::ERANGE); } } // Set the longest name we can. let truncated_name = &long_name[..long_name.len().min(MAX_THREAD_NAME_LEN - 1)]; let cstr = CString::new(truncated_name).unwrap(); assert_eq!(set_thread_name(&cstr), 0); // Now get it again, in various ways. // This name should round-trip properly. let mut buf = vec![0u8; long_name.len() + 1]; assert_eq!(get_thread_name(&mut buf), 0); let cstr = CStr::from_bytes_until_nul(&buf).unwrap(); assert_eq!(cstr.to_bytes(), truncated_name.as_bytes()); // Test what happens when our buffer is just one byte too small. let res = get_thread_name(&mut buf[..truncated_name.len()]); cfg_if::cfg_if! { if #[cfg(any(target_os = "freebsd", target_os = "macos"))] { // On macOS and FreeBSD it's not an error for the buffer to be // too short for the thread name -- they truncate instead. assert_eq!(res, 0); let cstr = CStr::from_bytes_until_nul(&buf).unwrap(); assert_eq!(cstr.to_bytes(), &truncated_name.as_bytes()[..(truncated_name.len() - 1)]); } else { // The rest should give an error. assert_eq!(res, libc::ERANGE); } } }) .unwrap() .join() .unwrap(); // Now set the name for a non-existing thread and verify error codes. let invalid_thread = 0xdeadbeef; let error = { cfg_if::cfg_if! { if #[cfg(target_os = "linux")] { libc::ENOENT } else { libc::ESRCH } } }; #[cfg(not(target_os = "macos"))] { // macOS has no `setname` function accepting a thread id as the first argument. let res = unsafe { libc::pthread_setname_np(invalid_thread, [0].as_ptr()) }; assert_eq!(res, error); } let mut buf = [0; 64]; let res = unsafe { libc::pthread_getname_np(invalid_thread, buf.as_mut_ptr(), buf.len()) }; assert_eq!(res, error); }