Skip to main content

conmonrs/
util.rs

1//! Small helpers which modules share that do not depend on each other
2//! otherwise.
3
4use anyhow::format_err;
5use std::cell::RefCell;
6use tokio::time::{Duration, Instant};
7
8thread_local! {
9    /// See [`with_scratch`].
10    static SCRATCH: RefCell<Vec<u8>> = const { RefCell::new(Vec::new()) };
11}
12
13/// Run `f` on a buffer of `len` bytes which the thread keeps for all reads
14/// which copy what they got out of it right away. The read loops of all
15/// containers and attach clients and the runtime calls share it, instead of
16/// keeping a buffer each while they wait. It grows to the largest `len`
17/// asked for and holds what the last use left in it.
18pub(crate) fn with_scratch<R>(len: usize, f: impl FnOnce(&mut [u8]) -> R) -> R {
19    SCRATCH.with(|scratch| match scratch.try_borrow_mut() {
20        Ok(mut scratch) => {
21            if scratch.len() < len {
22                scratch.resize(len, 0);
23            }
24            f(&mut scratch[..len])
25        }
26        // Only if `f` used it again, which none of the reads does.
27        Err(_) => f(&mut vec![0; len]),
28    })
29}
30
31/// A duration in whole seconds, with the minutes apart, like `10m`,
32/// `1m 30s` or `45s`, for the errors.
33pub(crate) fn format_duration(duration: Duration) -> String {
34    // Rounded, a deadline measured from now is short of a whole second.
35    let secs = (duration + Duration::from_millis(500)).as_secs();
36    match (secs / 60, secs % 60) {
37        (0, secs) => format!("{secs}s"),
38        (mins, 0) => format!("{mins}m"),
39        (mins, secs) => format!("{mins}m {secs}s"),
40    }
41}
42
43/// Sleep until the deadline, forever without one.
44pub(crate) async fn sleep_until(deadline: Option<Instant>) {
45    match deadline {
46        Some(deadline) => tokio::time::sleep_until(deadline).await,
47        None => std::future::pending().await,
48    }
49}
50
51/// Append the stderr of a runtime which failed to its error, unless it wrote
52/// nothing. It usually holds the reason.
53pub(crate) fn with_stderr(error: anyhow::Error, stderr: &[u8]) -> anyhow::Error {
54    let stderr = String::from_utf8_lossy(stderr);
55    let stderr = stderr.trim();
56    if stderr.is_empty() {
57        error
58    } else {
59        format_err!("{error:#}: {stderr}")
60    }
61}