-
Notifications
You must be signed in to change notification settings - Fork 35
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Progress bar failures when handling async, concurrent updates #204
Comments
Progress bars are thread safe: they have no mutable state other than a single atomic ref. Calling That exception is thrown by mordant to be conservative and avoid excessive spinning. I didn't expect anyone to try to update a progress bar from 10000 tasks concurrently, but that limit is safe to remove. |
Thanks for the quick response! Will Perhaps I'm asking a lot of Mordant and 10k tasks is too many, but I'm noticing slower performance with fewer tasks too. Is performance something you've looked at? |
I ran the IntelliJ Profiler on my example above and added your spinlock fix, and I noticed that while interestingly Line 172 in 1660323
Flamegraph: |
Thanks for the profile info, that's good to know. The |
I did some playing around and these changes help a lot with performance, though they might be difficult to decipher and maintain.
// Remove samples older than the speed estimate duration
val oldestSampleTime = timeSource.markNow() - speedEstimateDuration
val entry = HistoryEntry(timeSource.markNow(), scope.completed)
// convert to a mutable list to improve performance
val samples = samples.toMutableList()
val indexOfOldestSample = samples.binarySearchBy(oldestSampleTime) { it.elapsed }
samples.subList(indexOfOldestSample.coerceAtLeast(0), samples.size).clear()
samples.add(entry) There's probably a better way, but I thought I'd share this anyway. Something else that caught my eye: I see that |
Keeping track of progress history was taking a lot of CPU load if updates were happening very frequently. @aSemy suggested an improvement in #204 (comment), and this PR goes further: Instead of keeping a list with an entry for each call to `update`, we instead keep a fixed size list, and overwrite the last entry if it was very recent. We only look at the first and last entries to calculate speed, so there's no need to keep every entry. ``` Benchmark Mode Cnt Score Error Units JmhBenchmark.benchmarkCurrent avgt 3 34931.106 ± 1299.242 ns/op JmhBenchmark.benchmarkAsemy avgt 3 5474.108 ± 634.242 ns/op JmhBenchmark.benchmarkFixedList avgt 3 176.047 ± 53.293 ns/op ``` This change is a 350x speedup on the benchmarks I ran, which seems fast enough.
I would like to use a progress bar to track the status of many asynchronous tasks.
However, when I try to use it, I unfortunately encounter a ConcurrentModificationException.
From looking at the current code, I see some code that I think would cause issues. For example, updating
completed
doesn't have any of the recommended protections for mutable state.Could progress bar be updated to be thread-safe?
ConcurrentModificationException Example
The text was updated successfully, but these errors were encountered: