soft-cycle 0.2.0

Async controller for coordinating soft restarts and graceful shutdowns with shared listeners
Documentation
  • Coverage
  • 85.71%
    24 out of 28 items documented1 out of 16 items with examples
  • Size
  • Source code size: 46.92 kB This is the summed size of all the files inside the crates.io package for this release.
  • Documentation size: 3.92 MB This is the summed size of all files generated by rustdoc for all configured targets
  • Ø build duration
  • this release: 18s Average build duration of successful builds.
  • all releases: 17s Average build duration of successful builds in releases after 2024-10-23.
  • Links
  • Homepage
  • GeminiLab/soft-cycle
    0 0 0
  • crates.io
  • Dependencies
  • Versions
  • Owners
  • aarkegz

soft-cycle

Crates.io docs.rs License: MIT CI

A small async Rust crate for coordinating soft restarts and graceful shutdowns around a shared SoftCycleController, designed for async runtimes where many tasks need to observe lifecycle transitions without blocking:

  • try_notify(payload) publishes a lifecycle notification and wakes current listeners, with an optional payload.
  • try_clear() transitions the controller back to the non-notified state.
  • listener() returns a future that resolves when being notified with the observed payload.

Usage

Use [SoftCycleController::new] to create a new controller instance. Call try_notify to publish a notification with a payload and get Ok(sequence_number) on success (or Err(payload) if already notified), where sequence_number is a monotonically increasing number starting from 0. Call try_clear to clear the notified state and get Ok(sequence_number) for the cleared notification (or Err(()) if not currently notified). Call listener to create a [SoftCycleListener] future that resolves with Ok(payload) when a notification is observed.

Guarantees

  • Linearizable order: All try_notify and try_clear operations are linearizable with respect to a single global order.
  • Non-blocking: try_notify and try_clear are synchronous and never block.
  • Listener completion: A listener created after a notification and before the next clearance completes immediately with the current payload. A listener created after a clear and before the next notification completes in a finite number of polls (usually one) after the next try_notify. If multiple notify/clear cycles occur after a listener is created, it returns one of those payloads (no guarantee of returning the earliest or latest); this is a rare scenario and is not likely to happen unless try_notify and try_clear are called multiple times in a very short time frame.

Features

  • global_instance (default): Enables a process-wide default controller and the async free functions [get_lifetime_controller], [try_restart], [try_shutdown], [listener], and [clear] at the crate root. The global controller uses SoftCycleMessage as the payload type, which contains Shutdown and Restart variants.

Example

use soft_cycle::{SoftCycleController, SoftCycleMessage};
use std::sync::Arc;
use tokio::time::{sleep, Duration, timeout};

#[tokio::main]
async fn main() {
    let controller = Arc::new(SoftCycleController::<SoftCycleMessage>::new());

    // Worker that reacts to notifications.
    let worker_controller = controller.clone();
    let worker = tokio::spawn(async move {
        loop {
            let payload = worker_controller.listener().await.unwrap();
            match payload {
                SoftCycleMessage::Shutdown => {
                    println!("worker: shutdown");
                    break;
                }
                SoftCycleMessage::Restart => {
                    println!("worker: restart");
                }
            }
        }
    });

    // Producer notifies restart.
    assert_eq!(controller.try_notify(SoftCycleMessage::Restart), Ok(0));
    // Already notified, returns Err.
    assert_eq!(controller.try_notify(SoftCycleMessage::Restart), Err(SoftCycleMessage::Restart));
    // Clear when restart handling phase is done.
    assert_eq!(controller.try_clear(), Ok(0));
    // Already cleared, returns Err.
    assert_eq!(controller.try_clear(), Err(()));

    sleep(Duration::from_millis(100)).await;

    // Producer notifies restart again.
    assert_eq!(controller.try_notify(SoftCycleMessage::Restart), Ok(1));
    // Clear when restart handling phase is done.
    assert_eq!(controller.try_clear(), Ok(1));

    sleep(Duration::from_millis(100)).await;

    // Producer notifies shutdown.
    assert_eq!(controller.try_notify(SoftCycleMessage::Shutdown), Ok(2));
    // Optional: wait for worker to observe shutdown.
    timeout(Duration::from_secs(2), worker).await.unwrap().unwrap();
}