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