use std::sync::atomic::{AtomicU8, AtomicUsize, Ordering};
use std::sync::{Arc, Condvar, Mutex, MutexGuard};
use std::time::{Duration, Instant};
#[doc(inline)]
pub use crate::error::{Disconnected, NoReceivers, TryWaitError, WaitTimeoutError};
const PENDING: u8 = 0;
const FIRED: u8 = 1;
const DISCONNECTED: u8 = 2;
#[derive(Clone, Copy)]
enum Terminal {
Fired,
Disconnected,
}
struct Shared {
state: AtomicU8,
signalers: AtomicUsize,
waiters: AtomicUsize,
mutex: Mutex<()>,
condvar: Condvar,
}
impl Shared {
fn new() -> Self {
Shared {
state: AtomicU8::new(PENDING),
signalers: AtomicUsize::new(1), waiters: AtomicUsize::new(1), mutex: Mutex::new(()),
condvar: Condvar::new(),
}
}
#[inline]
fn signaler_count(&self) -> usize {
self.signalers.load(Ordering::Acquire)
}
#[inline]
fn waiter_count(&self) -> usize {
self.waiters.load(Ordering::Acquire)
}
#[inline]
fn load_terminal(&self) -> Option<Terminal> {
match self.state.load(Ordering::Acquire) {
FIRED => Some(Terminal::Fired),
DISCONNECTED => Some(Terminal::Disconnected),
_ => None,
}
}
fn signal(&self) -> Result<bool, NoReceivers> {
if self.state.load(Ordering::Acquire) == FIRED {
return Ok(false);
}
if self.waiters.load(Ordering::Acquire) == 0 {
return Err(NoReceivers);
}
match self
.state
.compare_exchange(PENDING, FIRED, Ordering::Release, Ordering::Relaxed)
{
Ok(_) => {
self.wake_all();
Ok(true)
}
Err(_) => Ok(false),
}
}
fn signaler_dropped(&self) {
if self.signalers.fetch_sub(1, Ordering::AcqRel) != 1 {
return; }
if self
.state
.compare_exchange(PENDING, DISCONNECTED, Ordering::Release, Ordering::Relaxed)
.is_ok()
{
self.wake_all();
}
}
fn wake_all(&self) {
let _guard = Self::lock_unpoisoned(&self.mutex);
self.condvar.notify_all();
}
fn lock_unpoisoned(mutex: &Mutex<()>) -> MutexGuard<'_, ()> {
match mutex.lock() {
Ok(guard) => guard,
Err(poisoned) => poisoned.into_inner(),
}
}
fn wait_unpoisoned<'a>(&self, guard: MutexGuard<'a, ()>) -> MutexGuard<'a, ()> {
match self.condvar.wait(guard) {
Ok(guard) => guard,
Err(poisoned) => poisoned.into_inner(),
}
}
fn wait_timeout_unpoisoned<'a>(
&self,
guard: MutexGuard<'a, ()>,
timeout: Duration,
) -> (MutexGuard<'a, ()>, bool) {
match self.condvar.wait_timeout(guard, timeout) {
Ok((guard, result)) => (guard, result.timed_out()),
Err(poisoned) => {
let (guard, result) = poisoned.into_inner();
(guard, result.timed_out())
}
}
}
fn wait(&self) -> Terminal {
if let Some(t) = self.load_terminal() {
return t;
}
let mut guard = Self::lock_unpoisoned(&self.mutex);
loop {
if let Some(t) = self.load_terminal() {
return t;
}
guard = self.wait_unpoisoned(guard);
}
}
fn wait_timeout(&self, timeout: Duration) -> Option<Terminal> {
if let Some(t) = self.load_terminal() {
return Some(t);
}
let deadline = Instant::now().checked_add(timeout);
let mut guard = Self::lock_unpoisoned(&self.mutex);
loop {
if let Some(t) = self.load_terminal() {
return Some(t);
}
let remaining = match deadline {
Some(d) => d.saturating_duration_since(Instant::now()),
None => {
guard = self.wait_unpoisoned(guard);
continue;
}
};
if remaining.is_zero() {
return None;
}
let (g, timed_out) = self.wait_timeout_unpoisoned(guard, remaining);
guard = g;
if timed_out {
return self.load_terminal();
}
}
}
}
pub struct Signaler {
shared: Arc<Shared>,
}
pub struct Waiter {
shared: Arc<Shared>,
}
pub fn create() -> (Signaler, Waiter) {
let shared = Arc::new(Shared::new());
(
Signaler {
shared: shared.clone(),
},
Waiter { shared },
)
}
impl Signaler {
pub fn signal(&self) -> Result<bool, NoReceivers> {
self.shared.signal()
}
pub fn is_signaled(&self) -> bool {
self.shared.state.load(Ordering::Acquire) == FIRED
}
pub fn signaler_count(&self) -> usize {
self.shared.signaler_count()
}
pub fn waiter_count(&self) -> usize {
self.shared.waiter_count()
}
}
impl Clone for Signaler {
fn clone(&self) -> Self {
self.shared.signalers.fetch_add(1, Ordering::Relaxed);
Signaler {
shared: self.shared.clone(),
}
}
}
impl Drop for Signaler {
fn drop(&mut self) {
self.shared.signaler_dropped();
}
}
impl Waiter {
pub fn wait(&self) -> Result<(), Disconnected> {
match self.shared.wait() {
Terminal::Fired => Ok(()),
Terminal::Disconnected => Err(Disconnected),
}
}
pub fn wait_timeout(&self, timeout: Duration) -> Result<(), WaitTimeoutError> {
match self.shared.wait_timeout(timeout) {
Some(Terminal::Fired) => Ok(()),
Some(Terminal::Disconnected) => Err(WaitTimeoutError::Disconnected),
None => Err(WaitTimeoutError::Timeout),
}
}
pub fn try_wait(&self) -> Result<(), TryWaitError> {
match self.shared.load_terminal() {
Some(Terminal::Fired) => Ok(()),
Some(Terminal::Disconnected) => Err(TryWaitError::Disconnected),
None => Err(TryWaitError::Pending),
}
}
pub fn is_signaled(&self) -> bool {
self.shared.state.load(Ordering::Acquire) == FIRED
}
pub fn is_disconnected(&self) -> bool {
self.shared.state.load(Ordering::Acquire) == DISCONNECTED
}
pub fn signaler_count(&self) -> usize {
self.shared.signaler_count()
}
pub fn waiter_count(&self) -> usize {
self.shared.waiter_count()
}
}
impl Clone for Waiter {
fn clone(&self) -> Self {
self.shared.waiters.fetch_add(1, Ordering::Relaxed);
Waiter {
shared: self.shared.clone(),
}
}
}
impl Drop for Waiter {
fn drop(&mut self) {
self.shared.waiters.fetch_sub(1, Ordering::AcqRel);
}
}