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(&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 /// Decrease the position of the progress bar by `delta`
239 pub fn dec(&self, delta: u64) {
240 self.pos.dec(delta);
241 let now = Instant::now();
242 if self.pos.allow(now) {
243 self.tick_inner(now);
244 }
245 }
246
247 /// A quick convenience check if the progress bar is hidden
248 pub fn is_hidden(&self) -> bool {
249 self.state().draw_target.is_hidden()
250 }
251
252 /// Indicates that the progress bar finished
253 pub fn is_finished(&self) -> bool {
254 self.state().state.is_finished()
255 }
256
257 /// Print a log line above the progress bar
258 ///
259 /// If the progress bar is hidden (e.g. when standard output is not a terminal), `println()`
260 /// will not do anything. If you want to write to the standard output in such cases as well, use
261 /// [`ProgressBar::suspend()`] instead.
262 ///
263 /// If the progress bar was added to a [`MultiProgress`], the log line will be
264 /// printed above all other progress bars.
265 ///
266 /// [`ProgressBar::suspend()`]: ProgressBar::suspend
267 /// [`MultiProgress`]: crate::MultiProgress
268 pub fn println<I: AsRef<str>>(&self, msg: I) {
269 self.state().println(Instant::now(), msg.as_ref());
270 }
271
272 /// Update the `ProgressBar`'s inner [`ProgressState`]
273 pub fn update(&self, f: impl FnOnce(&mut ProgressState)) {
274 self.state()
275 .update(Instant::now(), f, self.ticker.lock().unwrap().is_none());
276 }
277
278 /// Sets the position of the progress bar
279 pub fn set_position(&self, pos: u64) {
280 self.pos.set(pos);
281 let now = Instant::now();
282 if self.pos.allow(now) {
283 self.tick_inner(now);
284 }
285 }
286
287 /// Sets the length of the progress bar to `None`
288 pub fn unset_length(&self) {
289 self.state().unset_length(Instant::now());
290 }
291
292 /// Sets the length of the progress bar
293 pub fn set_length(&self, len: u64) {
294 self.state().set_length(Instant::now(), len);
295 }
296
297 /// Increase the length of the progress bar
298 pub fn inc_length(&self, delta: u64) {
299 self.state().inc_length(Instant::now(), delta);
300 }
301
302 /// Decrease the length of the progress bar
303 pub fn dec_length(&self, delta: u64) {
304 self.state().dec_length(Instant::now(), delta);
305 }
306
307 /// Sets the current prefix of the progress bar
308 ///
309 /// For the prefix to be visible, the `{prefix}` placeholder must be present in the template
310 /// (see [`ProgressStyle`]).
311 pub fn set_prefix(&self, prefix: impl Into<Cow<'static, str>>) {
312 let mut state = self.state();
313 state.state.prefix = TabExpandedString::new(prefix.into(), state.tab_width);
314 state.update_estimate_and_draw(Instant::now());
315 }
316
317 /// Sets the current message of the progress bar
318 ///
319 /// For the message to be visible, the `{msg}` placeholder must be present in the template (see
320 /// [`ProgressStyle`]).
321 pub fn set_message(&self, msg: impl Into<Cow<'static, str>>) {
322 let mut state = self.state();
323 state.state.message = TabExpandedString::new(msg.into(), state.tab_width);
324 state.update_estimate_and_draw(Instant::now());
325 }
326
327 /// Creates a new weak reference to this [`ProgressBar`]
328 pub fn downgrade(&self) -> WeakProgressBar {
329 WeakProgressBar {
330 state: Arc::downgrade(&self.state),
331 pos: Arc::downgrade(&self.pos),
332 ticker: Arc::downgrade(&self.ticker),
333 }
334 }
335
336 /// Resets the ETA calculation
337 ///
338 /// This can be useful if the progress bars made a large jump or was paused for a prolonged
339 /// time.
340 pub fn reset_eta(&self) {
341 self.state().reset(Instant::now(), Reset::Eta);
342 }
343
344 /// Resets elapsed time and the ETA calculation
345 pub fn reset_elapsed(&self) {
346 self.state().reset(Instant::now(), Reset::Elapsed);
347 }
348
349 /// Resets all of the progress bar state
350 pub fn reset(&self) {
351 self.state().reset(Instant::now(), Reset::All);
352 }
353
354 /// Finishes the progress bar and leaves the current message
355 pub fn finish(&self) {
356 self.state()
357 .finish_using_style(Instant::now(), ProgressFinish::AndLeave);
358 }
359
360 /// Finishes the progress bar and sets a message
361 ///
362 /// For the message to be visible, the `{msg}` placeholder must be present in the template (see
363 /// [`ProgressStyle`]).
364 pub fn finish_with_message(&self, msg: impl Into<Cow<'static, str>>) {
365 self.state()
366 .finish_using_style(Instant::now(), ProgressFinish::WithMessage(msg.into()));
367 }
368
369 /// Finishes the progress bar and completely clears it
370 pub fn finish_and_clear(&self) {
371 self.state()
372 .finish_using_style(Instant::now(), ProgressFinish::AndClear);
373 }
374
375 /// Finishes the progress bar and leaves the current message and progress
376 pub fn abandon(&self) {
377 self.state()
378 .finish_using_style(Instant::now(), ProgressFinish::Abandon);
379 }
380
381 /// Finishes the progress bar and sets a message, and leaves the current progress
382 ///
383 /// For the message to be visible, the `{msg}` placeholder must be present in the template (see
384 /// [`ProgressStyle`]).
385 pub fn abandon_with_message(&self, msg: impl Into<Cow<'static, str>>) {
386 self.state().finish_using_style(
387 Instant::now(),
388 ProgressFinish::AbandonWithMessage(msg.into()),
389 );
390 }
391
392 /// Finishes the progress bar using the behavior stored in the [`ProgressStyle`]
393 ///
394 /// See [`ProgressBar::with_finish()`].
395 pub fn finish_using_style(&self) {
396 let mut state = self.state();
397 let finish = state.on_finish.clone();
398 state.finish_using_style(Instant::now(), finish);
399 }
400
401 /// Sets a different draw target for the progress bar
402 ///
403 /// This can be used to draw the progress bar to stderr (this is the default):
404 ///
405 /// ```rust,no_run
406 /// # use indicatif::{ProgressBar, ProgressDrawTarget};
407 /// let pb = ProgressBar::new(100);
408 /// pb.set_draw_target(ProgressDrawTarget::stderr());
409 /// ```
410 ///
411 /// **Note:** Calling this method on a [`ProgressBar`] linked with a [`MultiProgress`] (after
412 /// running [`MultiProgress::add()`]) will unlink this progress bar. If you don't want this
413 /// behavior, call [`MultiProgress::set_draw_target()`] instead.
414 ///
415 /// Use [`ProgressBar::with_draw_target()`] to set the draw target during creation.
416 ///
417 /// [`MultiProgress`]: crate::MultiProgress
418 /// [`MultiProgress::add()`]: crate::MultiProgress::add
419 /// [`MultiProgress::set_draw_target()`]: crate::MultiProgress::set_draw_target
420 pub fn set_draw_target(&self, target: ProgressDrawTarget) {
421 let mut state = self.state();
422 state.draw_target.disconnect(Instant::now());
423 state.draw_target = target;
424 }
425
426 /// Hide the progress bar temporarily, execute `f`, then redraw the progress bar
427 ///
428 /// Useful for external code that writes to the standard output.
429 ///
430 /// If the progress bar was added to a [`MultiProgress`], it will suspend the entire [`MultiProgress`].
431 ///
432 /// **Note:** The internal lock is held while `f` is executed. Other threads trying to print
433 /// anything on the progress bar will be blocked until `f` finishes.
434 /// Therefore, it is recommended to avoid long-running operations in `f`.
435 ///
436 /// ```rust,no_run
437 /// # use indicatif::ProgressBar;
438 /// let mut pb = ProgressBar::new(3);
439 /// pb.suspend(|| {
440 /// println!("Log message");
441 /// })
442 /// ```
443 ///
444 /// [`MultiProgress`]: crate::MultiProgress
445 pub fn suspend<F: FnOnce() -> R, R>(&self, f: F) -> R {
446 self.state().suspend(Instant::now(), f)
447 }
448
449 /// Wraps an [`Iterator`] with the progress bar
450 ///
451 /// ```rust,no_run
452 /// # use indicatif::ProgressBar;
453 /// let v = vec![1, 2, 3];
454 /// let pb = ProgressBar::new(3);
455 /// for item in pb.wrap_iter(v.iter()) {
456 /// // ...
457 /// }
458 /// ```
459 pub fn wrap_iter<It: Iterator>(&self, it: It) -> ProgressBarIter<It> {
460 it.progress_with(self.clone())
461 }
462
463 /// Wraps an [`io::Read`] with the progress bar
464 ///
465 /// ```rust,no_run
466 /// # use std::fs::File;
467 /// # use std::io;
468 /// # use indicatif::ProgressBar;
469 /// # fn test () -> io::Result<()> {
470 /// let source = File::open("work.txt")?;
471 /// let mut target = File::create("done.txt")?;
472 /// let pb = ProgressBar::new(source.metadata()?.len());
473 /// io::copy(&mut pb.wrap_read(source), &mut target);
474 /// # Ok(())
475 /// # }
476 /// ```
477 pub fn wrap_read<R: io::Read>(&self, read: R) -> ProgressBarIter<R> {
478 ProgressBarIter {
479 progress: self.clone(),
480 it: read,
481 }
482 }
483
484 /// Wraps an [`io::Write`] with the progress bar
485 ///
486 /// ```rust,no_run
487 /// # use std::fs::File;
488 /// # use std::io;
489 /// # use indicatif::ProgressBar;
490 /// # fn test () -> io::Result<()> {
491 /// let mut source = File::open("work.txt")?;
492 /// let target = File::create("done.txt")?;
493 /// let pb = ProgressBar::new(source.metadata()?.len());
494 /// io::copy(&mut source, &mut pb.wrap_write(target));
495 /// # Ok(())
496 /// # }
497 /// ```
498 pub fn wrap_write<W: io::Write>(&self, write: W) -> ProgressBarIter<W> {
499 ProgressBarIter {
500 progress: self.clone(),
501 it: write,
502 }
503 }
504
505 #[cfg(feature = "tokio")]
506 #[cfg_attr(docsrs, doc(cfg(feature = "tokio")))]
507 /// Wraps an [`tokio::io::AsyncWrite`] with the progress bar
508 ///
509 /// ```rust,no_run
510 /// # use tokio::fs::File;
511 /// # use tokio::io;
512 /// # use indicatif::ProgressBar;
513 /// # async fn test() -> io::Result<()> {
514 /// let mut source = File::open("work.txt").await?;
515 /// let mut target = File::open("done.txt").await?;
516 /// let pb = ProgressBar::new(source.metadata().await?.len());
517 /// io::copy(&mut source, &mut pb.wrap_async_write(target)).await?;
518 /// # Ok(())
519 /// # }
520 /// ```
521 pub fn wrap_async_write<W: tokio::io::AsyncWrite + Unpin>(
522 &self,
523 write: W,
524 ) -> ProgressBarIter<W> {
525 ProgressBarIter {
526 progress: self.clone(),
527 it: write,
528 }
529 }
530
531 #[cfg(feature = "tokio")]
532 #[cfg_attr(docsrs, doc(cfg(feature = "tokio")))]
533 /// Wraps an [`tokio::io::AsyncRead`] with the progress bar
534 ///
535 /// ```rust,no_run
536 /// # use tokio::fs::File;
537 /// # use tokio::io;
538 /// # use indicatif::ProgressBar;
539 /// # async fn test() -> io::Result<()> {
540 /// let mut source = File::open("work.txt").await?;
541 /// let mut target = File::open("done.txt").await?;
542 /// let pb = ProgressBar::new(source.metadata().await?.len());
543 /// io::copy(&mut pb.wrap_async_read(source), &mut target).await?;
544 /// # Ok(())
545 /// # }
546 /// ```
547 pub fn wrap_async_read<R: tokio::io::AsyncRead + Unpin>(&self, read: R) -> ProgressBarIter<R> {
548 ProgressBarIter {
549 progress: self.clone(),
550 it: read,
551 }
552 }
553
554 /// Wraps a [`futures::Stream`](https://docs.rs/futures/0.3/futures/stream/trait.StreamExt.html) with the progress bar
555 ///
556 /// ```
557 /// # use indicatif::ProgressBar;
558 /// # futures::executor::block_on(async {
559 /// use futures::stream::{self, StreamExt};
560 /// let pb = ProgressBar::new(10);
561 /// let mut stream = pb.wrap_stream(stream::iter('a'..='z'));
562 ///
563 /// assert_eq!(stream.next().await, Some('a'));
564 /// assert_eq!(stream.count().await, 25);
565 /// # }); // block_on
566 /// ```
567 #[cfg(feature = "futures")]
568 #[cfg_attr(docsrs, doc(cfg(feature = "futures")))]
569 pub fn wrap_stream<S: futures_core::Stream>(&self, stream: S) -> ProgressBarIter<S> {
570 ProgressBarIter {
571 progress: self.clone(),
572 it: stream,
573 }
574 }
575
576 /// Returns the current position
577 pub fn position(&self) -> u64 {
578 self.state().state.pos()
579 }
580
581 /// Returns the current length
582 pub fn length(&self) -> Option<u64> {
583 self.state().state.len()
584 }
585
586 /// Returns the current ETA
587 pub fn eta(&self) -> Duration {
588 self.state().state.eta()
589 }
590
591 /// Returns the current rate of progress
592 pub fn per_sec(&self) -> f64 {
593 self.state().state.per_sec()
594 }
595
596 /// Returns the current expected duration
597 pub fn duration(&self) -> Duration {
598 self.state().state.duration()
599 }
600
601 /// Returns the current elapsed time
602 pub fn elapsed(&self) -> Duration {
603 self.state().state.elapsed()
604 }
605
606 /// Index in the `MultiState`
607 pub(crate) fn index(&self) -> Option<usize> {
608 self.state().draw_target.remote().map(|(_, idx)| idx)
609 }
610
611 /// Current message
612 pub fn message(&self) -> String {
613 self.state().state.message.expanded().to_string()
614 }
615
616 /// Current prefix
617 pub fn prefix(&self) -> String {
618 self.state().state.prefix.expanded().to_string()
619 }
620
621 #[inline]
622 pub(crate) fn state(&self) -> MutexGuard<'_, BarState> {
623 self.state.lock().unwrap()
624 }
625}
626
627/// A weak reference to a [`ProgressBar`].
628///
629/// Useful for creating custom steady tick implementations
630#[derive(Clone, Default)]
631pub struct WeakProgressBar {
632 state: Weak<Mutex<BarState>>,
633 pos: Weak<AtomicPosition>,
634 ticker: Weak<Mutex<Option<Ticker>>>,
635}
636
637impl WeakProgressBar {
638 /// Create a new [`WeakProgressBar`] that returns `None` when [`upgrade()`] is called.
639 ///
640 /// [`upgrade()`]: WeakProgressBar::upgrade
641 pub fn new() -> Self {
642 Self::default()
643 }
644
645 /// Attempts to upgrade the Weak pointer to a [`ProgressBar`], delaying dropping of the inner
646 /// value if successful. Returns [`None`] if the inner value has since been dropped.
647 ///
648 /// [`ProgressBar`]: struct.ProgressBar.html
649 pub fn upgrade(&self) -> Option<ProgressBar> {
650 let state = self.state.upgrade()?;
651 let pos = self.pos.upgrade()?;
652 let ticker = self.ticker.upgrade()?;
653 Some(ProgressBar { state, pos, ticker })
654 }
655}
656
657pub(crate) struct Ticker {
658 stopping: Arc<(Mutex<bool>, Condvar)>,
659 join_handle: Option<thread::JoinHandle<()>>,
660}
661
662impl Drop for Ticker {
663 fn drop(&mut self) {
664 self.stop();
665 self.join_handle.take().map(|handle| handle.join());
666 }
667}
668
669#[cfg(test)]
670static TICKER_RUNNING: AtomicBool = AtomicBool::new(false);
671
672impl Ticker {
673 pub(crate) fn new(interval: Duration, bar_state: &Arc<Mutex<BarState>>) -> Self {
674 debug_assert!(!interval.is_zero());
675
676 // A `Mutex<bool>` is used as a flag to indicate whether the ticker was requested to stop.
677 // The `Condvar` is used a notification mechanism: when the ticker is dropped, we notify
678 // the thread and interrupt the ticker wait.
679 #[allow(clippy::mutex_atomic)]
680 let stopping = Arc::new((Mutex::new(false), Condvar::new()));
681 let control = TickerControl {
682 stopping: stopping.clone(),
683 state: Arc::downgrade(bar_state),
684 };
685
686 let join_handle = thread::spawn(move || control.run(interval));
687 Self {
688 stopping,
689 join_handle: Some(join_handle),
690 }
691 }
692
693 pub(crate) fn stop(&self) {
694 *self.stopping.0.lock().unwrap() = true;
695 self.stopping.1.notify_one();
696 }
697}
698
699struct TickerControl {
700 stopping: Arc<(Mutex<bool>, Condvar)>,
701 state: Weak<Mutex<BarState>>,
702}
703
704impl TickerControl {
705 fn run(&self, interval: Duration) {
706 #[cfg(test)]
707 TICKER_RUNNING.store(true, Ordering::SeqCst);
708
709 while let Some(arc) = self.state.upgrade() {
710 let mut state = arc.lock().unwrap();
711 if state.state.is_finished() {
712 break;
713 }
714
715 state.tick(Instant::now());
716
717 drop(state); // Don't forget to drop the lock before sleeping
718 drop(arc); // Also need to drop Arc otherwise BarState won't be dropped
719
720 // Wait for `interval` but return early if we are notified to stop
721 let result = self
722 .stopping
723 .1
724 .wait_timeout_while(self.stopping.0.lock().unwrap(), interval, |stopped| {
725 !*stopped
726 })
727 .unwrap();
728
729 // If the wait didn't time out, it means we were notified to stop
730 if !result.1.timed_out() {
731 break;
732 }
733 }
734
735 #[cfg(test)]
736 TICKER_RUNNING.store(false, Ordering::SeqCst);
737 }
738}
739
740// Tests using the global TICKER_RUNNING flag need to be serialized
741#[cfg(test)]
742pub(crate) static TICKER_TEST: Lazy<Mutex<()>> = Lazy::new(Mutex::default);
743
744#[cfg(test)]
745mod tests {
746 use super::*;
747
748 #[allow(clippy::float_cmp)]
749 #[test]
750 fn test_pbar_zero() {
751 let pb = ProgressBar::new(0);
752 assert_eq!(pb.state().state.fraction(), 1.0);
753 }
754
755 #[allow(clippy::float_cmp)]
756 #[test]
757 fn test_pbar_maxu64() {
758 let pb = ProgressBar::new(!0);
759 assert_eq!(pb.state().state.fraction(), 0.0);
760 }
761
762 #[test]
763 fn test_pbar_overflow() {
764 let pb = ProgressBar::new(1);
765 pb.set_draw_target(ProgressDrawTarget::hidden());
766 pb.inc(2);
767 pb.finish();
768 }
769
770 #[test]
771 fn test_get_position() {
772 let pb = ProgressBar::new(1);
773 pb.set_draw_target(ProgressDrawTarget::hidden());
774 pb.inc(2);
775 let pos = pb.position();
776 assert_eq!(pos, 2);
777 }
778
779 #[test]
780 fn test_weak_pb() {
781 let pb = ProgressBar::new(0);
782 let weak = pb.downgrade();
783 assert!(weak.upgrade().is_some());
784 ::std::mem::drop(pb);
785 assert!(weak.upgrade().is_none());
786 }
787
788 #[test]
789 fn it_can_wrap_a_reader() {
790 let bytes = &b"I am an implementation of io::Read"[..];
791 let pb = ProgressBar::new(bytes.len() as u64);
792 let mut reader = pb.wrap_read(bytes);
793 let mut writer = Vec::new();
794 io::copy(&mut reader, &mut writer).unwrap();
795 assert_eq!(writer, bytes);
796 }
797
798 #[test]
799 fn it_can_wrap_a_writer() {
800 let bytes = b"implementation of io::Read";
801 let mut reader = &bytes[..];
802 let pb = ProgressBar::new(bytes.len() as u64);
803 let writer = Vec::new();
804 let mut writer = pb.wrap_write(writer);
805 io::copy(&mut reader, &mut writer).unwrap();
806 assert_eq!(writer.it, bytes);
807 }
808
809 #[test]
810 fn ticker_thread_terminates_on_drop() {
811 let _guard = TICKER_TEST.lock().unwrap();
812 assert!(!TICKER_RUNNING.load(Ordering::SeqCst));
813
814 let pb = ProgressBar::new_spinner();
815 pb.enable_steady_tick(Duration::from_millis(50));
816
817 // Give the thread time to start up
818 thread::sleep(Duration::from_millis(250));
819
820 assert!(TICKER_RUNNING.load(Ordering::SeqCst));
821
822 drop(pb);
823 assert!(!TICKER_RUNNING.load(Ordering::SeqCst));
824 }
825
826 #[test]
827 fn ticker_thread_terminates_on_drop_2() {
828 let _guard = TICKER_TEST.lock().unwrap();
829 assert!(!TICKER_RUNNING.load(Ordering::SeqCst));
830
831 let pb = ProgressBar::new_spinner();
832 pb.enable_steady_tick(Duration::from_millis(50));
833 let pb2 = pb.clone();
834
835 // Give the thread time to start up
836 thread::sleep(Duration::from_millis(250));
837
838 assert!(TICKER_RUNNING.load(Ordering::SeqCst));
839
840 drop(pb);
841 assert!(TICKER_RUNNING.load(Ordering::SeqCst));
842
843 drop(pb2);
844 assert!(!TICKER_RUNNING.load(Ordering::SeqCst));
845 }
846}