swctx 0.3.0

One-shot channel with some special semantics.
Documentation
use std::sync::Arc;

use crate::err::Error;

use super::{Shared, State};


/// End-point used to send value to the paired [`WaitCtx`](super::WaitCtx).
#[repr(transparent)]
pub struct SetCtx<T, S: Clone, E>(pub(crate) Arc<Shared<T, S, E>>);

impl<T, S, E> SetCtx<T, S, E>
where
  S: Clone
{
  /// Set the internal [`SetCtx`] state.
  ///
  /// If an `SetCtx` is dropped prematurely (i.e. without setting a value or
  /// repering a failure) an `Error::Aborted(S)` will automatically be sent to
  /// the linked [`WaitCtx`](super::WaitCtx) object, where `S` will be set to
  /// the value last set using `set_state()`.
  ///
  /// ```
  /// use std::thread;
  /// use swctx::{mkpair, Error};
  ///
  /// #[derive(Clone, Debug, Default, PartialEq)]
  /// enum State {
  ///   #[default]
  ///   Init,
  ///   InThread
  /// }
  ///
  /// let (sctx, wctx) = mkpair::<&str, State, &str>();
  /// let jh = thread::spawn(move || {
  ///   sctx.set_state(State::InThread);
  ///   // sctx is prematurely dropped here
  /// });
  /// jh.join().unwrap();
  ///
  /// assert_eq!(wctx.wait(), Err(Error::Aborted(State::InThread)));
  /// ```
  ///
  /// # Errors
  /// If the corresponding [`WaitCtx`](super::WaitCtx) has been dropped,
  /// [`Error::LostWaiter`] will be returned.
  pub fn set_state(&self, s: S) -> Result<(), Error<S, E>> {
    let mut inner = self.0.inner.lock();
    if inner.wctx_dropped {
      return Err(Error::LostWaiter);
    }
    inner.sctx_state = s;
    drop(inner);
    Ok(())
  }

  /// Consume the `SetCtx` and store a value for the wait context to return.
  ///
  /// # Errors
  /// If the corresponding [`WaitCtx`](super::WaitCtx) has been dropped,
  /// [`Error::LostWaiter`] will be returned.
  pub fn set(self, data: T) -> Result<(), Error<S, E>> {
    // - This and `fail()` consume `self`.
    // - The wait context does not modify the state.
    // - The only other method is `set_state()` and it only touches
    //   `sctx_state` and not `state`.
    //
    // These facts allow us to safely assume that the state does not need to be
    // checked here -- it can only be `Waiting`.
    let mut inner = self.0.inner.lock();
    if inner.wctx_dropped {
      return Err(Error::LostWaiter);
    }

    inner.state = State::Data(data);
    self.0.notify_waiter(&mut inner);
    drop(inner);

    Ok(())
  }

  /// Consume the `SetCtx` and store an error value for the wait context to
  /// return.
  ///
  /// # Errors
  /// If the corresponding [`WaitCtx`](super::WaitCtx) has been dropped,
  /// [`Error::LostWaiter`] will be returned.
  pub fn fail(self, error: E) -> Result<(), Error<S, E>> {
    // See comments in SetCtx::set() for implementation details.
    let mut inner = self.0.inner.lock();
    if inner.wctx_dropped {
      return Err(Error::LostWaiter);
    }

    inner.state = State::Err(Error::App(error));
    self.0.notify_waiter(&mut inner);
    drop(inner);

    Ok(())
  }
}

impl<T, S, E> Drop for SetCtx<T, S, E>
where
  S: Clone
{
  fn drop(&mut self) {
    let mut inner = self.0.inner.lock();
    match inner.state {
      State::Waiting => {
        inner.state = State::Err(Error::Aborted(inner.sctx_state.clone()));
        self.0.notify_waiter(&mut inner);
        drop(inner);
      }
      State::Data(_) | State::Err(_) | State::Finalized => {
        // Do nothing.
        // For the Data base, assume the waiter will handle the data.
      }
    }
  }
}

// vim: set ft=rust et sw=2 ts=2 sts=2 cinoptions=2 tw=79 :