//! A synchronization primitive for notifying async tasks and threads. //! //! This is a variant of a conditional variable that is heavily inspired by eventcounts invented //! by Dmitry Vyukov: http://www.1024cores.net/home/lock-free-algorithms/eventcounts use std::cell::Cell; use std::future::Future; use std::mem::{self, ManuallyDrop}; use std::ops::{Deref, DerefMut}; use std::pin::Pin; use std::ptr::NonNull; use std::sync::atomic::{self, AtomicPtr, AtomicUsize, Ordering}; use std::sync::{Arc, Mutex, MutexGuard}; use std::task::{Context, Poll, Waker}; use std::thread::{self, Thread}; /// A bit set inside `Signal` when there is at least one listener that has already been notified. const NOTIFIED: usize = 1 << 0; /// A bit set inside `Signal` when there is at least one notifiable listener. const NOTIFIABLE: usize = 1 << 1; /// Inner state of `Signal`. struct Inner { /// Holds bits `NOTIFIED` and `NOTIFIABLE`. flags: AtomicUsize, /// A linked list holding registered listeners. list: Mutex, } impl Inner { /// Locks the list. fn lock(&self) -> ListGuard<'_> { ListGuard { inner: self, guard: self.list.lock().unwrap(), } } } /// A synchronization primitive for notifying async tasks and threads. /// /// Listeners can be registered using `listen()`. There are two ways of notifying listeners: /// /// 1. `notify_one()` notifies one listener. /// 2. `notify_all()` notifies all listeners. /// /// If there are no active listeners at the time a notification is sent, it simply gets lost. /// /// Note that `notify_one()` does not notify one *additional* listener - it only makes sure /// *at least* one listener among the active ones is notified. /// /// There are two ways for a listener to wait for a notification: /// /// 1. In an asynchronous manner using `.await`. /// 2. In a blocking manner by calling `wait()` on it. /// /// If a notified listener is dropped without ever waiting for a notification, dropping will notify /// another another active listener. /// /// Listeners are registered and notified in the first-in first-out fashion, ensuring fairness. pub struct Signal { /// A pointer to heap-allocated inner state. /// /// This pointer is initially null and gets lazily initialized on first use. Semantically, it /// is an `Arc` so it's important to keep in mind that it contributes to the `Arc`s /// reference count. inner: AtomicPtr, } unsafe impl Send for Signal {} unsafe impl Sync for Signal {} impl Signal { /// Creates a new `Signal`. #[inline] pub fn new() -> Signal { Signal { inner: AtomicPtr::default(), } } /// Returns a guard listening for a notification. #[cold] pub fn listen(&self) -> SignalListener { let inner = self.inner(); let listener = SignalListener { inner: unsafe { Arc::clone(&ManuallyDrop::new(Arc::from_raw(inner))) }, entry: Some(inner.lock().insert()), }; // Make sure the listener is registered before whatever happens next. full_fence(); listener } /// Notifies a single active listener. /// /// Note that this does not notify one *additional* listener - it only makes sure *at least* /// one listener among the active ones is notified. #[inline] pub fn notify_one(&self) { let inner = self.inner(); // Make sure the notification comes after whatever triggered it. full_fence(); // Notify if no active listeners have been notified and there is at least one listener. let flags = inner.flags.load(Ordering::Relaxed); if flags & NOTIFIED == 0 && flags & NOTIFIABLE != 0 { inner.lock().notify(false); } } /// Notifies all active listeners. #[inline] pub fn notify_all(&self) { let inner = self.inner(); // Make sure the notification comes after whatever triggered it. full_fence(); // Notify if there is at least one listener. if inner.flags.load(Ordering::Relaxed) & NOTIFIABLE != 0 { inner.lock().notify(true); } } /// Returns a reference to the inner state. fn inner(&self) -> &Inner { let mut inner = self.inner.load(Ordering::Acquire); // Initialize the state if this is its first use. if inner.is_null() { // Allocate on the heap. let new = Arc::new(Inner { flags: AtomicUsize::new(0), list: Mutex::new(List { head: None, tail: None, len: 0, notifiable: 0, }), }); // Convert the heap-allocated state into a raw pointer. let new = Arc::into_raw(new) as *mut Inner; // Attempt to replace the null-pointer with the new state pointer. inner = self.inner.compare_and_swap(inner, new, Ordering::AcqRel); // Check if the old pointer value was indeed null. if inner.is_null() { // If yes, then use the new state pointer. inner = new; } else { // If not, that means a concurrent operation has initialized the state. // In that case, use the old pointer and deallocate the new one. unsafe { drop(Arc::from_raw(new)); } } } unsafe { &*inner } } } impl Drop for Signal { #[inline] fn drop(&mut self) { let inner: *mut Inner = *self.inner.get_mut(); // If the state pointer has been initialized, deallocate it. if !inner.is_null() { unsafe { drop(Arc::from_raw(inner)); } } } } impl Default for Signal { fn default() -> Signal { Signal::new() } } /// A guard waiting for a notification from a `Signal`. /// /// There are two ways for a listener to wait for a notification: /// /// 1. In an asynchronous manner using `.await`. /// 2. In a blocking manner by calling `wait()` on it. /// /// If a notified listener is dropped without ever waiting for a notification, dropping will notify /// another another active listener. pub struct SignalListener { /// A reference to `Signal`s inner state. inner: Arc, /// A pointer to this listener's entry in the linked list. entry: Option>, } unsafe impl Send for SignalListener {} unsafe impl Sync for SignalListener {} impl SignalListener { /// Blocks until a notification is received. pub fn wait(mut self) { // Take out the entry pointer and set it to `None`. let entry = match self.entry.take() { None => unreachable!("cannot wait twice on a `SignalListener`"), Some(entry) => entry, }; // Set this listener's state to `Waiting`. { let mut list = self.inner.lock(); let e = unsafe { entry.as_ref() }; // Do a dummy replace operation in order to take out the state. match e.state.replace(State::Notified) { State::Notified => { // If this listener has been notified, remove it from the list and return. list.remove(entry); return; } // Otherwise, set the state to `Waiting`. _ => e.state.set(State::Waiting(thread::current())), } } // Wait until a notification is received. loop { thread::park(); let mut list = self.inner.lock(); let e = unsafe { entry.as_ref() }; // Do a dummy replace operation in order to take out the state. match e.state.replace(State::Notified) { State::Notified => { // If this listener has been notified, remove it from the list and return. list.remove(entry); return; } // Otherwise, set the state back to `Waiting`. state => e.state.set(state), } } } } impl Future for SignalListener { type Output = (); fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let mut list = self.inner.lock(); let entry = match self.entry { None => unreachable!("cannot poll a completed `SignalListener` future"), Some(entry) => entry, }; let state = unsafe { &entry.as_ref().state }; // Do a dummy replace operation in order to take out the state. match state.replace(State::Notified) { State::Notified => { // If this listener has been notified, remove it from the list and return. list.remove(entry); drop(list); self.entry = None; return Poll::Ready(()); } State::Created => { // If the listener was just created, put it in the `Polling` state. state.set(State::Polling(cx.waker().clone())); } State::Polling(w) => { // If the listener was in the `Pooling` state, keep it. state.set(State::Polling(w)); } State::Waiting(_) => { unreachable!("cannot poll and wait on `SignalListener` at the same time") } } Poll::Pending } } impl Drop for SignalListener { fn drop(&mut self) { // If this listener has never picked up a notification... if let Some(entry) = self.entry.take() { let mut list = self.inner.lock(); // But if a notification was delivered to it... if list.remove(entry).is_notified() { // Then pass it on to another active listener. list.notify(false); } } } } /// A guard holding the linked list locked. struct ListGuard<'a> { /// A reference to `Signal`s inner state. inner: &'a Inner, /// The actual guard that acquired the linked list. guard: MutexGuard<'a, List>, } impl Drop for ListGuard<'_> { #[inline] fn drop(&mut self) { let list = &mut **self; let mut flags = 0; // Set the `NOTIFIED` flag if there is at least one notified listener. if list.len - list.notifiable > 0 { flags |= NOTIFIED; } // Set the `NOTIFIABLE` flag if there is at least one notifiable listener. if list.notifiable > 0 { flags |= NOTIFIABLE; } self.inner.flags.store(flags, Ordering::Release); } } impl Deref for ListGuard<'_> { type Target = List; #[inline] fn deref(&self) -> &List { &*self.guard } } impl DerefMut for ListGuard<'_> { #[inline] fn deref_mut(&mut self) -> &mut List { &mut *self.guard } } /// The state of a listener. enum State { /// It has just been created. Created, /// It has received a notification. Notified, /// An async task is polling it. Polling(Waker), /// A thread is blocked on it. Waiting(Thread), } impl State { /// Returns `true` if this is the `Notified` state. #[inline] fn is_notified(&self) -> bool { match self { State::Notified => true, State::Created | State::Polling(_) | State::Waiting(_) => false, } } } /// An entry representing a registered listener. struct Entry { /// THe state of this listener. state: Cell, /// Previous entry in the linked list. prev: Cell>>, /// Next entry in the linked list. next: Cell>>, } /// A linked list of entries. struct List { /// First entry in the list. head: Option>, /// Last entry in the list. tail: Option>, /// Total number of entries in the list. len: usize, /// Number of notifiable entries in the list. /// /// Notifiable entries are those that haven't been notified yet. notifiable: usize, } impl List { /// Inserts a new entry into the list. fn insert(&mut self) -> NonNull { unsafe { // Allocate an entry that is going to become the new tail. let entry = NonNull::new_unchecked(Box::into_raw(Box::new(Entry { state: Cell::new(State::Created), prev: Cell::new(self.tail), next: Cell::new(None), }))); // Replace the tail with the new entry. match mem::replace(&mut self.tail, Some(entry)) { None => self.head = Some(entry), Some(t) => t.as_ref().next.set(Some(entry)), } // Bump the total count and the count of notifiable entries. self.len += 1; self.notifiable += 1; entry } } /// Removes an entry from the list and returns its state. fn remove(&mut self, entry: NonNull) -> State { unsafe { let prev = entry.as_ref().prev.get(); let next = entry.as_ref().next.get(); // Unlink from the previous entry. match prev { None => self.head = next, Some(p) => p.as_ref().next.set(next), } // Unlink from the next entry. match next { None => self.tail = prev, Some(n) => n.as_ref().prev.set(prev), } // Deallocate and extract the state. let entry = Box::from_raw(entry.as_ptr()); let state = entry.state.into_inner(); // Update the counters. if !state.is_notified() { self.notifiable -= 1; } self.len -= 1; state } } /// Notifies an entry. #[cold] fn notify(&mut self, notify_all: bool) { let mut entry = self.tail; // Iterate over the entries in the list. while let Some(e) = entry { let e = unsafe { e.as_ref() }; // Set the state of this entry to `Notified`. let state = e.state.replace(State::Notified); let is_notified = state.is_notified(); // Wake the task or unpark the thread. match state { State::Notified => {} State::Created => {} State::Polling(w) => w.wake(), State::Waiting(t) => t.unpark(), } // Update the count of notifiable entries. if !is_notified { self.notifiable -= 1; } // If all entries need to be notified, go to the next one. if notify_all { entry = e.prev.get(); } else { break; } } } } /// Equivalent to `atomic::fence(Ordering::SeqCst)`, but in some cases faster. #[inline] fn full_fence() { if cfg!(any(target_arch = "x86", target_arch = "x86_64")) { // HACK(stjepang): On x86 architectures there are two different ways of executing // a `SeqCst` fence. // // 1. `atomic::fence(SeqCst)`, which compiles into a `mfence` instruction. // 2. `_.compare_and_swap(_, _, SeqCst)`, which compiles into a `lock cmpxchg` instruction. // // Both instructions have the effect of a full barrier, but empirical benchmarks have shown // that the second one makes notifiying listeners a bit faster. // // The ideal solution here would be to use inline assembly, but we're instead creating a // temporary atomic variable and compare-and-exchanging its value. No sane compiler to // x86 platforms is going to optimize this away. let a = AtomicUsize::new(0); a.compare_and_swap(0, 1, Ordering::SeqCst); } else { atomic::fence(Ordering::SeqCst); } }