indicatif/
progress_bar.rs

1#[cfg(test)]
2use portable_atomic::{AtomicBool, Ordering};
3use std::borrow::Cow;
4use std::sync::{Arc, Condvar, Mutex, MutexGuard, Weak};
5use std::time::Duration;
6#[cfg(not(target_arch = "wasm32"))]
7use std::time::Instant;
8use std::{fmt, io, thread};
9
10#[cfg(test)]
11use once_cell::sync::Lazy;
12#[cfg(target_arch = "wasm32")]
13use web_time::Instant;
14
15use crate::draw_target::ProgressDrawTarget;
16use crate::state::{AtomicPosition, BarState, ProgressFinish, Reset, TabExpandedString};
17use crate::style::ProgressStyle;
18use crate::{ProgressBarIter, ProgressIterator, ProgressState};
19
20/// A progress bar or spinner
21///
22/// The progress bar is an [`Arc`] around its internal state. When the progress bar is cloned it
23/// just increments the refcount (so the original and its clone share the same state).
24#[derive(Clone)]
25pub struct ProgressBar {
26    state: Arc<Mutex<BarState>>,
27    pos: Arc<AtomicPosition>,
28    ticker: Arc<Mutex<Option<Ticker>>>,
29}
30
31impl fmt::Debug for ProgressBar {
32    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
33        f.debug_struct("ProgressBar").finish()
34    }
35}
36
37impl ProgressBar {
38    /// Creates a new progress bar with a given length
39    ///
40    /// This progress bar by default draws directly to stderr, and refreshes a maximum of 20 times
41    /// a second. To change the refresh rate, [set] the [draw target] to one with a different refresh
42    /// rate.
43    ///
44    /// [set]: ProgressBar::set_draw_target
45    /// [draw target]: ProgressDrawTarget
46    pub fn new(len: u64) -> Self {
47        Self::with_draw_target(Some(len), ProgressDrawTarget::stderr())
48    }
49
50    /// Creates a new progress bar without a specified length
51    ///
52    /// This progress bar by default draws directly to stderr, and refreshes a maximum of 20 times
53    /// a second. To change the refresh rate, [set] the [draw target] to one with a different refresh
54    /// rate.
55    ///
56    /// [set]: ProgressBar::set_draw_target
57    /// [draw target]: ProgressDrawTarget
58    pub fn no_length() -> Self {
59        Self::with_draw_target(None, ProgressDrawTarget::stderr())
60    }
61
62    /// Creates a completely hidden progress bar
63    ///
64    /// This progress bar still responds to API changes but it does not have a length or render in
65    /// any way.
66    pub fn hidden() -> Self {
67        Self::with_draw_target(None, ProgressDrawTarget::hidden())
68    }
69
70    /// Creates a new progress bar with a given length and draw target
71    pub fn with_draw_target(len: Option<u64>, draw_target: ProgressDrawTarget) -> Self {
72        let pos = Arc::new(AtomicPosition::new());
73        Self {
74            state: Arc::new(Mutex::new(BarState::new(len, draw_target, pos.clone()))),
75            pos,
76            ticker: Arc::new(Mutex::new(None)),
77        }
78    }
79
80    /// Get a clone of the current progress bar style.
81    pub fn style(&self) -> ProgressStyle {
82        self.state().style.clone()
83    }
84
85    /// A convenience builder-like function for a progress bar with a given style
86    pub fn with_style(self, style: ProgressStyle) -> Self {
87        self.set_style(style);
88        self
89    }
90
91    /// A convenience builder-like function for a progress bar with a given tab width
92    pub fn with_tab_width(self, tab_width: usize) -> Self {
93        self.state().set_tab_width(tab_width);
94        self
95    }
96
97    /// A convenience builder-like function for a progress bar with a given prefix
98    ///
99    /// For the prefix to be visible, the `{prefix}` placeholder must be present in the template
100    /// (see [`ProgressStyle`]).
101    pub fn with_prefix(self, prefix: impl Into<Cow<'static, str>>) -> Self {
102        let mut state = self.state();
103        state.state.prefix = TabExpandedString::new(prefix.into(), state.tab_width);
104        drop(state);
105        self
106    }
107
108    /// A convenience builder-like function for a progress bar with a given message
109    ///
110    /// For the message to be visible, the `{msg}` placeholder must be present in the template (see
111    /// [`ProgressStyle`]).
112    pub fn with_message(self, message: impl Into<Cow<'static, str>>) -> Self {
113        let mut state = self.state();
114        state.state.message = TabExpandedString::new(message.into(), state.tab_width);
115        drop(state);
116        self
117    }
118
119    /// A convenience builder-like function for a progress bar with a given position
120    pub fn with_position(self, pos: u64) -> Self {
121        self.state().state.set_pos(pos);
122        self
123    }
124
125    /// A convenience builder-like function for a progress bar with a given elapsed time
126    pub fn with_elapsed(self, elapsed: Duration) -> Self {
127        self.state().state.started = Instant::now().checked_sub(elapsed).unwrap();
128        self
129    }
130
131    /// Sets the finish behavior for the progress bar
132    ///
133    /// This behavior is invoked when [`ProgressBar`] or
134    /// [`ProgressBarIter`] completes and
135    /// [`ProgressBar::is_finished()`] is false.
136    /// If you don't want the progress bar to be automatically finished then
137    /// call `with_finish(Abandon)`.
138    ///
139    /// [`ProgressBar`]: crate::ProgressBar
140    /// [`ProgressBarIter`]: crate::ProgressBarIter
141    /// [`ProgressBar::is_finished()`]: crate::ProgressBar::is_finished
142    pub fn with_finish(self, finish: ProgressFinish) -> Self {
143        self.state().on_finish = finish;
144        self
145    }
146
147    /// Creates a new spinner
148    ///
149    /// This spinner by default draws directly to stderr. This adds the default spinner style to it.
150    pub fn new_spinner() -> Self {
151        let rv = Self::with_draw_target(None, ProgressDrawTarget::stderr());
152        rv.set_style(ProgressStyle::default_spinner());
153        rv
154    }
155
156    /// Overrides the stored style
157    ///
158    /// This does not redraw the bar. Call [`ProgressBar::tick()`] to force it.
159    pub fn set_style(&self, style: ProgressStyle) {
160        self.state().set_style(style);
161    }
162
163    /// Sets the tab width (default: 8). All tabs will be expanded to this many spaces.
164    pub fn set_tab_width(&mut self, tab_width: usize) {
165        let mut state = self.state();
166        state.set_tab_width(tab_width);
167        state.draw(true, Instant::now()).unwrap();
168    }
169
170    /// Spawns a background thread to tick the progress bar
171    ///
172    /// When this is enabled a background thread will regularly tick the progress bar in the given
173    /// interval. This is useful to advance progress bars that are very slow by themselves.
174    ///
175    /// When steady ticks are enabled, calling [`ProgressBar::tick()`] on a progress bar does not
176    /// have any effect.
177    pub fn enable_steady_tick(&self, interval: Duration) {
178        // The way we test for ticker termination is with a single static `AtomicBool`. Since cargo
179        // runs tests concurrently, we have a `TICKER_TEST` lock to make sure tests using ticker
180        // don't step on each other. This check catches attempts to use tickers in tests without
181        // acquiring the lock.
182        #[cfg(test)]
183        {
184            let guard = TICKER_TEST.try_lock();
185            let lock_acquired = guard.is_ok();
186            // Drop the guard before panicking to avoid poisoning the lock (which would cause other
187            // ticker tests to fail)
188            drop(guard);
189            if lock_acquired {
190                panic!("you must acquire the TICKER_TEST lock in your test to use this method");
191            }
192        }
193
194        if interval.is_zero() {
195            return;
196        }
197
198        self.stop_and_replace_ticker(Some(interval));
199    }
200
201    /// Undoes [`ProgressBar::enable_steady_tick()`]
202    pub fn disable_steady_tick(&self) {
203        self.stop_and_replace_ticker(None);
204    }
205
206    fn stop_and_replace_ticker(&self, interval: Option<Duration>) {
207        let mut ticker_state = self.ticker.lock().unwrap();
208        if let Some(ticker) = ticker_state.take() {
209            ticker.stop();
210        }
211
212        *ticker_state = interval.map(|interval| Ticker::new(interval, &self.state));
213    }
214
215    /// Manually ticks the spinner or progress bar
216    ///
217    /// This automatically happens on any other change to a progress bar.
218    pub fn tick(&self) {
219        self.tick_inner(Instant::now());
220    }
221
222    fn tick_inner(&self, now: Instant) {
223        // Only tick if a `Ticker` isn't installed
224        if self.ticker.lock().unwrap().is_none() {
225            self.state().tick(now);
226        }
227    }
228
229    /// Advances the position of the progress bar by `delta`
230    pub fn inc(&self, delta: u64) {
231        self.pos.inc(delta);
232        let now = Instant::now();
233        if self.pos.allow(now) {
234            self.tick_inner(now);
235        }
236    }
237
238    /// A quick convenience check if the progress bar is hidden
239    pub fn is_hidden(&self) -> bool {
240        self.state().draw_target.is_hidden()
241    }
242
243    /// Indicates that the progress bar finished
244    pub fn is_finished(&self) -> bool {
245        self.state().state.is_finished()
246    }
247
248    /// Print a log line above the progress bar
249    ///
250    /// If the progress bar is hidden (e.g. when standard output is not a terminal), `println()`
251    /// will not do anything. If you want to write to the standard output in such cases as well, use
252    /// [`ProgressBar::suspend()`] instead.
253    ///
254    /// If the progress bar was added to a [`MultiProgress`], the log line will be
255    /// printed above all other progress bars.
256    ///
257    /// [`ProgressBar::suspend()`]: ProgressBar::suspend
258    /// [`MultiProgress`]: crate::MultiProgress
259    pub fn println<I: AsRef<str>>(&self, msg: I) {
260        self.state().println(Instant::now(), msg.as_ref());
261    }
262
263    /// Update the `ProgressBar`'s inner [`ProgressState`]
264    pub fn update(&self, f: impl FnOnce(&mut ProgressState)) {
265        self.state()
266            .update(Instant::now(), f, self.ticker.lock().unwrap().is_none());
267    }
268
269    /// Sets the position of the progress bar
270    pub fn set_position(&self, pos: u64) {
271        self.pos.set(pos);
272        let now = Instant::now();
273        if self.pos.allow(now) {
274            self.tick_inner(now);
275        }
276    }
277
278    /// Sets the length of the progress bar to `None`
279    pub fn unset_length(&self) {
280        self.state().unset_length(Instant::now());
281    }
282
283    /// Sets the length of the progress bar
284    pub fn set_length(&self, len: u64) {
285        self.state().set_length(Instant::now(), len);
286    }
287
288    /// Increase the length of the progress bar
289    pub fn inc_length(&self, delta: u64) {
290        self.state().inc_length(Instant::now(), delta);
291    }
292
293    /// Sets the current prefix of the progress bar
294    ///
295    /// For the prefix to be visible, the `{prefix}` placeholder must be present in the template
296    /// (see [`ProgressStyle`]).
297    pub fn set_prefix(&self, prefix: impl Into<Cow<'static, str>>) {
298        let mut state = self.state();
299        state.state.prefix = TabExpandedString::new(prefix.into(), state.tab_width);
300        state.update_estimate_and_draw(Instant::now());
301    }
302
303    /// Sets the current message of the progress bar
304    ///
305    /// For the message to be visible, the `{msg}` placeholder must be present in the template (see
306    /// [`ProgressStyle`]).
307    pub fn set_message(&self, msg: impl Into<Cow<'static, str>>) {
308        let mut state = self.state();
309        state.state.message = TabExpandedString::new(msg.into(), state.tab_width);
310        state.update_estimate_and_draw(Instant::now());
311    }
312
313    /// Creates a new weak reference to this [`ProgressBar`]
314    pub fn downgrade(&self) -> WeakProgressBar {
315        WeakProgressBar {
316            state: Arc::downgrade(&self.state),
317            pos: Arc::downgrade(&self.pos),
318            ticker: Arc::downgrade(&self.ticker),
319        }
320    }
321
322    /// Resets the ETA calculation
323    ///
324    /// This can be useful if the progress bars made a large jump or was paused for a prolonged
325    /// time.
326    pub fn reset_eta(&self) {
327        self.state().reset(Instant::now(), Reset::Eta);
328    }
329
330    /// Resets elapsed time and the ETA calculation
331    pub fn reset_elapsed(&self) {
332        self.state().reset(Instant::now(), Reset::Elapsed);
333    }
334
335    /// Resets all of the progress bar state
336    pub fn reset(&self) {
337        self.state().reset(Instant::now(), Reset::All);
338    }
339
340    /// Finishes the progress bar and leaves the current message
341    pub fn finish(&self) {
342        self.state()
343            .finish_using_style(Instant::now(), ProgressFinish::AndLeave);
344    }
345
346    /// Finishes the progress bar and sets a message
347    ///
348    /// For the message to be visible, the `{msg}` placeholder must be present in the template (see
349    /// [`ProgressStyle`]).
350    pub fn finish_with_message(&self, msg: impl Into<Cow<'static, str>>) {
351        self.state()
352            .finish_using_style(Instant::now(), ProgressFinish::WithMessage(msg.into()));
353    }
354
355    /// Finishes the progress bar and completely clears it
356    pub fn finish_and_clear(&self) {
357        self.state()
358            .finish_using_style(Instant::now(), ProgressFinish::AndClear);
359    }
360
361    /// Finishes the progress bar and leaves the current message and progress
362    pub fn abandon(&self) {
363        self.state()
364            .finish_using_style(Instant::now(), ProgressFinish::Abandon);
365    }
366
367    /// Finishes the progress bar and sets a message, and leaves the current progress
368    ///
369    /// For the message to be visible, the `{msg}` placeholder must be present in the template (see
370    /// [`ProgressStyle`]).
371    pub fn abandon_with_message(&self, msg: impl Into<Cow<'static, str>>) {
372        self.state().finish_using_style(
373            Instant::now(),
374            ProgressFinish::AbandonWithMessage(msg.into()),
375        );
376    }
377
378    /// Finishes the progress bar using the behavior stored in the [`ProgressStyle`]
379    ///
380    /// See [`ProgressBar::with_finish()`].
381    pub fn finish_using_style(&self) {
382        let mut state = self.state();
383        let finish = state.on_finish.clone();
384        state.finish_using_style(Instant::now(), finish);
385    }
386
387    /// Sets a different draw target for the progress bar
388    ///
389    /// This can be used to draw the progress bar to stderr (this is the default):
390    ///
391    /// ```rust,no_run
392    /// # use indicatif::{ProgressBar, ProgressDrawTarget};
393    /// let pb = ProgressBar::new(100);
394    /// pb.set_draw_target(ProgressDrawTarget::stderr());
395    /// ```
396    ///
397    /// **Note:** Calling this method on a [`ProgressBar`] linked with a [`MultiProgress`] (after
398    /// running [`MultiProgress::add()`]) will unlink this progress bar. If you don't want this
399    /// behavior, call [`MultiProgress::set_draw_target()`] instead.
400    ///
401    /// Use [`ProgressBar::with_draw_target()`] to set the draw target during creation.
402    ///
403    /// [`MultiProgress`]: crate::MultiProgress
404    /// [`MultiProgress::add()`]: crate::MultiProgress::add
405    /// [`MultiProgress::set_draw_target()`]: crate::MultiProgress::set_draw_target
406    pub fn set_draw_target(&self, target: ProgressDrawTarget) {
407        let mut state = self.state();
408        state.draw_target.disconnect(Instant::now());
409        state.draw_target = target;
410    }
411
412    /// Hide the progress bar temporarily, execute `f`, then redraw the progress bar
413    ///
414    /// Useful for external code that writes to the standard output.
415    ///
416    /// If the progress bar was added to a [`MultiProgress`], it will suspend the entire [`MultiProgress`].
417    ///
418    /// **Note:** The internal lock is held while `f` is executed. Other threads trying to print
419    /// anything on the progress bar will be blocked until `f` finishes.
420    /// Therefore, it is recommended to avoid long-running operations in `f`.
421    ///
422    /// ```rust,no_run
423    /// # use indicatif::ProgressBar;
424    /// let mut pb = ProgressBar::new(3);
425    /// pb.suspend(|| {
426    ///     println!("Log message");
427    /// })
428    /// ```
429    ///
430    /// [`MultiProgress`]: crate::MultiProgress
431    pub fn suspend<F: FnOnce() -> R, R>(&self, f: F) -> R {
432        self.state().suspend(Instant::now(), f)
433    }
434
435    /// Wraps an [`Iterator`] with the progress bar
436    ///
437    /// ```rust,no_run
438    /// # use indicatif::ProgressBar;
439    /// let v = vec![1, 2, 3];
440    /// let pb = ProgressBar::new(3);
441    /// for item in pb.wrap_iter(v.iter()) {
442    ///     // ...
443    /// }
444    /// ```
445    pub fn wrap_iter<It: Iterator>(&self, it: It) -> ProgressBarIter<It> {
446        it.progress_with(self.clone())
447    }
448
449    /// Wraps an [`io::Read`] with the progress bar
450    ///
451    /// ```rust,no_run
452    /// # use std::fs::File;
453    /// # use std::io;
454    /// # use indicatif::ProgressBar;
455    /// # fn test () -> io::Result<()> {
456    /// let source = File::open("work.txt")?;
457    /// let mut target = File::create("done.txt")?;
458    /// let pb = ProgressBar::new(source.metadata()?.len());
459    /// io::copy(&mut pb.wrap_read(source), &mut target);
460    /// # Ok(())
461    /// # }
462    /// ```
463    pub fn wrap_read<R: io::Read>(&self, read: R) -> ProgressBarIter<R> {
464        ProgressBarIter {
465            progress: self.clone(),
466            it: read,
467        }
468    }
469
470    /// Wraps an [`io::Write`] with the progress bar
471    ///
472    /// ```rust,no_run
473    /// # use std::fs::File;
474    /// # use std::io;
475    /// # use indicatif::ProgressBar;
476    /// # fn test () -> io::Result<()> {
477    /// let mut source = File::open("work.txt")?;
478    /// let target = File::create("done.txt")?;
479    /// let pb = ProgressBar::new(source.metadata()?.len());
480    /// io::copy(&mut source, &mut pb.wrap_write(target));
481    /// # Ok(())
482    /// # }
483    /// ```
484    pub fn wrap_write<W: io::Write>(&self, write: W) -> ProgressBarIter<W> {
485        ProgressBarIter {
486            progress: self.clone(),
487            it: write,
488        }
489    }
490
491    #[cfg(feature = "tokio")]
492    #[cfg_attr(docsrs, doc(cfg(feature = "tokio")))]
493    /// Wraps an [`tokio::io::AsyncWrite`] with the progress bar
494    ///
495    /// ```rust,no_run
496    /// # use tokio::fs::File;
497    /// # use tokio::io;
498    /// # use indicatif::ProgressBar;
499    /// # async fn test() -> io::Result<()> {
500    /// let mut source = File::open("work.txt").await?;
501    /// let mut target = File::open("done.txt").await?;
502    /// let pb = ProgressBar::new(source.metadata().await?.len());
503    /// io::copy(&mut source, &mut pb.wrap_async_write(target)).await?;
504    /// # Ok(())
505    /// # }
506    /// ```
507    pub fn wrap_async_write<W: tokio::io::AsyncWrite + Unpin>(
508        &self,
509        write: W,
510    ) -> ProgressBarIter<W> {
511        ProgressBarIter {
512            progress: self.clone(),
513            it: write,
514        }
515    }
516
517    #[cfg(feature = "tokio")]
518    #[cfg_attr(docsrs, doc(cfg(feature = "tokio")))]
519    /// Wraps an [`tokio::io::AsyncRead`] with the progress bar
520    ///
521    /// ```rust,no_run
522    /// # use tokio::fs::File;
523    /// # use tokio::io;
524    /// # use indicatif::ProgressBar;
525    /// # async fn test() -> io::Result<()> {
526    /// let mut source = File::open("work.txt").await?;
527    /// let mut target = File::open("done.txt").await?;
528    /// let pb = ProgressBar::new(source.metadata().await?.len());
529    /// io::copy(&mut pb.wrap_async_read(source), &mut target).await?;
530    /// # Ok(())
531    /// # }
532    /// ```
533    pub fn wrap_async_read<R: tokio::io::AsyncRead + Unpin>(&self, read: R) -> ProgressBarIter<R> {
534        ProgressBarIter {
535            progress: self.clone(),
536            it: read,
537        }
538    }
539
540    /// Wraps a [`futures::Stream`](https://docs.rs/futures/0.3/futures/stream/trait.StreamExt.html) with the progress bar
541    ///
542    /// ```
543    /// # use indicatif::ProgressBar;
544    /// # futures::executor::block_on(async {
545    /// use futures::stream::{self, StreamExt};
546    /// let pb = ProgressBar::new(10);
547    /// let mut stream = pb.wrap_stream(stream::iter('a'..='z'));
548    ///
549    /// assert_eq!(stream.next().await, Some('a'));
550    /// assert_eq!(stream.count().await, 25);
551    /// # }); // block_on
552    /// ```
553    #[cfg(feature = "futures")]
554    #[cfg_attr(docsrs, doc(cfg(feature = "futures")))]
555    pub fn wrap_stream<S: futures_core::Stream>(&self, stream: S) -> ProgressBarIter<S> {
556        ProgressBarIter {
557            progress: self.clone(),
558            it: stream,
559        }
560    }
561
562    /// Returns the current position
563    pub fn position(&self) -> u64 {
564        self.state().state.pos()
565    }
566
567    /// Returns the current length
568    pub fn length(&self) -> Option<u64> {
569        self.state().state.len()
570    }
571
572    /// Returns the current ETA
573    pub fn eta(&self) -> Duration {
574        self.state().state.eta()
575    }
576
577    /// Returns the current rate of progress
578    pub fn per_sec(&self) -> f64 {
579        self.state().state.per_sec()
580    }
581
582    /// Returns the current expected duration
583    pub fn duration(&self) -> Duration {
584        self.state().state.duration()
585    }
586
587    /// Returns the current elapsed time
588    pub fn elapsed(&self) -> Duration {
589        self.state().state.elapsed()
590    }
591
592    /// Index in the `MultiState`
593    pub(crate) fn index(&self) -> Option<usize> {
594        self.state().draw_target.remote().map(|(_, idx)| idx)
595    }
596
597    /// Current message
598    pub fn message(&self) -> String {
599        self.state().state.message.expanded().to_string()
600    }
601
602    /// Current prefix
603    pub fn prefix(&self) -> String {
604        self.state().state.prefix.expanded().to_string()
605    }
606
607    #[inline]
608    pub(crate) fn state(&self) -> MutexGuard<'_, BarState> {
609        self.state.lock().unwrap()
610    }
611}
612
613/// A weak reference to a [`ProgressBar`].
614///
615/// Useful for creating custom steady tick implementations
616#[derive(Clone, Default)]
617pub struct WeakProgressBar {
618    state: Weak<Mutex<BarState>>,
619    pos: Weak<AtomicPosition>,
620    ticker: Weak<Mutex<Option<Ticker>>>,
621}
622
623impl WeakProgressBar {
624    /// Create a new [`WeakProgressBar`] that returns `None` when [`upgrade()`] is called.
625    ///
626    /// [`upgrade()`]: WeakProgressBar::upgrade
627    pub fn new() -> Self {
628        Self::default()
629    }
630
631    /// Attempts to upgrade the Weak pointer to a [`ProgressBar`], delaying dropping of the inner
632    /// value if successful. Returns [`None`] if the inner value has since been dropped.
633    ///
634    /// [`ProgressBar`]: struct.ProgressBar.html
635    pub fn upgrade(&self) -> Option<ProgressBar> {
636        let state = self.state.upgrade()?;
637        let pos = self.pos.upgrade()?;
638        let ticker = self.ticker.upgrade()?;
639        Some(ProgressBar { state, pos, ticker })
640    }
641}
642
643pub(crate) struct Ticker {
644    stopping: Arc<(Mutex<bool>, Condvar)>,
645    join_handle: Option<thread::JoinHandle<()>>,
646}
647
648impl Drop for Ticker {
649    fn drop(&mut self) {
650        self.stop();
651        self.join_handle.take().map(|handle| handle.join());
652    }
653}
654
655#[cfg(test)]
656static TICKER_RUNNING: AtomicBool = AtomicBool::new(false);
657
658impl Ticker {
659    pub(crate) fn new(interval: Duration, bar_state: &Arc<Mutex<BarState>>) -> Self {
660        debug_assert!(!interval.is_zero());
661
662        // A `Mutex<bool>` is used as a flag to indicate whether the ticker was requested to stop.
663        // The `Condvar` is used a notification mechanism: when the ticker is dropped, we notify
664        // the thread and interrupt the ticker wait.
665        #[allow(clippy::mutex_atomic)]
666        let stopping = Arc::new((Mutex::new(false), Condvar::new()));
667        let control = TickerControl {
668            stopping: stopping.clone(),
669            state: Arc::downgrade(bar_state),
670        };
671
672        let join_handle = thread::spawn(move || control.run(interval));
673        Self {
674            stopping,
675            join_handle: Some(join_handle),
676        }
677    }
678
679    pub(crate) fn stop(&self) {
680        *self.stopping.0.lock().unwrap() = true;
681        self.stopping.1.notify_one();
682    }
683}
684
685struct TickerControl {
686    stopping: Arc<(Mutex<bool>, Condvar)>,
687    state: Weak<Mutex<BarState>>,
688}
689
690impl TickerControl {
691    fn run(&self, interval: Duration) {
692        #[cfg(test)]
693        TICKER_RUNNING.store(true, Ordering::SeqCst);
694
695        while let Some(arc) = self.state.upgrade() {
696            let mut state = arc.lock().unwrap();
697            if state.state.is_finished() {
698                break;
699            }
700
701            state.tick(Instant::now());
702
703            drop(state); // Don't forget to drop the lock before sleeping
704            drop(arc); // Also need to drop Arc otherwise BarState won't be dropped
705
706            // Wait for `interval` but return early if we are notified to stop
707            let result = self
708                .stopping
709                .1
710                .wait_timeout_while(self.stopping.0.lock().unwrap(), interval, |stopped| {
711                    !*stopped
712                })
713                .unwrap();
714
715            // If the wait didn't time out, it means we were notified to stop
716            if !result.1.timed_out() {
717                break;
718            }
719        }
720
721        #[cfg(test)]
722        TICKER_RUNNING.store(false, Ordering::SeqCst);
723    }
724}
725
726// Tests using the global TICKER_RUNNING flag need to be serialized
727#[cfg(test)]
728pub(crate) static TICKER_TEST: Lazy<Mutex<()>> = Lazy::new(Mutex::default);
729
730#[cfg(test)]
731mod tests {
732    use super::*;
733
734    #[allow(clippy::float_cmp)]
735    #[test]
736    fn test_pbar_zero() {
737        let pb = ProgressBar::new(0);
738        assert_eq!(pb.state().state.fraction(), 1.0);
739    }
740
741    #[allow(clippy::float_cmp)]
742    #[test]
743    fn test_pbar_maxu64() {
744        let pb = ProgressBar::new(!0);
745        assert_eq!(pb.state().state.fraction(), 0.0);
746    }
747
748    #[test]
749    fn test_pbar_overflow() {
750        let pb = ProgressBar::new(1);
751        pb.set_draw_target(ProgressDrawTarget::hidden());
752        pb.inc(2);
753        pb.finish();
754    }
755
756    #[test]
757    fn test_get_position() {
758        let pb = ProgressBar::new(1);
759        pb.set_draw_target(ProgressDrawTarget::hidden());
760        pb.inc(2);
761        let pos = pb.position();
762        assert_eq!(pos, 2);
763    }
764
765    #[test]
766    fn test_weak_pb() {
767        let pb = ProgressBar::new(0);
768        let weak = pb.downgrade();
769        assert!(weak.upgrade().is_some());
770        ::std::mem::drop(pb);
771        assert!(weak.upgrade().is_none());
772    }
773
774    #[test]
775    fn it_can_wrap_a_reader() {
776        let bytes = &b"I am an implementation of io::Read"[..];
777        let pb = ProgressBar::new(bytes.len() as u64);
778        let mut reader = pb.wrap_read(bytes);
779        let mut writer = Vec::new();
780        io::copy(&mut reader, &mut writer).unwrap();
781        assert_eq!(writer, bytes);
782    }
783
784    #[test]
785    fn it_can_wrap_a_writer() {
786        let bytes = b"implementation of io::Read";
787        let mut reader = &bytes[..];
788        let pb = ProgressBar::new(bytes.len() as u64);
789        let writer = Vec::new();
790        let mut writer = pb.wrap_write(writer);
791        io::copy(&mut reader, &mut writer).unwrap();
792        assert_eq!(writer.it, bytes);
793    }
794
795    #[test]
796    fn ticker_thread_terminates_on_drop() {
797        let _guard = TICKER_TEST.lock().unwrap();
798        assert!(!TICKER_RUNNING.load(Ordering::SeqCst));
799
800        let pb = ProgressBar::new_spinner();
801        pb.enable_steady_tick(Duration::from_millis(50));
802
803        // Give the thread time to start up
804        thread::sleep(Duration::from_millis(250));
805
806        assert!(TICKER_RUNNING.load(Ordering::SeqCst));
807
808        drop(pb);
809        assert!(!TICKER_RUNNING.load(Ordering::SeqCst));
810    }
811
812    #[test]
813    fn ticker_thread_terminates_on_drop_2() {
814        let _guard = TICKER_TEST.lock().unwrap();
815        assert!(!TICKER_RUNNING.load(Ordering::SeqCst));
816
817        let pb = ProgressBar::new_spinner();
818        pb.enable_steady_tick(Duration::from_millis(50));
819        let pb2 = pb.clone();
820
821        // Give the thread time to start up
822        thread::sleep(Duration::from_millis(250));
823
824        assert!(TICKER_RUNNING.load(Ordering::SeqCst));
825
826        drop(pb);
827        assert!(TICKER_RUNNING.load(Ordering::SeqCst));
828
829        drop(pb2);
830        assert!(!TICKER_RUNNING.load(Ordering::SeqCst));
831    }
832}