| //@ignore-target-windows: No pthreads on Windows |
| // We use `yield` to test specific interleavings, so disable automatic preemption. |
| //@compile-flags: -Zmiri-preemption-rate=0 |
| #![feature(sync_unsafe_cell)] |
| |
| use std::cell::SyncUnsafeCell; |
| use std::mem::MaybeUninit; |
| use std::{mem, ptr, thread}; |
| |
| fn main() { |
| test_mutex_libc_init_recursive(); |
| test_mutex_libc_init_normal(); |
| test_mutex_libc_init_errorcheck(); |
| test_rwlock_libc_static_initializer(); |
| #[cfg(target_os = "linux")] |
| test_mutex_libc_static_initializer_recursive(); |
| |
| check_mutex(); |
| check_rwlock_write(); |
| check_rwlock_read_no_deadlock(); |
| check_cond(); |
| check_condattr(); |
| } |
| |
| fn test_mutex_libc_init_recursive() { |
| unsafe { |
| let mut attr: libc::pthread_mutexattr_t = mem::zeroed(); |
| assert_eq!(libc::pthread_mutexattr_init(&mut attr as *mut _), 0); |
| assert_eq!( |
| libc::pthread_mutexattr_settype(&mut attr as *mut _, libc::PTHREAD_MUTEX_RECURSIVE), |
| 0, |
| ); |
| let mut mutex: libc::pthread_mutex_t = mem::zeroed(); |
| assert_eq!(libc::pthread_mutex_init(&mut mutex as *mut _, &mut attr as *mut _), 0); |
| assert_eq!(libc::pthread_mutex_lock(&mut mutex as *mut _), 0); |
| assert_eq!(libc::pthread_mutex_trylock(&mut mutex as *mut _), 0); |
| assert_eq!(libc::pthread_mutex_unlock(&mut mutex as *mut _), 0); |
| assert_eq!(libc::pthread_mutex_unlock(&mut mutex as *mut _), 0); |
| assert_eq!(libc::pthread_mutex_trylock(&mut mutex as *mut _), 0); |
| assert_eq!(libc::pthread_mutex_lock(&mut mutex as *mut _), 0); |
| assert_eq!(libc::pthread_mutex_unlock(&mut mutex as *mut _), 0); |
| assert_eq!(libc::pthread_mutex_unlock(&mut mutex as *mut _), 0); |
| assert_eq!(libc::pthread_mutex_unlock(&mut mutex as *mut _), libc::EPERM); |
| assert_eq!(libc::pthread_mutex_destroy(&mut mutex as *mut _), 0); |
| assert_eq!(libc::pthread_mutexattr_destroy(&mut attr as *mut _), 0); |
| } |
| } |
| |
| fn test_mutex_libc_init_normal() { |
| unsafe { |
| let mut mutexattr: libc::pthread_mutexattr_t = mem::zeroed(); |
| assert_eq!( |
| libc::pthread_mutexattr_settype(&mut mutexattr as *mut _, 0x12345678), |
| libc::EINVAL, |
| ); |
| assert_eq!( |
| libc::pthread_mutexattr_settype(&mut mutexattr as *mut _, libc::PTHREAD_MUTEX_NORMAL), |
| 0, |
| ); |
| let mut mutex: libc::pthread_mutex_t = mem::zeroed(); |
| assert_eq!(libc::pthread_mutex_init(&mut mutex as *mut _, &mutexattr as *const _), 0); |
| assert_eq!(libc::pthread_mutex_lock(&mut mutex as *mut _), 0); |
| assert_eq!(libc::pthread_mutex_trylock(&mut mutex as *mut _), libc::EBUSY); |
| assert_eq!(libc::pthread_mutex_unlock(&mut mutex as *mut _), 0); |
| assert_eq!(libc::pthread_mutex_trylock(&mut mutex as *mut _), 0); |
| assert_eq!(libc::pthread_mutex_unlock(&mut mutex as *mut _), 0); |
| assert_eq!(libc::pthread_mutex_destroy(&mut mutex as *mut _), 0); |
| } |
| } |
| |
| fn test_mutex_libc_init_errorcheck() { |
| unsafe { |
| let mut mutexattr: libc::pthread_mutexattr_t = mem::zeroed(); |
| assert_eq!( |
| libc::pthread_mutexattr_settype( |
| &mut mutexattr as *mut _, |
| libc::PTHREAD_MUTEX_ERRORCHECK, |
| ), |
| 0, |
| ); |
| let mut mutex: libc::pthread_mutex_t = mem::zeroed(); |
| assert_eq!(libc::pthread_mutex_init(&mut mutex as *mut _, &mutexattr as *const _), 0); |
| assert_eq!(libc::pthread_mutex_lock(&mut mutex as *mut _), 0); |
| assert_eq!(libc::pthread_mutex_trylock(&mut mutex as *mut _), libc::EBUSY); |
| assert_eq!(libc::pthread_mutex_lock(&mut mutex as *mut _), libc::EDEADLK); |
| assert_eq!(libc::pthread_mutex_unlock(&mut mutex as *mut _), 0); |
| assert_eq!(libc::pthread_mutex_trylock(&mut mutex as *mut _), 0); |
| assert_eq!(libc::pthread_mutex_unlock(&mut mutex as *mut _), 0); |
| assert_eq!(libc::pthread_mutex_unlock(&mut mutex as *mut _), libc::EPERM); |
| assert_eq!(libc::pthread_mutex_destroy(&mut mutex as *mut _), 0); |
| } |
| } |
| |
| // Only linux provides PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP, |
| // libc for macOS just has the default PTHREAD_MUTEX_INITIALIZER. |
| #[cfg(target_os = "linux")] |
| fn test_mutex_libc_static_initializer_recursive() { |
| let mutex = std::cell::UnsafeCell::new(libc::PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP); |
| unsafe { |
| assert_eq!(libc::pthread_mutex_lock(mutex.get()), 0); |
| assert_eq!(libc::pthread_mutex_trylock(mutex.get()), 0); |
| assert_eq!(libc::pthread_mutex_unlock(mutex.get()), 0); |
| assert_eq!(libc::pthread_mutex_unlock(mutex.get()), 0); |
| assert_eq!(libc::pthread_mutex_trylock(mutex.get()), 0); |
| assert_eq!(libc::pthread_mutex_lock(mutex.get()), 0); |
| assert_eq!(libc::pthread_mutex_unlock(mutex.get()), 0); |
| assert_eq!(libc::pthread_mutex_unlock(mutex.get()), 0); |
| assert_eq!(libc::pthread_mutex_unlock(mutex.get()), libc::EPERM); |
| assert_eq!(libc::pthread_mutex_destroy(mutex.get()), 0); |
| } |
| } |
| |
| struct SendPtr<T> { |
| ptr: *mut T, |
| } |
| unsafe impl<T> Send for SendPtr<T> {} |
| impl<T> Copy for SendPtr<T> {} |
| impl<T> Clone for SendPtr<T> { |
| fn clone(&self) -> Self { |
| *self |
| } |
| } |
| |
| fn check_mutex() { |
| // Specifically *not* using `Arc` to make sure there is no synchronization apart from the mutex. |
| unsafe { |
| let data = SyncUnsafeCell::new((libc::PTHREAD_MUTEX_INITIALIZER, 0)); |
| let ptr = SendPtr { ptr: data.get() }; |
| let mut threads = Vec::new(); |
| |
| for _ in 0..3 { |
| let thread = thread::spawn(move || { |
| let ptr = ptr; // circumvent per-field closure capture |
| let mutexptr = ptr::addr_of_mut!((*ptr.ptr).0); |
| assert_eq!(libc::pthread_mutex_lock(mutexptr), 0); |
| thread::yield_now(); |
| (*ptr.ptr).1 += 1; |
| assert_eq!(libc::pthread_mutex_unlock(mutexptr), 0); |
| }); |
| threads.push(thread); |
| } |
| |
| for thread in threads { |
| thread.join().unwrap(); |
| } |
| |
| let mutexptr = ptr::addr_of_mut!((*ptr.ptr).0); |
| assert_eq!(libc::pthread_mutex_trylock(mutexptr), 0); |
| assert_eq!((*ptr.ptr).1, 3); |
| } |
| } |
| |
| fn check_rwlock_write() { |
| unsafe { |
| let data = SyncUnsafeCell::new((libc::PTHREAD_RWLOCK_INITIALIZER, 0)); |
| let ptr = SendPtr { ptr: data.get() }; |
| let mut threads = Vec::new(); |
| |
| for _ in 0..3 { |
| let thread = thread::spawn(move || { |
| let ptr = ptr; // circumvent per-field closure capture |
| let rwlockptr = ptr::addr_of_mut!((*ptr.ptr).0); |
| assert_eq!(libc::pthread_rwlock_wrlock(rwlockptr), 0); |
| thread::yield_now(); |
| (*ptr.ptr).1 += 1; |
| assert_eq!(libc::pthread_rwlock_unlock(rwlockptr), 0); |
| }); |
| threads.push(thread); |
| |
| let readthread = thread::spawn(move || { |
| let ptr = ptr; // circumvent per-field closure capture |
| let rwlockptr = ptr::addr_of_mut!((*ptr.ptr).0); |
| assert_eq!(libc::pthread_rwlock_rdlock(rwlockptr), 0); |
| thread::yield_now(); |
| let val = (*ptr.ptr).1; |
| assert!(val >= 0 && val <= 3); |
| assert_eq!(libc::pthread_rwlock_unlock(rwlockptr), 0); |
| }); |
| threads.push(readthread); |
| } |
| |
| for thread in threads { |
| thread.join().unwrap(); |
| } |
| |
| let rwlockptr = ptr::addr_of_mut!((*ptr.ptr).0); |
| assert_eq!(libc::pthread_rwlock_tryrdlock(rwlockptr), 0); |
| assert_eq!((*ptr.ptr).1, 3); |
| } |
| } |
| |
| fn check_rwlock_read_no_deadlock() { |
| unsafe { |
| let l1 = SyncUnsafeCell::new(libc::PTHREAD_RWLOCK_INITIALIZER); |
| let l1 = SendPtr { ptr: l1.get() }; |
| let l2 = SyncUnsafeCell::new(libc::PTHREAD_RWLOCK_INITIALIZER); |
| let l2 = SendPtr { ptr: l2.get() }; |
| |
| // acquire l1 and hold it until after the other thread is done |
| assert_eq!(libc::pthread_rwlock_rdlock(l1.ptr), 0); |
| let handle = thread::spawn(move || { |
| let l1 = l1; // circumvent per-field closure capture |
| let l2 = l2; // circumvent per-field closure capture |
| // acquire l2 before the other thread |
| assert_eq!(libc::pthread_rwlock_rdlock(l2.ptr), 0); |
| thread::yield_now(); |
| assert_eq!(libc::pthread_rwlock_rdlock(l1.ptr), 0); |
| thread::yield_now(); |
| assert_eq!(libc::pthread_rwlock_unlock(l1.ptr), 0); |
| assert_eq!(libc::pthread_rwlock_unlock(l2.ptr), 0); |
| }); |
| thread::yield_now(); |
| assert_eq!(libc::pthread_rwlock_rdlock(l2.ptr), 0); |
| handle.join().unwrap(); |
| } |
| } |
| |
| fn check_cond() { |
| unsafe { |
| let mut cond: MaybeUninit<libc::pthread_cond_t> = MaybeUninit::uninit(); |
| assert_eq!(libc::pthread_cond_init(cond.as_mut_ptr(), ptr::null()), 0); |
| let cond = SendPtr { ptr: cond.as_mut_ptr() }; |
| |
| let mut mutex: libc::pthread_mutex_t = libc::PTHREAD_MUTEX_INITIALIZER; |
| let mutex = SendPtr { ptr: &mut mutex }; |
| |
| let mut data = 0; |
| let data = SendPtr { ptr: &mut data }; |
| |
| let t = thread::spawn(move || { |
| let mutex = mutex; // circumvent per-field closure capture |
| let cond = cond; |
| let data = data; |
| assert_eq!(libc::pthread_mutex_lock(mutex.ptr), 0); |
| assert!(data.ptr.read() == 0); |
| data.ptr.write(1); |
| libc::pthread_cond_wait(cond.ptr, mutex.ptr); |
| assert!(data.ptr.read() == 3); |
| data.ptr.write(4); |
| assert_eq!(libc::pthread_mutex_unlock(mutex.ptr), 0); |
| }); |
| |
| thread::yield_now(); |
| |
| assert_eq!(libc::pthread_mutex_lock(mutex.ptr), 0); |
| assert!(data.ptr.read() == 1); |
| data.ptr.write(2); |
| assert_eq!(libc::pthread_cond_signal(cond.ptr), 0); |
| thread::yield_now(); // the other thread wakes up but can't get the lock yet |
| assert!(data.ptr.read() == 2); |
| data.ptr.write(3); |
| assert_eq!(libc::pthread_mutex_unlock(mutex.ptr), 0); |
| |
| thread::yield_now(); // now the other thread gets the lock back |
| |
| assert_eq!(libc::pthread_mutex_lock(mutex.ptr), 0); |
| assert!(data.ptr.read() == 4); |
| assert_eq!(libc::pthread_cond_broadcast(cond.ptr), 0); // just a smoke test |
| assert_eq!(libc::pthread_mutex_unlock(mutex.ptr), 0); |
| |
| t.join().unwrap(); |
| } |
| } |
| |
| fn check_condattr() { |
| unsafe { |
| // Just smoke-testing that these functions can be called. |
| let mut attr: MaybeUninit<libc::pthread_condattr_t> = MaybeUninit::uninit(); |
| assert_eq!(libc::pthread_condattr_init(attr.as_mut_ptr()), 0); |
| |
| #[cfg(not(target_os = "macos"))] // setclock-getclock do not exist on macOS |
| { |
| let clock_id = libc::CLOCK_MONOTONIC; |
| assert_eq!(libc::pthread_condattr_setclock(attr.as_mut_ptr(), clock_id), 0); |
| let mut check_clock_id = MaybeUninit::<libc::clockid_t>::uninit(); |
| assert_eq!( |
| libc::pthread_condattr_getclock(attr.as_mut_ptr(), check_clock_id.as_mut_ptr()), |
| 0 |
| ); |
| assert_eq!(check_clock_id.assume_init(), clock_id); |
| } |
| |
| let mut cond: MaybeUninit<libc::pthread_cond_t> = MaybeUninit::uninit(); |
| assert_eq!(libc::pthread_cond_init(cond.as_mut_ptr(), attr.as_ptr()), 0); |
| assert_eq!(libc::pthread_condattr_destroy(attr.as_mut_ptr()), 0); |
| assert_eq!(libc::pthread_cond_destroy(cond.as_mut_ptr()), 0); |
| } |
| } |
| |
| // std::sync::RwLock does not even used pthread_rwlock any more. |
| // Do some smoke testing of the API surface. |
| fn test_rwlock_libc_static_initializer() { |
| let rw = std::cell::UnsafeCell::new(libc::PTHREAD_RWLOCK_INITIALIZER); |
| unsafe { |
| assert_eq!(libc::pthread_rwlock_rdlock(rw.get()), 0); |
| assert_eq!(libc::pthread_rwlock_rdlock(rw.get()), 0); |
| assert_eq!(libc::pthread_rwlock_unlock(rw.get()), 0); |
| assert_eq!(libc::pthread_rwlock_tryrdlock(rw.get()), 0); |
| assert_eq!(libc::pthread_rwlock_unlock(rw.get()), 0); |
| assert_eq!(libc::pthread_rwlock_trywrlock(rw.get()), libc::EBUSY); |
| assert_eq!(libc::pthread_rwlock_unlock(rw.get()), 0); |
| |
| assert_eq!(libc::pthread_rwlock_wrlock(rw.get()), 0); |
| assert_eq!(libc::pthread_rwlock_tryrdlock(rw.get()), libc::EBUSY); |
| assert_eq!(libc::pthread_rwlock_trywrlock(rw.get()), libc::EBUSY); |
| assert_eq!(libc::pthread_rwlock_unlock(rw.get()), 0); |
| |
| assert_eq!(libc::pthread_rwlock_trywrlock(rw.get()), 0); |
| assert_eq!(libc::pthread_rwlock_tryrdlock(rw.get()), libc::EBUSY); |
| assert_eq!(libc::pthread_rwlock_trywrlock(rw.get()), libc::EBUSY); |
| assert_eq!(libc::pthread_rwlock_unlock(rw.get()), 0); |
| |
| assert_eq!(libc::pthread_rwlock_destroy(rw.get()), 0); |
| } |
| } |