diff options
| author | Christophe Besson <cbesson@gmail.com> | 2026-08-25 18:18:54 +0200 |
|---|---|---|
| committer | Christophe Besson <cbesson@gmail.com> | 2026-08-25 18:18:54 +0200 |
| commit | 2af320ba4da49547176ef7e4c081956c33841958 (patch) | |
| tree | 6b79cb92c785378b855da3862a3bf371abe701bc /packages/meshbay-node/src/meshbay_node/indexer | |
| parent | bbc76ad4da3ca61f820b9605ec9228c7a9357352 (diff) | |
| parent | 3c44f55f6b0aba77c7ad57d0a5ebe3e55b409473 (diff) | |
| download | meshbay-2af320ba4da49547176ef7e4c081956c33841958.tar.gz | |
Merge branch 'fix/wizard-apps-and-progress'
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013XSohfUQQiaE77qyFLgSv3
Diffstat (limited to 'packages/meshbay-node/src/meshbay_node/indexer')
| -rw-r--r-- | packages/meshbay-node/src/meshbay_node/indexer/indexer.py | 65 |
1 files changed, 49 insertions, 16 deletions
diff --git a/packages/meshbay-node/src/meshbay_node/indexer/indexer.py b/packages/meshbay-node/src/meshbay_node/indexer/indexer.py index 10981c0..f78465b 100644 --- a/packages/meshbay-node/src/meshbay_node/indexer/indexer.py +++ b/packages/meshbay-node/src/meshbay_node/indexer/indexer.py @@ -139,6 +139,27 @@ def _walk_root(root: Root) -> list[Path]: return [p for p in root.path.rglob("*") if p.is_file()] +def _size_files(files: list[Path]) -> list[tuple[Path, int]]: + """ + Blocking: stat() every file from an already-completed walk — run via an + executor for the same reason `_walk_root` is (its own docstring above). + Previously a plain loop straight on the asyncio event loop thread: for a + root with many thousands of files (a real personal library, not a + hypothetical — docs/musicbay.md's own "several thousand files" example) + that blocked the entire daemon, every WebRTC session and the admin UI + included, for as long as the stat() calls took — and did so *before* + `_scan_root` had even set `progress.scanning`, so a consumer polling it + saw "not scanning" the whole time real, blocking work was happening. + """ + sized: list[tuple[Path, int]] = [] + for p in files: + try: + sized.append((p, p.stat().st_size)) + except OSError: + continue + return sized + + def _scan_file(root: Root, file_path: Path) -> IndexEntry | None: """Compute IndexEntry for a file. Blocking — run in executor. Uses streaming blake3 so arbitrarily large files (ISOs, VM images, etc.) @@ -304,26 +325,38 @@ class DirectoryIndexer: log.info("Scanning %s (root %r) ...", root.path, root.name) count = 0 loop = asyncio.get_event_loop() - try: - files = await loop.run_in_executor(self._executor, _walk_root, root) - except OSError as e: - log.warning("Cannot scan root %r: %s", root.name, e) - return 0 - - # Sizes up front, off the same listing that already walked the tree — - # the progress bar's denominator, not a second pass over the disk. - sized: list[tuple[Path, int]] = [] - for p in files: - try: - sized.append((p, p.stat().st_size)) - except OSError: - continue + # `scanning` flips on here, before the walk — not after it and the + # sizing pass, which for a large or slow (network/USB) root can + # themselves take a long time despite running off-loop. A consumer + # polling IndexProgress (index-status; the Create Group wizard's + # own progress bar) must see "scanning" the moment real work starts, + # not only once the file list and its total size are known. Found + # live (2026-08-25): the wizard's post-creation "add extra roots" + # step gave up waiting after a short grace period because the flag + # had not yet turned on, even though the node was already several + # seconds into walking and sizing a large root — total_bytes is + # unknown at this point, so it starts at 0 and is corrected below. self.progress.scanning = True self.progress.scanned_bytes = 0 - self.progress.total_bytes = sum(size for _, size in sized) - self.progress.current_dir = "" + self.progress.total_bytes = 0 + self.progress.current_dir = root.name try: + try: + files = await loop.run_in_executor(self._executor, _walk_root, root) + except OSError as e: + log.warning("Cannot scan root %r: %s", root.name, e) + return 0 + + # Sizes up front, off the same listing that already walked the + # tree — the progress bar's denominator, not a second pass over + # the disk. Off-loop for the same reason the walk itself is + # (_size_files's own docstring: this used to be a synchronous + # loop right here, blocking the whole daemon for a large root). + sized = await loop.run_in_executor(self._executor, _size_files, files) + + self.progress.total_bytes = sum(size for _, size in sized) + self.progress.current_dir = "" for file_path, size in sized: self.progress.current_dir = file_path.parent.name entry = await self._hash_or_cached(root, file_path) |