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, 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 }
502 }
503
504 /// Wraps an [`io::Write`] with the progress bar
505 ///
506 /// ```rust,no_run
507 /// # use std::fs::File;
508 /// # use std::io;
509 /// # use indicatif::ProgressBar;
510 /// # fn test () -> io::Result<()> {
511 /// let mut source = File::open("work.txt")?;
512 /// let target = File::create("done.txt")?;
513 /// let pb = ProgressBar::new(source.metadata()?.len());
514 /// io::copy(&mut source, &mut pb.wrap_write(target));
515 /// # Ok(())
516 /// # }
517 /// ```
518 pub fn wrap_write<W: io::Write>(&self, write: W) -> ProgressBarIter<W> {
519 ProgressBarIter {
520 progress: self.clone(),
521 it: write,
522 }
523 }
524
525 #[cfg(feature = "tokio")]
526 #[cfg_attr(docsrs, doc(cfg(feature = "tokio")))]
527 /// Wraps an [`tokio::io::AsyncWrite`] with the progress bar
528 ///
529 /// ```rust,no_run
530 /// # use tokio::fs::File;
531 /// # use tokio::io;
532 /// # use indicatif::ProgressBar;
533 /// # async fn test() -> io::Result<()> {
534 /// let mut source = File::open("work.txt").await?;
535 /// let mut target = File::open("done.txt").await?;
536 /// let pb = ProgressBar::new(source.metadata().await?.len());
537 /// io::copy(&mut source, &mut pb.wrap_async_write(target)).await?;
538 /// # Ok(())
539 /// # }
540 /// ```
541 pub fn wrap_async_write<W: tokio::io::AsyncWrite + Unpin>(
542 &self,
543 write: W,
544 ) -> ProgressBarIter<W> {
545 ProgressBarIter {
546 progress: self.clone(),
547 it: write,
548 }
549 }
550
551 #[cfg(feature = "tokio")]
552 #[cfg_attr(docsrs, doc(cfg(feature = "tokio")))]
553 /// Wraps an [`tokio::io::AsyncRead`] with the progress bar
554 ///
555 /// ```rust,no_run
556 /// # use tokio::fs::File;
557 /// # use tokio::io;
558 /// # use indicatif::ProgressBar;
559 /// # async fn test() -> io::Result<()> {
560 /// let mut source = File::open("work.txt").await?;
561 /// let mut target = File::open("done.txt").await?;
562 /// let pb = ProgressBar::new(source.metadata().await?.len());
563 /// io::copy(&mut pb.wrap_async_read(source), &mut target).await?;
564 /// # Ok(())
565 /// # }
566 /// ```
567 pub fn wrap_async_read<R: tokio::io::AsyncRead + Unpin>(&self, read: R) -> ProgressBarIter<R> {
568 ProgressBarIter {
569 progress: self.clone(),
570 it: read,
571 }
572 }
573
574 /// Wraps a [`futures::Stream`](https://docs.rs/futures/0.3/futures/stream/trait.StreamExt.html) with the progress bar
575 ///
576 /// ```
577 /// # use indicatif::ProgressBar;
578 /// # futures::executor::block_on(async {
579 /// use futures::stream::{self, StreamExt};
580 /// let pb = ProgressBar::new(10);
581 /// let mut stream = pb.wrap_stream(stream::iter('a'..='z'));
582 ///
583 /// assert_eq!(stream.next().await, Some('a'));
584 /// assert_eq!(stream.count().await, 25);
585 /// # }); // block_on
586 /// ```
587 #[cfg(feature = "futures")]
588 #[cfg_attr(docsrs, doc(cfg(feature = "futures")))]
589 pub fn wrap_stream<S: futures_core::Stream>(&self, stream: S) -> ProgressBarIter<S> {
590 ProgressBarIter {
591 progress: self.clone(),
592 it: stream,
593 }
594 }
595
596 /// Returns the current position
597 pub fn position(&self) -> u64 {
598 self.state().state.pos()
599 }
600
601 /// Returns the current length
602 pub fn length(&self) -> Option<u64> {
603 self.state().state.len()
604 }
605
606 /// Returns the current ETA
607 pub fn eta(&self) -> Duration {
608 self.state().state.eta()
609 }
610
611 /// Returns the current rate of progress
612 pub fn per_sec(&self) -> f64 {
613 self.state().state.per_sec()
614 }
615
616 /// Returns the current expected duration
617 pub fn duration(&self) -> Duration {
618 self.state().state.duration()
619 }
620
621 /// Returns the current elapsed time
622 pub fn elapsed(&self) -> Duration {
623 self.state().state.elapsed()
624 }
625
626 /// Index in the `MultiState`
627 pub(crate) fn index(&self) -> Option<usize> {
628 self.state().draw_target.remote().map(|(_, idx)| idx)
629 }
630
631 /// Current message
632 pub fn message(&self) -> String {
633 self.state().state.message.expanded().to_string()
634 }
635
636 /// Current prefix
637 pub fn prefix(&self) -> String {
638 self.state().state.prefix.expanded().to_string()
639 }
640
641 #[inline]
642 pub(crate) fn state(&self) -> MutexGuard<'_, BarState> {
643 self.state.lock().unwrap()
644 }
645}
646
647/// A weak reference to a [`ProgressBar`].
648///
649/// Useful for creating custom steady tick implementations
650#[derive(Clone, Default)]
651pub struct WeakProgressBar {
652 state: Weak<Mutex<BarState>>,
653 pos: Weak<AtomicPosition>,
654 ticker: Weak<Mutex<Option<Ticker>>>,
655}
656
657impl WeakProgressBar {
658 /// Create a new [`WeakProgressBar`] that returns `None` when [`upgrade()`] is called.
659 ///
660 /// [`upgrade()`]: WeakProgressBar::upgrade
661 pub fn new() -> Self {
662 Self::default()
663 }
664
665 /// Attempts to upgrade the Weak pointer to a [`ProgressBar`], delaying dropping of the inner
666 /// value if successful. Returns [`None`] if the inner value has since been dropped.
667 ///
668 /// [`ProgressBar`]: struct.ProgressBar.html
669 pub fn upgrade(&self) -> Option<ProgressBar> {
670 let state = self.state.upgrade()?;
671 let pos = self.pos.upgrade()?;
672 let ticker = self.ticker.upgrade()?;
673 Some(ProgressBar { state, pos, ticker })
674 }
675}
676
677pub(crate) struct Ticker {
678 stopping: Arc<(Mutex<bool>, Condvar)>,
679 join_handle: Option<thread::JoinHandle<()>>,
680}
681
682impl Drop for Ticker {
683 fn drop(&mut self) {
684 self.stop();
685 self.join_handle.take().map(|handle| handle.join());
686 }
687}
688
689#[cfg(test)]
690static TICKER_RUNNING: AtomicBool = AtomicBool::new(false);
691
692impl Ticker {
693 pub(crate) fn new(interval: Duration, bar_state: &Arc<Mutex<BarState>>) -> Self {
694 debug_assert!(!interval.is_zero());
695
696 // A `Mutex<bool>` is used as a flag to indicate whether the ticker was requested to stop.
697 // The `Condvar` is used a notification mechanism: when the ticker is dropped, we notify
698 // the thread and interrupt the ticker wait.
699 #[allow(clippy::mutex_atomic)]
700 let stopping = Arc::new((Mutex::new(false), Condvar::new()));
701 let control = TickerControl {
702 stopping: stopping.clone(),
703 state: Arc::downgrade(bar_state),
704 };
705
706 let join_handle = thread::spawn(move || control.run(interval));
707 Self {
708 stopping,
709 join_handle: Some(join_handle),
710 }
711 }
712
713 pub(crate) fn stop(&self) {
714 *self.stopping.0.lock().unwrap() = true;
715 self.stopping.1.notify_one();
716 }
717}
718
719struct TickerControl {
720 stopping: Arc<(Mutex<bool>, Condvar)>,
721 state: Weak<Mutex<BarState>>,
722}
723
724impl TickerControl {
725 fn run(&self, interval: Duration) {
726 #[cfg(test)]
727 TICKER_RUNNING.store(true, Ordering::SeqCst);
728
729 while let Some(arc) = self.state.upgrade() {
730 let mut state = arc.lock().unwrap();
731 if state.state.is_finished() {
732 break;
733 }
734
735 state.tick(Instant::now());
736
737 drop(state); // Don't forget to drop the lock before sleeping
738 drop(arc); // Also need to drop Arc otherwise BarState won't be dropped
739
740 // Wait for `interval` but return early if we are notified to stop
741 let result = self
742 .stopping
743 .1
744 .wait_timeout_while(self.stopping.0.lock().unwrap(), interval, |stopped| {
745 !*stopped
746 })
747 .unwrap();
748
749 // If the wait didn't time out, it means we were notified to stop
750 if !result.1.timed_out() {
751 break;
752 }
753 }
754
755 #[cfg(test)]
756 TICKER_RUNNING.store(false, Ordering::SeqCst);
757 }
758}
759
760// Tests using the global TICKER_RUNNING flag need to be serialized
761#[cfg(test)]
762pub(crate) static TICKER_TEST: Lazy<Mutex<()>> = Lazy::new(Mutex::default);
763
764#[cfg(test)]
765mod tests {
766 use super::*;
767
768 #[allow(clippy::float_cmp)]
769 #[test]
770 fn test_pbar_zero() {
771 let pb = ProgressBar::new(0);
772 assert_eq!(pb.state().state.fraction(), 1.0);
773 }
774
775 #[allow(clippy::float_cmp)]
776 #[test]
777 fn test_pbar_maxu64() {
778 let pb = ProgressBar::new(!0);
779 assert_eq!(pb.state().state.fraction(), 0.0);
780 }
781
782 #[test]
783 fn test_pbar_overflow() {
784 let pb = ProgressBar::new(1);
785 pb.set_draw_target(ProgressDrawTarget::hidden());
786 pb.inc(2);
787 pb.finish();
788 }
789
790 #[test]
791 fn test_get_position() {
792 let pb = ProgressBar::new(1);
793 pb.set_draw_target(ProgressDrawTarget::hidden());
794 pb.inc(2);
795 let pos = pb.position();
796 assert_eq!(pos, 2);
797 }
798
799 #[test]
800 fn test_weak_pb() {
801 let pb = ProgressBar::new(0);
802 let weak = pb.downgrade();
803 assert!(weak.upgrade().is_some());
804 ::std::mem::drop(pb);
805 assert!(weak.upgrade().is_none());
806 }
807
808 #[test]
809 fn it_can_wrap_a_reader() {
810 let bytes = &b"I am an implementation of io::Read"[..];
811 let pb = ProgressBar::new(bytes.len() as u64);
812 let mut reader = pb.wrap_read(bytes);
813 let mut writer = Vec::new();
814 io::copy(&mut reader, &mut writer).unwrap();
815 assert_eq!(writer, bytes);
816 }
817
818 #[test]
819 fn it_can_wrap_a_writer() {
820 let bytes = b"implementation of io::Read";
821 let mut reader = &bytes[..];
822 let pb = ProgressBar::new(bytes.len() as u64);
823 let writer = Vec::new();
824 let mut writer = pb.wrap_write(writer);
825 io::copy(&mut reader, &mut writer).unwrap();
826 assert_eq!(writer.it, bytes);
827 }
828
829 #[test]
830 fn ticker_thread_terminates_on_drop() {
831 let _guard = TICKER_TEST.lock().unwrap();
832 assert!(!TICKER_RUNNING.load(Ordering::SeqCst));
833
834 let pb = ProgressBar::new_spinner();
835 pb.enable_steady_tick(Duration::from_millis(50));
836
837 // Give the thread time to start up
838 thread::sleep(Duration::from_millis(250));
839
840 assert!(TICKER_RUNNING.load(Ordering::SeqCst));
841
842 drop(pb);
843 assert!(!TICKER_RUNNING.load(Ordering::SeqCst));
844 }
845
846 #[test]
847 fn ticker_thread_terminates_on_drop_2() {
848 let _guard = TICKER_TEST.lock().unwrap();
849 assert!(!TICKER_RUNNING.load(Ordering::SeqCst));
850
851 let pb = ProgressBar::new_spinner();
852 pb.enable_steady_tick(Duration::from_millis(50));
853 let pb2 = pb.clone();
854
855 // Give the thread time to start up
856 thread::sleep(Duration::from_millis(250));
857
858 assert!(TICKER_RUNNING.load(Ordering::SeqCst));
859
860 drop(pb);
861 assert!(TICKER_RUNNING.load(Ordering::SeqCst));
862
863 drop(pb2);
864 assert!(!TICKER_RUNNING.load(Ordering::SeqCst));
865 }
866}