mirror of
https://github.com/vhaudiquet/ecrou.git
synced 2026-11-12 22:22:44 +00:00
feat(progress): report uncounted status through the sink
A run blocked on the cache-entry flock was silent: the wait went to the log sink, which renders nowhere by default, so the CLI read as a hang. The Progress trait gains message() - a status line outside the counted-operation protocol, defaulting to a no-op so existing sinks keep compiling - and the flock wait reports what it is waiting for. The CLI renders messages on the active bar and spins one up when no operation is active.
This commit is contained in:
1 parent
89a1028d46
commit
c2e8d82cd3
5 files changed
+42
-7
No files matched your search
@@ -18,8 +18,9 @@ propagates exit codes. The split is enforced by the dependency edges:
|
|||||||
the library links no clap and no rendering crate. It never writes to
|
the library links no clap and no rendering crate. It never writes to
|
||||||
the terminal: diagnostics go through the `log` facade, filtered by
|
the terminal: diagnostics go through the `log` facade, filtered by
|
||||||
`RunOptions::log_level` (an engine-owned enum, not UI booleans), long
|
`RunOptions::log_level` (an engine-owned enum, not UI booleans), long
|
||||||
operations report through the `Progress` sink, all output is the
|
operations report through the `Progress` sink (sequential
|
||||||
CLI's. In-process failures come back as typed errors; the one
|
`begin`/`advance`/`finish` operations plus uncounted `message`
|
||||||
|
status lines), all output is the CLI's. In-process failures come back as typed errors; the one
|
||||||
exception is the forked machine child's last-gasp diagnostics, which
|
exception is the forked machine child's last-gasp diagnostics, which
|
||||||
land in the console file the parent streams. The CLI owns clap, the
|
land in the console file the parent streams. The CLI owns clap, the
|
||||||
indicatif rendering of progress events, the log sink, and exit codes.
|
indicatif rendering of progress events, the log sink, and exit codes.
|
||||||
|
|||||||
@@ -88,4 +88,24 @@ impl Progress for TermProgress {
|
|||||||
bar.finish_and_clear();
|
bar.finish_and_clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn message(&self, text: &str) {
|
||||||
|
let mut slot = self.bar.lock().unwrap();
|
||||||
|
if slot.is_none() {
|
||||||
|
// No operation is active: the message becomes a spinner
|
||||||
|
// until the next `begin` replaces it.
|
||||||
|
let pb = ProgressBar::new_spinner();
|
||||||
|
pb.set_style(
|
||||||
|
ProgressStyle::default_spinner()
|
||||||
|
.template("{spinner:.green} {msg}")
|
||||||
|
.unwrap(),
|
||||||
|
);
|
||||||
|
pb.set_message(text.to_string());
|
||||||
|
*slot = Some(pb);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if let Some(bar) = &*slot {
|
||||||
|
bar.set_message(text.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -94,9 +94,11 @@ pub(crate) struct EntryLock {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl EntryLock {
|
impl EntryLock {
|
||||||
/// Block until the entry is free. `no_cache` callers skip the
|
/// Block until the entry is free, telling the sink what the run
|
||||||
/// lock entirely: an explicit fresh download never waits.
|
/// is waiting on (a silent block reads as a hang). `no_cache`
|
||||||
pub(crate) fn acquire(entry: &Path) -> anyhow::Result<Self> {
|
/// callers skip the lock entirely: an explicit fresh download
|
||||||
|
/// never waits.
|
||||||
|
pub(crate) fn acquire(entry: &Path, progress: &dyn Progress) -> anyhow::Result<Self> {
|
||||||
use std::os::fd::AsRawFd;
|
use std::os::fd::AsRawFd;
|
||||||
|
|
||||||
let lock_path = entry.with_extension("lock");
|
let lock_path = entry.with_extension("lock");
|
||||||
@@ -120,6 +122,10 @@ impl EntryLock {
|
|||||||
"waiting for another ecrou run to finish preparing {}",
|
"waiting for another ecrou run to finish preparing {}",
|
||||||
entry.display()
|
entry.display()
|
||||||
);
|
);
|
||||||
|
progress.message(&format!(
|
||||||
|
"Waiting for another ecrou run to finish preparing {}",
|
||||||
|
entry.display()
|
||||||
|
));
|
||||||
let held = unsafe { libc::flock(file.as_raw_fd(), libc::LOCK_EX) };
|
let held = unsafe { libc::flock(file.as_raw_fd(), libc::LOCK_EX) };
|
||||||
if held != 0 {
|
if held != 0 {
|
||||||
return Err(anyhow::Error::from(std::io::Error::last_os_error())
|
return Err(anyhow::Error::from(std::io::Error::last_os_error())
|
||||||
|
|||||||
@@ -19,7 +19,9 @@ pub enum Unit {
|
|||||||
///
|
///
|
||||||
/// Operations are sequential: `begin` starts the next operation (any
|
/// Operations are sequential: `begin` starts the next operation (any
|
||||||
/// previous one is implicitly over), `advance` counts completed units,
|
/// previous one is implicitly over), `advance` counts completed units,
|
||||||
/// `finish` marks its end. Sinks must be callable from any thread.
|
/// `finish` marks its end. `message` reports uncounted status outside
|
||||||
|
/// that protocol — a wait, a decision, a retry — when there is nothing
|
||||||
|
/// to count. Sinks must be callable from any thread.
|
||||||
pub trait Progress: Send + Sync {
|
pub trait Progress: Send + Sync {
|
||||||
/// An operation labelled `label` starts, counting `unit`s; `total`
|
/// An operation labelled `label` starts, counting `unit`s; `total`
|
||||||
/// is the unit count when known upfront.
|
/// is the unit count when known upfront.
|
||||||
@@ -30,6 +32,11 @@ pub trait Progress: Send + Sync {
|
|||||||
|
|
||||||
/// The current operation ended.
|
/// The current operation ended.
|
||||||
fn finish(&self);
|
fn finish(&self);
|
||||||
|
|
||||||
|
/// A status line outside the counted-operation protocol. The
|
||||||
|
/// default discards it; rendering sinks surface it as a spinner
|
||||||
|
/// message, starting one if no operation is active.
|
||||||
|
fn message(&self, _text: &str) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A [`Progress`] sink that discards every event.
|
/// A [`Progress`] sink that discards every event.
|
||||||
|
|||||||
@@ -88,7 +88,8 @@ impl RootfsCache {
|
|||||||
// One writer per entry: a concurrent run preparing the
|
// One writer per entry: a concurrent run preparing the
|
||||||
// same image waits here instead of downloading a second
|
// same image waits here instead of downloading a second
|
||||||
// copy the atomic rename would throw away.
|
// copy the atomic rename would throw away.
|
||||||
let _entry_lock = EntryLock::acquire(&cache_path).map_err(Error::Other)?;
|
let _entry_lock =
|
||||||
|
EntryLock::acquire(&cache_path, progress).map_err(Error::Other)?;
|
||||||
// Re-derive the freshness answer under the lock: a
|
// Re-derive the freshness answer under the lock: a
|
||||||
// competing run may have filled or refreshed the entry
|
// competing run may have filled or refreshed the entry
|
||||||
// while we waited.
|
// while we waited.
|
||||||
|
|||||||
Reference in new issue
Block a user