You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Briefly, the way MultiProgress works is as follows. When you add a ProgressBar to a MultiProgress, the pb's draw target is replaced by a special draw target that redirects any draws to MultiState. On the one hand this is nice because it keeps ProgressBar relatively simple - it needn't be aware of whether or not it is part of a MultiProgress. In my experience however this design leads to some deadlocks (particularly when you involve Ticker).
In each loop of Ticker it first gets the BarState of the target ProgressBar, then it calls draw.
When Ticker is installed on a ProgressBar that is part of a MultiProgress, the calls above to draw_target.width() and draw_target.drawable() will end up locking MultiState (because draw_target.kind is TargetKind::Multi).
This gives a lock order of BarState, MultiState.
The problem is that it's easy to accidentally create a lock order of MultiState, BarState. When operating on a MultiProgress, it's more natural to lock in that order anyway - I already fixed an issue stemming from this lock inversion here: #424.
Another consequence of the current design is that the lifecycle of ProgressBars as they relate to MultiProgress is a little hard to reason about. Currently, I am working on fixing #426. The issue there is that when a ProgressBar drops it is not automatically removed from the MultiProgress which can cause some visual artifacts (as well as wasting memory). The first attempt I made was to impl drop on TargetKind such that when it is TargetKind::Multi we automatically call MultiState::remove_idx. This doesn't work however, because you don't want to remove the pb if, for example, if it hasn't actually been drawn yet (depending on ProgressFinish).
Right now I'm thinking of having MultiState maintain a Weak<BarState> for each pb. Then each time MultiState::draw is called, we look for pb's that have been dropped and prune them appropriately. I anticipate locking issues with this approach however :(.
I'd like to pitch a somewhat major API change: require that every ProgressBar be part of a MultiProgress, in which case we could rename MultiProgress to something more appropriate e.g. ProgressBarContainer or similar. For example, the single.rs example could be modified like such:
use std::thread;use std::time::Duration;use indicatif::ProgressBar;fnmain(){let container = ProgressBarContainer::new();let pb = container.new_progress_bar(1024);// Or possibly builder pattern, e.g. container.add(ProgressBar::build(1024).with_message("msg"))for _ in0..1024{
pb.inc(1);
thread::sleep(Duration::from_millis(5));}
pb.finish_with_message("done");}
Anticipated benefits of doing it this way:
A single code path for case of single vs. multiple progress bars
Clearer lifecycle management. In the scheme I am envisioning, essentially each ProgressBar will hold an Arc<ContainerState>. ContainerState will hold a Weak to each ProgressBar that it holds.
Simpler code - the burden of drawing will be moved entirely onto ProgressBarContainer.
The progress bar library I built (before investing in indicatif) worked this way and I found it simpler to reason about. I will work on getting that library open-sourced so you can see it.
The text was updated successfully, but these errors were encountered:
If I were to summarize this issue, I'd say that IMHO coordinating multiple progress bars is too complicated to be shoe-horned in is as it is currently done.
We can do a whole bunch of refactoring, but here are some principles that I think we should uphold:
Should not impose API (ergonomics) overhead for users who only need a single progress bar
Should not impose performance overhead for users who only need a single progress bar
The former implies that the top-level API should probably not involve something like an explicit ProgressBarContainer; the latter implies that we shouldn't change the single bar case to require a bunch of hash maps like we currently have in MultiProgressState.
Briefly, the way
MultiProgress
works is as follows. When you add aProgressBar
to aMultiProgress
, the pb's draw target is replaced by a special draw target that redirects any draws toMultiState
. On the one hand this is nice because it keepsProgressBar
relatively simple - it needn't be aware of whether or not it is part of aMultiProgress
. In my experience however this design leads to some deadlocks (particularly when you involveTicker
).In each loop of
Ticker
it first gets theBarState
of the targetProgressBar
, then it callsdraw
.indicatif/src/progress_bar.rs
Lines 588 to 598 in 496a605
BarState::draw
:indicatif/src/state.rs
Lines 145 to 151 in 496a605
When
Ticker
is installed on aProgressBar
that is part of aMultiProgress
, the calls above todraw_target.width()
anddraw_target.drawable()
will end up lockingMultiState
(becausedraw_target.kind
isTargetKind::Multi
).This gives a lock order of
BarState
,MultiState
.The problem is that it's easy to accidentally create a lock order of
MultiState
,BarState
. When operating on aMultiProgress
, it's more natural to lock in that order anyway - I already fixed an issue stemming from this lock inversion here: #424.Another consequence of the current design is that the lifecycle of
ProgressBar
s as they relate toMultiProgress
is a little hard to reason about. Currently, I am working on fixing #426. The issue there is that when aProgressBar
drops it is not automatically removed from theMultiProgress
which can cause some visual artifacts (as well as wasting memory). The first attempt I made was to impldrop
onTargetKind
such that when it isTargetKind::Multi
we automatically callMultiState::remove_idx
. This doesn't work however, because you don't want to remove the pb if, for example, if it hasn't actually been drawn yet (depending onProgressFinish
).Right now I'm thinking of having
MultiState
maintain aWeak<BarState>
for each pb. Then each timeMultiState::draw
is called, we look for pb's that have been dropped and prune them appropriately. I anticipate locking issues with this approach however :(.I'd like to pitch a somewhat major API change: require that every
ProgressBar
be part of aMultiProgress
, in which case we could renameMultiProgress
to something more appropriate e.g.ProgressBarContainer
or similar. For example, the single.rs example could be modified like such:Anticipated benefits of doing it this way:
ProgressBar
will hold anArc<ContainerState>
.ContainerState
will hold aWeak
to eachProgressBar
that it holds.ProgressBarContainer
.The progress bar library I built (before investing in indicatif) worked this way and I found it simpler to reason about. I will work on getting that library open-sourced so you can see it.
The text was updated successfully, but these errors were encountered: