killswitch 0.4.2

Killswitch used to broadcast a shutdown request.
Documentation
//! The paired killswitch is separated into a [`KillTrig`] (used to trigger a
//! shutdown notification) and a [`KillWait`] (used to wait for the killswitch
//! to be triggered).
//!
//! Only the `KillWait` can be cloned, thus there's a guarantee that only one
//! kill trigger exists.
//!
//! ```
//! use std::error::Error;
//! use tokio::time::{sleep, Duration};
//! use killswitch::pair::{self, KillWait};
//!
//! #[tokio::main]
//! async fn main() -> Result<(), Box<dyn Error>> {
//!   let (kt, kw) = pair::create();
//!
//!   tokio::spawn(killable(String::from("test1"), kw.clone()));
//!   tokio::spawn(killable(String::from("test2"), kw.clone()));
//!
//!   sleep(Duration::from_secs(1)).await;
//!
//!   println!("Triggering kill switch");
//!   kt.trigger();
//!
//!   tokio::spawn(killable(String::from("test3"), kw.clone()));
//!   tokio::spawn(killable(String::from("test4"), kw));
//!
//!   // Wait for all waiters to drop
//!   kt.await;
//!
//!   Ok(())
//! }
//!
//! async fn killable(s: String, kw: KillWait) {
//!   println!("killable({}) entered", s);
//!   kw.wait().await;
//!   println!("killable({}) leaving", s);
//! }
//! ```
//!
//! The semantics of `.await`:ing the `KillTrig` has a notable semantic
//! difference compared to calling
//! [`KillSwitch::finalize()`](super::KillSwitch::finalize()):  The
//! `KillSwitch` finalization future waits for all _active_ waiters to
//! terminate, while awaiting `KillTrig` will return once all associated
//! `KillWait` objects have been dropped.

pub mod killtrig;
pub mod killwait;

use std::{
  collections::HashMap,
  sync::atomic::{AtomicBool, AtomicUsize, Ordering},
  sync::Arc,
  task::Waker
};

use parking_lot::Mutex;

pub use killtrig::KillTrig;
pub use killwait::KillWait;


/// Shared state buffer that is hidden behind a Mutex.
struct State {
  /// Once the killswitch is triggered there needs to be a way to wake all
  /// tasking waiting for a shutdown.  This map is used to keep track of all
  /// active wakers.
  waiting: HashMap<usize, Waker>,

  /// Waker used for waiting for all KillWait objects to be dropped.
  waker: Option<Waker>
}

/// Buffer shared among all KillSwitch and Shutdown objects.
struct Shared {
  /// This value is used as an increasing/wrapping value to generate unique
  /// id:s.
  id: AtomicUsize,

  /// Keep track of whether the killswitch has been triggered or not.
  triggered: AtomicBool,

  state: Mutex<State>
}

impl Shared {
  /// Return next (wrapping) non-zero id.
  #[inline]
  fn id(&self) -> usize {
    loop {
      let id = self.id.fetch_add(1, Ordering::SeqCst);
      if id != 0 {
        break id;
      }
    }
  }
}


/// Create a connected [`KillTrig`] and a [`KillWait`] pair.
pub fn create() -> (KillTrig, KillWait) {
  let state = State {
    waiting: HashMap::new(),
    waker: None
  };
  let shared = Shared {
    id: AtomicUsize::new(1),
    triggered: AtomicBool::new(false),
    state: Mutex::new(state)
  };
  let shared = Arc::new(shared);

  let kt = KillTrig(Arc::clone(&shared));
  let kw = KillWait { ctx: shared };

  (kt, kw)
}

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