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