diff --git a/maitake/src/loom.rs b/maitake/src/loom.rs index c8ed9d1b..44dc3d74 100644 --- a/maitake/src/loom.rs +++ b/maitake/src/loom.rs @@ -141,12 +141,35 @@ mod inner { f(self.0.get()) } + #[inline(always)] + pub(crate) fn get(&self) -> ConstPtr { + ConstPtr(self.0.get()) + } + #[inline(always)] pub(crate) fn get_mut(&self) -> MutPtr { MutPtr(self.0.get()) } } + #[derive(Debug)] + pub(crate) struct ConstPtr(*const T); + + impl ConstPtr { + #[inline(always)] + pub(crate) unsafe fn deref(&self) -> &T { + &*self.0 + } + + #[inline(always)] + pub fn with(&self, f: F) -> R + where + F: FnOnce(*const T) -> R, + { + f(self.0) + } + } + #[derive(Debug)] pub(crate) struct MutPtr(*mut T); diff --git a/maitake/src/sync.rs b/maitake/src/sync.rs index cff89bc8..024ec392 100644 --- a/maitake/src/sync.rs +++ b/maitake/src/sync.rs @@ -4,6 +4,9 @@ //! //! - [`Mutex`]: a fairly queued, asynchronous [mutual exclusion lock], for //! protecting shared data +//! - [`RwLock`]: a fairly queued, asynchronous [readers-writer lock], which +//! allows concurrent read access to shared data while ensuring write +//! access is exclusive //! - [`Semaphore`]: an asynchronous [counting semaphore], for limiting the //! number of tasks which may run concurrently //! - [`WaitCell`], a cell that stores a *single* waiting task's [`Waker`], so @@ -13,11 +16,13 @@ //! - [`WaitMap`], a set of waiting tasks associated with keys, in which a task //! can be woken by its key //! -//! [mutual exclusion lock]:https://en.wikipedia.org/wiki/Mutual_exclusion +//! [mutual exclusion lock]: https://en.wikipedia.org/wiki/Mutual_exclusion +//! [readers-writer lock]: https://en.wikipedia.org/wiki/Readers%E2%80%93writer_lock //! [counting semaphore]: https://en.wikipedia.org/wiki/Semaphore_(programming) //! [`Waker`]: core::task::Waker #![warn(missing_docs, missing_debug_implementations)] pub mod mutex; +pub mod rwlock; pub mod semaphore; pub mod wait_cell; pub mod wait_map; @@ -29,6 +34,8 @@ pub use self::mutex::OwnedMutexGuard; #[doc(inline)] pub use self::mutex::{Mutex, MutexGuard}; #[doc(inline)] +pub use self::rwlock::{RwLock, RwLockReadGuard, RwLockWriteGuard}; +#[doc(inline)] pub use self::semaphore::Semaphore; #[doc(inline)] pub use self::wait_cell::WaitCell; diff --git a/maitake/src/sync/mutex.rs b/maitake/src/sync/mutex.rs index 92c01a60..c825a8d7 100644 --- a/maitake/src/sync/mutex.rs +++ b/maitake/src/sync/mutex.rs @@ -1,6 +1,8 @@ -//! An asynchronous mutual exclusion lock. +//! An asynchronous [mutual exclusion lock][mutex]. //! //! See the documentation on the [`Mutex`] type for details. +//! +/// [mutex]: https://en.wikipedia.org/wiki/Mutual_exclusion use crate::{ loom::cell::{MutPtr, UnsafeCell}, sync::wait_queue::{self, WaitQueue}, diff --git a/maitake/src/sync/rwlock.rs b/maitake/src/sync/rwlock.rs new file mode 100644 index 00000000..613647e6 --- /dev/null +++ b/maitake/src/sync/rwlock.rs @@ -0,0 +1,468 @@ +//! An asynchronous [readers-writer lock]. +//! +//! See the documentation for the [`RwLock`] type for details. +//! +//! [readers-writer lock]: https://en.wikipedia.org/wiki/Readers%E2%80%93writer_lock +use super::semaphore::{self, Semaphore}; +use crate::loom::cell::{self, UnsafeCell}; +use core::ops::{Deref, DerefMut}; +use mycelium_util::fmt; + +#[cfg(all(test, loom))] +mod loom; + +#[cfg(all(test, not(loom)))] +mod tests; + +/// An asynchronous [readers-writer lock]. +/// +/// This type of lock protects shared data by allowing either multiple +/// concurrent readers (shared access), or a single writer (exclusive access) at +/// a given point in time. If the shared data must be modified, write access +/// must be acquired, preventing other threads from accessing the data while it +/// is being written, but multiple threads can read the shared data when it is +/// not being mutated. +/// +/// This is in contrast to a [`Mutex`](super::Mutex), which only ever allows a +/// single core/thread to access the shared data at any point in time. In some +/// cases, such as when a large number of readers need to access the shared data +/// without modifying it, using a `RwLock` can be more efficient than a [`Mutex`]. +/// +/// # Usage +/// +/// The type parameter `T` represents the data that this lock protects. It is +/// required that `T` satisfies [`Send`] to be shared across threads and +/// [`Sync`] to allow concurrent access through readers. The RAII guards +/// returned from the locking methods implement [`Deref`] (and [`DerefMut`] +/// for the `write` methods) to allow access to the content of the lock. +/// +/// The [`read`] method acquires read access to the lock, returning a +/// [`RwLockReadGuard`]. If the lock is currently locked for write access, the +/// [`read`] method will wait until the write access completes before allowing +/// read access to the locked data. +/// +/// The [`write`] method acquires write access to the lock, returning a +/// [`RwLockWriteGuard`], which implements [`DerefMut`]. If the lock is +/// currently locked for reading *or* writing, the [`write`] method will wait +/// until all current reads or the current write completes before allowing write +/// access to the locked data. +/// +/// # Priority Policy +/// +/// The priority policy of this lock is _fair_ (or [_write-preferring_]), in +/// order to ensure that readers cannot starve writers. Fairness is ensured +/// using a first-in, first-out queue for the tasks awaiting the lock; if a task +/// that wishes to acquire the write lock is at the head of the queue, read +/// locks will not be given out until the write lock has been released. This is +/// in contrast to the Rust standard library's [`std::sync::RwLock`], where the +/// priority policy is dependent on the operating system's implementation. +/// +/// # Examples +/// +/// ``` +/// use maitake::sync::RwLock; +/// +/// async fn example() { +/// let lock = RwLock::new(5); +/// +/// // many reader locks can be held at once +/// { +/// let r1 = lock.read().await; +/// let r2 = lock.read().await; +/// assert_eq!(*r1, 5); +/// assert_eq!(*r2, 5); +/// } // read locks are dropped at this point +/// +/// // only one write lock may be held, however +/// { +/// let mut w = lock.write().await; +/// *w += 1; +/// assert_eq!(*w, 6); +/// } // write lock is dropped here +/// } +/// +/// # use maitake::scheduler::Scheduler; +/// # let scheduler = std::sync::Arc::new(Scheduler::new()); +/// # scheduler.spawn(example()); +/// # scheduler.tick(); +/// ``` +/// +/// [`read`]: Self::read +/// [`write`]: Self::write +/// [readers-writer lock]: https://en.wikipedia.org/wiki/Readers%E2%80%93writer_lock +/// [_write-preferring_]: https://en.wikipedia.org/wiki/Readers%E2%80%93writer_lock#Priority_policies +/// [`std::sync::RwLock`]: https://doc.rust-lang.org/stable/std/sync/struct.RwLock.html +pub struct RwLock { + /// The semaphore used to control access to `data`. + /// + /// To read `data`, a single permit must be acquired. To write to `data`, + /// all the permits in the semaphore must be acquired. + sem: Semaphore, + + /// The data protected by the lock. + data: UnsafeCell, +} + +/// [RAII] structure used to release the shared read access of a [`RwLock`] when +/// dropped. +/// +/// The data protected by the [`RwLock`] can be accessed through this guard via +/// its [`Deref`](#impl-Deref) implementation. +/// +/// This guard can be held across any `.await` point, as it implements +/// [`Send`]. +/// +/// This structure is created by the [`read`] and [`try_read`] methods on +/// [`RwLock`]. +/// +/// [RAII]: https://rust-unofficial.github.io/patterns/patterns/behavioural/RAII.html +/// [`read`]: RwLock::read +/// [`try_read`]: RwLock::try_read +#[must_use = "if unused, the `RwLock` will immediately unlock"] +pub struct RwLockReadGuard<'lock, T: ?Sized> { + /// /!\ WARNING: semi-load-bearing drop order /!\ + /// + /// This struct's field ordering is important for Loom tests; the `ConstPtr` + /// must be dropped before the permit, as dropping the permit may wake + /// another task that wants to access the cell, and Loom will still consider the data to + /// be "accessed" until the `ConstPtr` is dropped. + data: cell::ConstPtr, + _permit: semaphore::Permit<'lock>, +} + +/// [RAII] structure used to release the exclusive write access of a [`RwLock`] when +/// dropped. +/// +/// The data protected by the [`RwLock`] can be accessed through this guard via +/// its [`Deref`](#impl-Deref) and [`DerefMut`](#impl-Deref) implementations. +/// +/// This guard can be held across any `.await` point, as it implements +/// [`Send`]. +/// +/// This structure is created by the [`write`] and [`try_write`] methods on +/// [`RwLock`]. +/// +/// [RAII]: https://rust-unofficial.github.io/patterns/patterns/behavioural/RAII.html +/// [`write`]: RwLock::write +/// [`try_write`]: RwLock::try_write +#[must_use = "if unused, the `RwLock` will immediately unlock"] +pub struct RwLockWriteGuard<'lock, T: ?Sized> { + /// /!\ WARNING: semi-load-bearing drop order /!\ + /// + /// This struct's field ordering is important for Loom tests; the `MutPtr` + /// must be dropped before the permit, as dropping the permit may wake + /// another task that wants to access the cell, and Loom will still consider + /// the data to be "accessed mutably" until the `MutPtr` is dropped. + data: cell::MutPtr, + _permit: semaphore::Permit<'lock>, +} + +// === impl RwLock === + +impl RwLock { + loom_const_fn! { + /// Returns a new `RwLock` protecting the provided `data`, in an + /// unlocked state. + /// + /// # Examples + /// + /// ``` + /// use maitake::sync::RwLock; + /// + /// let lock = RwLock::new(5); + /// # drop(lock) + /// ``` + /// + /// Because this is a `const fn`, it may be used in `static` + /// initializers: + /// + /// ``` + /// use maitake::sync::RwLock; + /// + /// static LOCK: RwLock = RwLock::new(5); + /// ``` + #[must_use] + pub fn new(data: T) -> Self { + Self { + sem: Semaphore::new(Self::MAX_READERS), + data: UnsafeCell::new(data), + } + } + } +} + +impl RwLock { + const MAX_READERS: usize = Semaphore::MAX_PERMITS; + + /// Locks this `RwLock` with shared read access, causing the current task + /// to yield until the lock has been acquired. + /// + /// If the lock is locked for write access, the calling task will yield and + /// wait until there are no writers which hold the lock. There may be other + /// readers inside the lock when the task resumes. + /// + /// Note that under the [priority policy] of [`RwLock`], read locks are not + /// granted until prior write locks, to prevent starvation. Therefore + /// deadlock may occur if a read lock is held by the current task, a write + /// lock attempt is made, and then a subsequent read lock attempt is made + /// by the current task. + /// + /// Returns [an RAII guard] which will release this read access of the + /// `RwLock` when dropped. + /// + /// # Cancellation + /// + /// This method [uses a queue to fairly distribute locks][priority policy] + /// in the order they were requested. Cancelling a call to `read` results + /// in the calling task losing its place in the queue. + /// + /// # Examples + /// + /// ``` + /// use maitake::scheduler::Scheduler; + /// use maitake::sync::RwLock; + /// use alloc::sync::Arc; + /// + /// let scheduler = Arc::new(Scheduler::new()); + /// + /// let lock = Arc::new(RwLock::new(1)); + /// // hold the lock for reading in `main`. + /// let n = lock + /// .try_read() + /// .expect("read lock must be acquired, as the lock is unlocked"); + /// assert_eq!(*n, 1); + /// + /// scheduler.spawn({ + /// let lock = lock.clone(); + /// async move { + /// // While main has an active read lock, this task can acquire + /// // one too. + /// let n = lock.read().await; + /// assert_eq!(*n, 1); + /// } + /// }); + /// + /// scheduler.tick(); + /// ``` + /// + /// [priority policy]: Self#priority-policy + /// [an RAII guard]: + pub async fn read(&self) -> RwLockReadGuard<'_, T> { + let _permit = self + .sem + .acquire(1) + .await + .expect("RwLock semaphore should never be closed"); + RwLockReadGuard { + data: self.data.get(), + _permit, + } + } + + /// Locks this `RwLock` with exclusive write access, causing the current + /// task to yield until the lock has been acquired. + /// + /// If other tasks are holding a read or write lock, the calling task will + /// wait until the write lock or all read locks are released. + /// + /// Returns [an RAII guard] which will release the write access of this + /// `RwLock` when dropped. + /// + /// # Cancellation + /// + /// This method [uses a queue to fairly distribute + /// locks](Self#priority-policy) in the order they were requested. + /// Cancelling a call to `write` results in the calling task losing its place + /// in the queue. + /// + /// # Examples + /// + /// ``` + /// use maitake::scheduler::Scheduler; + /// use maitake::sync::RwLock; + /// use alloc::sync::Arc; + /// + /// let scheduler = Arc::new(Scheduler::new()); + /// + /// let lock = Arc::new(RwLock::new(1)); + /// + /// scheduler.spawn(async move { + /// let mut guard = lock.write().await; + /// *guard += 1; + /// }); + /// + /// scheduler.tick(); + /// ``` + pub async fn write(&self) -> RwLockWriteGuard<'_, T> { + let _permit = self + .sem + .acquire(Self::MAX_READERS) + .await + .expect("RwLock semaphore should never be closed"); + RwLockWriteGuard { + data: self.data.get_mut(), + _permit, + } + } + + /// Attempts to acquire this `RwLock` for shared read access, without + /// waiting. + /// + /// If the access couldn't be acquired immediately, this method returns + /// [`None`] rather than waiting. + /// + /// Otherwise, [an RAII guard] is returned, which allows read access to the + /// protected data and will release that access when dropped. + /// + /// # Examples + /// + /// ``` + /// use maitake::sync::RwLock; + /// + /// let lock = RwLock::new(1); + /// + /// let mut write_guard = lock + /// .try_write() + /// .expect("lock is unlocked, so write access should be acquired"); + /// *write_guard += 1; + /// + /// // because a write guard is held, we cannot acquire the read lock, so + /// // this will return `None`. + /// assert!(lock.try_read().is_none()); + /// ``` + /// + /// [an RAII guard]: RwLockReadGuard + pub fn try_read(&self) -> Option> { + match self.sem.try_acquire(1) { + Ok(_permit) => Some(RwLockReadGuard { + data: self.data.get(), + _permit, + }), + Err(semaphore::TryAcquireError::InsufficientPermits) => None, + Err(semaphore::TryAcquireError::Closed) => { + unreachable!("RwLock semaphore should never be closed") + } + } + } + + /// Attempts to acquire this `RwLock` for exclusive write access, without + /// waiting. + /// + /// If the access couldn't be acquired immediately, this method returns + /// [`None`] rather than waiting. + /// + /// Otherwise, [an RAII guard] is returned, which allows write access to the + /// protected data and will release that access when dropped. + /// + /// # Examples + /// + /// ``` + /// use maitake::sync::RwLock; + /// + /// let lock = RwLock::new(1); + /// + /// let read_guard = lock + /// .try_read() + /// .expect("lock is unlocked, so read access should be acquired"); + /// assert_eq!(*read_guard, 1); + /// + /// // because a read guard is held, we cannot acquire the write lock, so + /// // this will return `None`. + /// assert!(lock.try_write().is_none()); + /// ``` + /// + /// [an RAII guard]: RwLockWriteGuard + pub fn try_write(&self) -> Option> { + match self.sem.try_acquire(Self::MAX_READERS) { + Ok(_permit) => Some(RwLockWriteGuard { + data: self.data.get_mut(), + _permit, + }), + Err(semaphore::TryAcquireError::InsufficientPermits) => None, + Err(semaphore::TryAcquireError::Closed) => { + unreachable!("RwLock semaphore should never be closed") + } + } + } +} + +impl fmt::Debug for RwLock { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("RwLock") + .field("sem", &self.sem) + .field("data", &fmt::opt(&self.try_read()).or_else("")) + .finish() + } +} + +// Safety: if `T` is `Send + Sync`, an `RwLock` can safely be sent or shared +// between threads. If `T` wasn't `Send`, this would be unsafe, since the +// `RwLock` exposes access to the `T`. +unsafe impl Send for RwLock where T: ?Sized + Send {} +unsafe impl Sync for RwLock where T: ?Sized + Send + Sync {} + +// === impl RwLockReadGuard === + +impl Deref for RwLockReadGuard<'_, T> { + type Target = T; + + #[inline] + fn deref(&self) -> &Self::Target { + unsafe { + // safety: we are holding the semaphore permit that ensures the lock + // cannot be accessed mutably. + self.data.deref() + } + } +} + +impl fmt::Debug for RwLockReadGuard<'_, T> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.deref().fmt(f) + } +} + +// Safety: A read guard can be shared or sent between threads as long as `T` is +// `Sync`. It can implement `Send` even if `T` does not implement `Send`, as +// long as `T` is `Sync`, because the read guard only permits borrowing the `T`. +unsafe impl Send for RwLockReadGuard<'_, T> where T: ?Sized + Sync {} +unsafe impl Sync for RwLockReadGuard<'_, T> where T: ?Sized + Send + Sync {} + +// === impl RwLockWriteGuard === + +impl Deref for RwLockWriteGuard<'_, T> { + type Target = T; + + #[inline] + fn deref(&self) -> &Self::Target { + unsafe { + // safety: we are holding all the semaphore permits, so the data + // inside the lock cannot be accessed by another thread. + self.data.deref() + } + } +} + +impl DerefMut for RwLockWriteGuard<'_, T> { + #[inline] + fn deref_mut(&mut self) -> &mut Self::Target { + unsafe { + // safety: we are holding all the semaphore permits, so the data + // inside the lock cannot be accessed by another thread. + self.data.deref() + } + } +} + +impl fmt::Debug for RwLockWriteGuard<'_, T> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.deref().fmt(f) + } +} + +// Safety: Unlike the read guard, `T` must be both `Send` and `Sync` for the +// write guard to be `Send`, because the mutable access provided by the write +// guard can be used to `mem::replace` or `mem::take` the value, transferring +// ownership of it across threads. +unsafe impl Send for RwLockWriteGuard<'_, T> where T: ?Sized + Send + Sync {} +unsafe impl Sync for RwLockWriteGuard<'_, T> where T: ?Sized + Send + Sync {} diff --git a/maitake/src/sync/rwlock/loom.rs b/maitake/src/sync/rwlock/loom.rs new file mode 100644 index 00000000..6d4c9839 --- /dev/null +++ b/maitake/src/sync/rwlock/loom.rs @@ -0,0 +1,54 @@ +use super::*; +use crate::loom::{self, thread, future, sync::Arc}; + +#[test] +fn write() { + const WRITERS: usize = 2; + + loom::model(|| { + let lock = Arc::new(RwLock::::new(0)); + let threads = (0..WRITERS).map(|_| { + let lock = lock.clone(); + thread::spawn(writer(lock)) + }).collect::>(); + + for thread in threads { + thread.join().expect("writer thread mustn't panic"); + } + + let guard = future::block_on(lock.read()); + assert_eq!(*guard, WRITERS, "final state must equal number of writers"); + }); +} + +#[test] +fn read_write() { + const WRITERS: usize = 2; + + loom::model(|| { + let lock = Arc::new(RwLock::::new(0)); + let w_threads = (0..WRITERS).map(|_| { + let lock = lock.clone(); + thread::spawn(writer(lock)) + }).collect::>(); + + { + let guard = future::block_on(lock.read()); + assert!(*guard == 0 || *guard == 1 || *guard == 2); + } + + for thread in w_threads { + thread.join().expect("writer thread mustn't panic") + } + + let guard = future::block_on(lock.read()); + assert_eq!(*guard, WRITERS, "final state must equal number of writers"); + }); +} + +fn writer(lock: Arc>) -> impl FnOnce() { + move || future::block_on(async { + let mut guard = lock.write().await; + *guard += 1; + }) +} \ No newline at end of file diff --git a/maitake/src/sync/rwlock/tests.rs b/maitake/src/sync/rwlock/tests.rs new file mode 100644 index 00000000..369204c1 --- /dev/null +++ b/maitake/src/sync/rwlock/tests.rs @@ -0,0 +1,113 @@ +use super::*; +use crate::util; +use tokio_test::{assert_pending, assert_ready, task}; + +#[test] +fn lock_is_send_sync() { + util::test::assert_send_sync::>(); +} + +#[test] +fn read_guard_is_send_sync() { + util::test::assert_send_sync::>(); +} + +#[test] +fn write_guard_is_send_sync() { + util::test::assert_send_sync::>(); +} + +// multiple reads should be Ready +#[test] +fn read_shared() { + let lock = RwLock::new(100); + + let mut t1 = task::spawn(lock.read()); + let _g1 = assert_ready!(t1.poll()); + let mut t2 = task::spawn(lock.read()); + let _g2 = assert_ready!(t2.poll()); +} + +// When there is an active shared owner, exclusive access should not be possible +#[test] +fn write_shared_pending() { + let lock = RwLock::new(100); + let mut t1 = task::spawn(lock.read()); + + let _g1 = assert_ready!(t1.poll()); + let mut t2 = task::spawn(lock.write()); + assert_pending!(t2.poll()); +} + +// When there is an active exclusive owner, subsequent exclusive access should not be possible +#[test] +fn read_exclusive_pending() { + let lock = RwLock::new(100); + let mut t1 = task::spawn(lock.write()); + + let _g1 = assert_ready!(t1.poll()); + let mut t2 = task::spawn(lock.read()); + assert_pending!(t2.poll()); +} + +// When there is an active exclusive owner, subsequent exclusive access should not be possible +#[test] +fn write_exclusive_pending() { + let lock = RwLock::new(100); + let mut t1 = task::spawn(lock.write()); + + let _g1 = assert_ready!(t1.poll()); + let mut t2 = task::spawn(lock.write()); + assert_pending!(t2.poll()); +} + +// When there is an active shared owner, exclusive access should be possible after shared is dropped +#[test] +fn write_shared_drop() { + let lock = RwLock::new(100); + let mut t1 = task::spawn(lock.read()); + + let g1 = assert_ready!(t1.poll()); + let mut t2 = task::spawn(lock.write()); + assert_pending!(t2.poll()); + drop(g1); + assert!(t2.is_woken()); + let _g2 = assert_ready!(t2.poll()); +} + +// when there is an active shared owner, and exclusive access is triggered, +// subsequent shared access should not be possible as write gathers all the available semaphore permits +#[test] +fn write_read_shared_pending() { + let lock = RwLock::new(100); + let mut t1 = task::spawn(lock.read()); + let _g1 = assert_ready!(t1.poll()); + + let mut t2 = task::spawn(lock.read()); + let _g2 = assert_ready!(t2.poll()); + + let mut t3 = task::spawn(lock.write()); + assert_pending!(t3.poll()); + + let mut t4 = task::spawn(lock.read()); + assert_pending!(t4.poll()); +} + +// when there is an active shared owner, and exclusive access is triggered, +// reading should be possible after pending exclusive access is dropped +#[test] +fn write_read_shared_drop_pending() { + let lock = RwLock::new(100); + let mut t1 = task::spawn(lock.read()); + let _g1 = assert_ready!(t1.poll()); + + let mut t2 = task::spawn(lock.write()); + assert_pending!(t2.poll()); + + let mut t3 = task::spawn(lock.read()); + assert_pending!(t3.poll()); + drop(t2); + + assert!(t3.is_woken()); + let _t3 = assert_ready!(t3.poll()); +} diff --git a/maitake/src/sync/semaphore/tests.rs b/maitake/src/sync/semaphore/tests.rs index 654a09f2..781b0a4c 100644 --- a/maitake/src/sync/semaphore/tests.rs +++ b/maitake/src/sync/semaphore/tests.rs @@ -1,20 +1,19 @@ use super::*; - -fn assert_send_sync() {} +use crate::util; #[test] fn semaphore_is_send_and_sync() { - assert_send_sync::(); + util::test::assert_send_sync::(); } #[test] fn permit_is_send_and_sync() { - assert_send_sync::>(); + util::test::assert_send_sync::>(); } #[test] fn acquire_is_send_and_sync() { - assert_send_sync::>(); + util::test::assert_send_sync::>(); } #[cfg(feature = "alloc")] @@ -26,12 +25,12 @@ mod alloc { #[test] fn owned_permit_is_send_and_sync() { - assert_send_sync::(); + util::test::assert_send_sync::(); } #[test] fn acquire_owned_is_send_and_sync() { - assert_send_sync::(); + util::test::assert_send_sync::(); } #[test] diff --git a/maitake/src/util.rs b/maitake/src/util.rs index 9e0d36d5..51e2863e 100644 --- a/maitake/src/util.rs +++ b/maitake/src/util.rs @@ -105,10 +105,22 @@ pub(crate) unsafe fn non_null(ptr: *mut T) -> NonNull { } #[cfg(all(test, not(loom)))] -pub(crate) fn trace_init() { - use tracing_subscriber::filter::LevelFilter; - let _ = tracing_subscriber::fmt() - .with_max_level(LevelFilter::TRACE) - .with_test_writer() - .try_init(); +pub(crate) use self::test::trace_init; + +#[cfg(all(test, not(loom)))] +pub(crate) mod test { + pub(crate) fn trace_init() { + use tracing_subscriber::filter::LevelFilter; + let _ = tracing_subscriber::fmt() + .with_max_level(LevelFilter::TRACE) + .with_test_writer() + .try_init(); + } + + #[allow(dead_code)] + pub(crate) fn assert_send() {} + + #[allow(dead_code)] + pub(crate) fn assert_sync() {} + pub(crate) fn assert_send_sync() {} }