console-rs / indicatif

A command line progress reporting library for Rust
MIT License
4.22k stars 238 forks source link

Spinner not spinning #588

Closed rawhuul closed 4 months ago

rawhuul commented 9 months ago

Hey there, this is my code:


impl SyncAll for Buckets {
    type Error = ScoopieError;
    fn sync() -> Result<Vec<SyncStatus>, Self::Error> {
        let buckets = Config::read()?.known_buckets();
        let num_buckets = buckets.len();

        let st = ProgressStyle::with_template("{spinner:.bold} ({pos}/{len}) {msg}").unwrap();

        let pb = ProgressBar::new(num_buckets as u64);
        pb.set_style(st);
        pb.set_message(
            style("Syncing buckets from the remote...")
                .bold()
                .to_string(),
        );

        buckets
            .par_iter()
            .map(|v| Bucket::sync(&pb, v.0, v.1))
            .collect()
    }
}

trait Sync: ReadFromRepo {
    type Error;
    fn sync(pb: &ProgressBar, name: &str, url: &str) -> Result<SyncStatus, <Self as Sync>::Error>;
}

impl Sync for Bucket {
    type Error = ScoopieError;

    fn sync(pb: &ProgressBar, name: &str, url: &str) -> Result<SyncStatus, <Self as Sync>::Error> {
        let temp_dir_builder = TempDir::build()?;
        let temp_dir = temp_dir_builder.path();

        let repo = RepoBuilder::new()
            .clone(url, &temp_dir)
            .map_err(|_| ScoopieError::SyncUnableToFetchRepo)?;

        let head = repo.head().map_err(|_| ScoopieError::SyncUnableToGetHead)?;

        let commit_id = head
            .peel_to_commit()
            .map_err(|_| ScoopieError::SyncUnableToGetCommit)?
            .id()
            .to_string();

        let bucket_dir = Config::buckets_dir()?;
        let bucket_path = bucket_dir.join(name);

        let mut metadata = MetaData::read()?;

        let st = match (
            bucket_path.exists(),
            metadata.get(name).commit_id == commit_id,
        ) {
            (true, true) => SyncStatus::UpToDate(name.into()),
            (true, false) => {
                Self::read(&temp_dir)?.write_to(&bucket_path);
                metadata.write(name, url, &commit_id)?;
                SyncStatus::Synced(name.into())
            }

            (false, _) => {
                Self::read(&temp_dir)?.write_to(&bucket_path);
                metadata.write(name, url, &commit_id)?;
                SyncStatus::Created(name.into())
            }
        };

        pb.inc(1);

        Ok(st)
    }
}

Everything work as expected but the spinner is not spinning as expected it is intended to be spin indefinitely without stopping until all function returns.

https://github.com/console-rs/indicatif/assets/84739019/103eb01b-5ef7-4431-b1c3-5a1d83d4ab87

My Cargo.toml looks like:

...
indicatif = { version = "0.17.6", features = ["rayon"] }

# To show colors in terminal
console = "0.15.7"
djc commented 9 months ago

Can you turn this into a minimal reproduction that doesn't have any dependencies other than indicatif?

rawhuul commented 9 months ago

Yeah sure.

Here is the code,

use indicatif::{ProgressBar, ProgressStyle};
use rayon::prelude::*;

fn waiting(pb: &ProgressBar, n: i32) -> i32 {
    std::thread::sleep(std::time::Duration::from_secs(10));
    pb.inc(1);

    n * n
}

fn main() {
    let st = ProgressStyle::with_template("{spinner:.bold} ({pos}/{len}) {msg}").unwrap();

    let pb = ProgressBar::new(4 as u64);
    pb.set_style(st);
    pb.set_message("Syncing buckets from the remote...");

    let v = (1..4)
        .into_par_iter()
        .map(|n| waiting(&pb, n))
        .collect::<Vec<_>>();

    println!("{v:#?}");
}

Cargo.toml:

[package]
name = "indi"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
indicatif = { version = "0.17.6", features = ["rayon"] }
rayon = "1.7.0"

Results:

https://github.com/console-rs/indicatif/assets/84739019/f241efb5-60bc-485b-a52a-d1397d7fbf01

As you can see spinner is stuck and only spins after function returns, whereas it must be spinning all the time unless the spinner finishes (I guess it should be intended).

djc commented 9 months ago

I think this behavior is expected? indicatif doesn't spawn a separate thread unless you explicitly enable that (using enable_steady_tick()), so unless you do that the spinner will only update whenever you call pb.inc().

chris-laplante commented 9 months ago

Yes, this is expected behavior. Perhaps we need a documentation change to make it clear that a '{spinner}' doesn't automatically update unless something is driving it. I'd be happy to submit a PR for that.

djc commented 9 months ago

@chris-laplante please do!

chris-laplante commented 4 months ago

I documented this in https://github.com/console-rs/indicatif/pull/593 and then forgot to close this issue. Closing now.