Skip to main content

Status

Struct Status 

Source
pub struct Status { /* private fields */ }
Expand description

A spinner + message context helper, inspired by Python Rich’s Console.status(...).

When the console is interactive (Console::is_interactive()), this starts a Live display that refreshes a single-line spinner. When the console is not interactive, it prints the message once and does not animate.

Dropping this value stops the live display.

§Thread Safety

Status is Send + Sync and can be safely shared between threads. The update method is safe to call concurrently from multiple threads — it performs a single atomic mutex write with poison recovery.

Updates are eventually consistent: the displayed message is guaranteed to reflect one of the recent update() calls within ~100ms (one refresh cycle).

§Design RFC: Atomic Status::update (bd-gg33)

§Problem

Status::update currently performs two operations: (1) write the new message into Arc<Mutex<String>>, then (2) call live.refresh(). These are not atomic: another thread could update the message between steps 1 and 2, causing refresh to display a message from a different update call.

§Options Evaluated

OptionApproachComplexityBreaking?
AMessage versioning (u64 counter + Live version check)MediumNo
BDeferred refresh (remove explicit refresh call)LowNo
CCombined mutex (hold during refresh)HighPotentially
DDocument as known limitationNoneNo

§Decision: Option B — Deferred Refresh

Remove the explicit live.refresh() call from update().

Rationale:

  • Live already runs a timer-based refresh at refresh_per_second: 10.0 (100 ms interval). The explicit refresh() call is redundant.
  • Removing it eliminates the race window entirely: update() becomes a single mutex write, which is inherently atomic.
  • No performance cost. The message is guaranteed to appear on the next scheduled refresh cycle (within ~100 ms), which is imperceptible.
  • Simplest implementation: fewer lines of code, fewer failure modes.

Alternatives rejected:

  • Option A (versioning): Adds complexity for no user-visible benefit. The race condition is cosmetic (self-corrects in one refresh cycle).
  • Option C (combined mutex): Risk of deadlock with Live’s internal mutexes. Increased lock contention under heavy concurrent updates.
  • Option D (document only): Leaves an unnecessary race when a simple fix exists.

§Migration

// Before (current):
pub fn update(&self, message: impl Into<String>) {
    *crate::sync::lock_recover(&self.message) = message.into();
    if let Some(live) = &self.live {
        let _ = live.refresh();  // <-- race window here
    }
}

// After (Option B):
pub fn update(&self, message: impl Into<String>) {
    *crate::sync::lock_recover(&self.message) = message.into();
    // Live's timer-based refresh picks up the new message automatically.
}

§Test Plan

  1. Existing test_status_non_interactive_prints_message_once still passes.
  2. New test: rapid concurrent update() calls from multiple threads, verifying no panics and final message is one of the expected values.
  3. New test: update() after Live has stopped (no-op, no crash).

Implementations§

Source§

impl Status

Source

pub fn new(console: &Arc<Console>, message: impl Into<String>) -> Result<Self>

Start a status spinner with a message.

Source

pub fn update(&self, message: impl Into<String>)

Update the displayed message.

§Design Note (RFC bd-gg33)

This method does NOT explicitly trigger a refresh. The Live display’s background thread (running at 10Hz) will pick up the new message on its next tick. This design eliminates a race condition where concurrent update() calls could cause message ordering issues.

See the module-level RFC documentation on Status for full analysis.

Trait Implementations§

Source§

impl Drop for Status

Source§

fn drop(&mut self)

Executes the destructor for this type. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.