Follow-up to #10081, which added ProgressIndicatorSpinner but no caller for it yet.
Since the spinner repaints in place and is FPS rate limited, it can be called from inside a hot loop, so it fits the spots where borg currently goes quiet for a long time and looks hung. Candidate call sites, checked against master (91129b2):
-
Chunk index fragment merge in build_chunkindex_from_repo() (src/borg/cache.py): the for hash in hashes: loop loads and merges one fragment per iteration and logs only at debug level, so at info level nothing is output while it runs.
-
Slow chunk index rebuild, same function: for info in repository.store_list("packs") issues one range request per pack through PackReader.iter_headers(), and the only output is a single debug line after the whole loop.
-
Lock acquisition in Lock.acquire() (src/borg/storelocking.py): the retry sleep loop also only logs at debug level, so with a long BORG_LOCK_WAIT borg sits silent while waiting for another client to release the lock.
-
The store.list() materializations, Repository.store_list() and the local store_list() in Repository.check(). Both do list(self.store.list(ns)), one blocking call with a repo-sized latency. Using the spinner here means consuming the generator in a loop instead of materializing it first, so it is a slightly bigger change than the others.
Not in scope:
Cache.close(): single-call phases, nothing would animate there.
borg create: process_file_chunks() already calls stats.show_progress() per chunk, so the display keeps moving inside a large file.
One thing to settle before I start: _setup_implied_logging() in src/borg/archiver/__init__.py puts borg.output.progress at WARN unless --progress was given, so the spinner stays silent without it. Should these life signs stay behind --progress, or should the spinner animate by default? That decides whether 3 is worth doing at all, since a user waiting on a lock has usually not passed --progress.
I would do one PR per area rather than one big one. Happy to reorder the list or drop items from it.
Follow-up to #10081, which added
ProgressIndicatorSpinnerbut no caller for it yet.Since the spinner repaints in place and is FPS rate limited, it can be called from inside a hot loop, so it fits the spots where borg currently goes quiet for a long time and looks hung. Candidate call sites, checked against master (91129b2):
Chunk index fragment merge in
build_chunkindex_from_repo()(src/borg/cache.py): thefor hash in hashes:loop loads and merges one fragment per iteration and logs only at debug level, so at info level nothing is output while it runs.Slow chunk index rebuild, same function:
for info in repository.store_list("packs")issues one range request per pack throughPackReader.iter_headers(), and the only output is a single debug line after the whole loop.Lock acquisition in
Lock.acquire()(src/borg/storelocking.py): the retry sleep loop also only logs at debug level, so with a longBORG_LOCK_WAITborg sits silent while waiting for another client to release the lock.The
store.list()materializations,Repository.store_list()and the localstore_list()inRepository.check(). Both dolist(self.store.list(ns)), one blocking call with a repo-sized latency. Using the spinner here means consuming the generator in a loop instead of materializing it first, so it is a slightly bigger change than the others.Not in scope:
Cache.close(): single-call phases, nothing would animate there.borg create:process_file_chunks()already callsstats.show_progress()per chunk, so the display keeps moving inside a large file.One thing to settle before I start:
_setup_implied_logging()insrc/borg/archiver/__init__.pyputsborg.output.progressat WARN unless--progresswas given, so the spinner stays silent without it. Should these life signs stay behind--progress, or should the spinner animate by default? That decides whether 3 is worth doing at all, since a user waiting on a lock has usually not passed--progress.I would do one PR per area rather than one big one. Happy to reorder the list or drop items from it.