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
| Option | Approach | Complexity | Breaking? |
|---|---|---|---|
| A | Message versioning (u64 counter + Live version check) | Medium | No |
| B | Deferred refresh (remove explicit refresh call) | Low | No |
| C | Combined mutex (hold during refresh) | High | Potentially |
| D | Document as known limitation | None | No |
§Decision: Option B — Deferred Refresh
Remove the explicit live.refresh() call from update().
Rationale:
Livealready runs a timer-based refresh atrefresh_per_second: 10.0(100 ms interval). The explicitrefresh()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
- Existing
test_status_non_interactive_prints_message_oncestill passes. - New test: rapid concurrent
update()calls from multiple threads, verifying no panics and final message is one of the expected values. - New test:
update()afterLivehas stopped (no-op, no crash).
Implementations§
Source§impl Status
impl Status
Sourcepub fn new(console: &Arc<Console>, message: impl Into<String>) -> Result<Self>
pub fn new(console: &Arc<Console>, message: impl Into<String>) -> Result<Self>
Start a status spinner with a message.
Sourcepub fn update(&self, message: impl Into<String>)
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.