| use super::Builder; |
| use crate::any::Any; |
| use crate::panic::panic_any; |
| use crate::sync::atomic::{AtomicBool, Ordering}; |
| use crate::sync::mpsc::{Sender, channel}; |
| use crate::sync::{Arc, Barrier}; |
| use crate::thread::{self, Scope, ThreadId}; |
| use crate::time::{Duration, Instant}; |
| use crate::{mem, result}; |
| |
| // !!! These tests are dangerous. If something is buggy, they will hang, !!! |
| // !!! instead of exiting cleanly. This might wedge the buildbots. !!! |
| |
| #[test] |
| fn test_unnamed_thread() { |
| thread::spawn(move || { |
| assert!(thread::current().name().is_none()); |
| }) |
| .join() |
| .ok() |
| .expect("thread panicked"); |
| } |
| |
| #[test] |
| fn test_named_thread() { |
| Builder::new() |
| .name("ada lovelace".to_string()) |
| .spawn(move || { |
| assert!(thread::current().name().unwrap() == "ada lovelace".to_string()); |
| }) |
| .unwrap() |
| .join() |
| .unwrap(); |
| } |
| |
| #[cfg(any( |
| // Note: musl didn't add pthread_getname_np until 1.2.3 |
| all(target_os = "linux", target_env = "gnu"), |
| target_vendor = "apple", |
| ))] |
| #[test] |
| fn test_named_thread_truncation() { |
| use crate::ffi::CStr; |
| |
| let long_name = crate::iter::once("test_named_thread_truncation") |
| .chain(crate::iter::repeat(" yada").take(100)) |
| .collect::<String>(); |
| |
| let result = Builder::new().name(long_name.clone()).spawn(move || { |
| // Rust remembers the full thread name itself. |
| assert_eq!(thread::current().name(), Some(long_name.as_str())); |
| |
| // But the system is limited -- make sure we successfully set a truncation. |
| let mut buf = vec![0u8; long_name.len() + 1]; |
| unsafe { |
| libc::pthread_getname_np(libc::pthread_self(), buf.as_mut_ptr().cast(), buf.len()); |
| } |
| let cstr = CStr::from_bytes_until_nul(&buf).unwrap(); |
| assert!(cstr.to_bytes().len() > 0); |
| assert!(long_name.as_bytes().starts_with(cstr.to_bytes())); |
| }); |
| result.unwrap().join().unwrap(); |
| } |
| |
| #[test] |
| #[should_panic] |
| fn test_invalid_named_thread() { |
| let _ = Builder::new().name("ada l\0velace".to_string()).spawn(|| {}); |
| } |
| |
| #[test] |
| fn test_run_basic() { |
| let (tx, rx) = channel(); |
| thread::spawn(move || { |
| tx.send(()).unwrap(); |
| }); |
| rx.recv().unwrap(); |
| } |
| |
| #[test] |
| fn test_is_finished() { |
| let b = Arc::new(Barrier::new(2)); |
| let t = thread::spawn({ |
| let b = b.clone(); |
| move || { |
| b.wait(); |
| 1234 |
| } |
| }); |
| |
| // Thread is definitely running here, since it's still waiting for the barrier. |
| assert_eq!(t.is_finished(), false); |
| |
| // Unblock the barrier. |
| b.wait(); |
| |
| // Now check that t.is_finished() becomes true within a reasonable time. |
| let start = Instant::now(); |
| while !t.is_finished() { |
| assert!(start.elapsed() < Duration::from_secs(2)); |
| thread::sleep(Duration::from_millis(15)); |
| } |
| |
| // Joining the thread should not block for a significant time now. |
| let join_time = Instant::now(); |
| assert_eq!(t.join().unwrap(), 1234); |
| assert!(join_time.elapsed() < Duration::from_secs(2)); |
| } |
| |
| #[test] |
| fn test_join_panic() { |
| match thread::spawn(move || panic!()).join() { |
| result::Result::Err(_) => (), |
| result::Result::Ok(()) => panic!(), |
| } |
| } |
| |
| #[test] |
| fn test_spawn_sched() { |
| let (tx, rx) = channel(); |
| |
| fn f(i: i32, tx: Sender<()>) { |
| let tx = tx.clone(); |
| thread::spawn(move || { |
| if i == 0 { |
| tx.send(()).unwrap(); |
| } else { |
| f(i - 1, tx); |
| } |
| }); |
| } |
| f(10, tx); |
| rx.recv().unwrap(); |
| } |
| |
| #[test] |
| fn test_spawn_sched_childs_on_default_sched() { |
| let (tx, rx) = channel(); |
| |
| thread::spawn(move || { |
| thread::spawn(move || { |
| tx.send(()).unwrap(); |
| }); |
| }); |
| |
| rx.recv().unwrap(); |
| } |
| |
| fn avoid_copying_the_body<F>(spawnfn: F) |
| where |
| F: FnOnce(Box<dyn Fn() + Send>), |
| { |
| let (tx, rx) = channel(); |
| |
| let x: Box<_> = Box::new(1); |
| let x_in_parent = (&*x) as *const i32 as usize; |
| |
| spawnfn(Box::new(move || { |
| let x_in_child = (&*x) as *const i32 as usize; |
| tx.send(x_in_child).unwrap(); |
| })); |
| |
| let x_in_child = rx.recv().unwrap(); |
| assert_eq!(x_in_parent, x_in_child); |
| } |
| |
| #[test] |
| fn test_avoid_copying_the_body_spawn() { |
| avoid_copying_the_body(|v| { |
| thread::spawn(move || v()); |
| }); |
| } |
| |
| #[test] |
| fn test_avoid_copying_the_body_thread_spawn() { |
| avoid_copying_the_body(|f| { |
| thread::spawn(move || { |
| f(); |
| }); |
| }) |
| } |
| |
| #[test] |
| fn test_avoid_copying_the_body_join() { |
| avoid_copying_the_body(|f| { |
| let _ = thread::spawn(move || f()).join(); |
| }) |
| } |
| |
| #[test] |
| fn test_child_doesnt_ref_parent() { |
| // If the child refcounts the parent thread, this will stack overflow when |
| // climbing the thread tree to dereference each ancestor. (See #1789) |
| // (well, it would if the constant were 8000+ - I lowered it to be more |
| // valgrind-friendly. try this at home, instead..!) |
| const GENERATIONS: u32 = 16; |
| fn child_no(x: u32) -> Box<dyn Fn() + Send> { |
| return Box::new(move || { |
| if x < GENERATIONS { |
| thread::spawn(move || child_no(x + 1)()); |
| } |
| }); |
| } |
| thread::spawn(|| child_no(0)()); |
| } |
| |
| #[test] |
| fn test_simple_newsched_spawn() { |
| thread::spawn(move || {}); |
| } |
| |
| #[test] |
| fn test_try_panic_message_string_literal() { |
| match thread::spawn(move || { |
| panic!("static string"); |
| }) |
| .join() |
| { |
| Err(e) => { |
| type T = &'static str; |
| assert!(e.is::<T>()); |
| assert_eq!(*e.downcast::<T>().unwrap(), "static string"); |
| } |
| Ok(()) => panic!(), |
| } |
| } |
| |
| #[test] |
| fn test_try_panic_any_message_owned_str() { |
| match thread::spawn(move || { |
| panic_any("owned string".to_string()); |
| }) |
| .join() |
| { |
| Err(e) => { |
| type T = String; |
| assert!(e.is::<T>()); |
| assert_eq!(*e.downcast::<T>().unwrap(), "owned string".to_string()); |
| } |
| Ok(()) => panic!(), |
| } |
| } |
| |
| #[test] |
| fn test_try_panic_any_message_any() { |
| match thread::spawn(move || { |
| panic_any(Box::new(413u16) as Box<dyn Any + Send>); |
| }) |
| .join() |
| { |
| Err(e) => { |
| type T = Box<dyn Any + Send>; |
| assert!(e.is::<T>()); |
| let any = e.downcast::<T>().unwrap(); |
| assert!(any.is::<u16>()); |
| assert_eq!(*any.downcast::<u16>().unwrap(), 413); |
| } |
| Ok(()) => panic!(), |
| } |
| } |
| |
| #[test] |
| fn test_try_panic_any_message_unit_struct() { |
| struct Juju; |
| |
| match thread::spawn(move || panic_any(Juju)).join() { |
| Err(ref e) if e.is::<Juju>() => {} |
| Err(_) | Ok(()) => panic!(), |
| } |
| } |
| |
| #[test] |
| fn test_park_unpark_before() { |
| for _ in 0..10 { |
| thread::current().unpark(); |
| thread::park(); |
| } |
| } |
| |
| #[test] |
| fn test_park_unpark_called_other_thread() { |
| for _ in 0..10 { |
| let th = thread::current(); |
| |
| let _guard = thread::spawn(move || { |
| super::sleep(Duration::from_millis(50)); |
| th.unpark(); |
| }); |
| |
| thread::park(); |
| } |
| } |
| |
| #[test] |
| fn test_park_timeout_unpark_before() { |
| for _ in 0..10 { |
| thread::current().unpark(); |
| thread::park_timeout(Duration::from_millis(u32::MAX as u64)); |
| } |
| } |
| |
| #[test] |
| fn test_park_timeout_unpark_not_called() { |
| for _ in 0..10 { |
| thread::park_timeout(Duration::from_millis(10)); |
| } |
| } |
| |
| #[test] |
| fn test_park_timeout_unpark_called_other_thread() { |
| for _ in 0..10 { |
| let th = thread::current(); |
| |
| let _guard = thread::spawn(move || { |
| super::sleep(Duration::from_millis(50)); |
| th.unpark(); |
| }); |
| |
| thread::park_timeout(Duration::from_millis(u32::MAX as u64)); |
| } |
| } |
| |
| #[test] |
| fn sleep_ms_smoke() { |
| thread::sleep(Duration::from_millis(2)); |
| } |
| |
| #[test] |
| fn test_size_of_option_thread_id() { |
| assert_eq!(mem::size_of::<Option<ThreadId>>(), mem::size_of::<ThreadId>()); |
| } |
| |
| #[test] |
| fn test_thread_id_equal() { |
| assert!(thread::current().id() == thread::current().id()); |
| } |
| |
| #[test] |
| fn test_thread_id_not_equal() { |
| let spawned_id = thread::spawn(|| thread::current().id()).join().unwrap(); |
| assert!(thread::current().id() != spawned_id); |
| } |
| |
| #[test] |
| fn test_scoped_threads_drop_result_before_join() { |
| let actually_finished = &AtomicBool::new(false); |
| struct X<'scope, 'env>(&'scope Scope<'scope, 'env>, &'env AtomicBool); |
| impl Drop for X<'_, '_> { |
| fn drop(&mut self) { |
| thread::sleep(Duration::from_millis(20)); |
| let actually_finished = self.1; |
| self.0.spawn(move || { |
| thread::sleep(Duration::from_millis(20)); |
| actually_finished.store(true, Ordering::Relaxed); |
| }); |
| } |
| } |
| thread::scope(|s| { |
| s.spawn(move || { |
| thread::sleep(Duration::from_millis(20)); |
| X(s, actually_finished) |
| }); |
| }); |
| assert!(actually_finished.load(Ordering::Relaxed)); |
| } |
| |
| #[test] |
| fn test_scoped_threads_nll() { |
| // this is mostly a *compilation test* for this exact function: |
| fn foo(x: &u8) { |
| thread::scope(|s| { |
| s.spawn(|| match x { |
| _ => (), |
| }); |
| }); |
| } |
| // let's also run it for good measure |
| let x = 42_u8; |
| foo(&x); |
| } |
| |
| // Regression test for https://github.com/rust-lang/rust/issues/98498. |
| #[test] |
| #[cfg(miri)] // relies on Miri's data race detector |
| fn scope_join_race() { |
| for _ in 0..100 { |
| let a_bool = AtomicBool::new(false); |
| |
| thread::scope(|s| { |
| for _ in 0..5 { |
| s.spawn(|| a_bool.load(Ordering::Relaxed)); |
| } |
| |
| for _ in 0..5 { |
| s.spawn(|| a_bool.load(Ordering::Relaxed)); |
| } |
| }); |
| } |
| } |
| |
| // Test that the smallest value for stack_size works on Windows. |
| #[cfg(windows)] |
| #[test] |
| fn test_minimal_thread_stack() { |
| use crate::sync::atomic::AtomicU8; |
| static COUNT: AtomicU8 = AtomicU8::new(0); |
| |
| let builder = thread::Builder::new().stack_size(1); |
| let before = builder.spawn(|| COUNT.fetch_add(1, Ordering::Relaxed)).unwrap().join().unwrap(); |
| assert_eq!(before, 0); |
| assert_eq!(COUNT.load(Ordering::Relaxed), 1); |
| } |