Skip to content

UI freezes at startup and on folder add — debounced watcher builds a file-ID cache over the whole tree (macOS/Windows) #121

Description

@sashamerzliakov

Transparency up front: I hit this bug as a user; the root-cause tracing through the vendored crate sources was done with AI assistance (Claude), with the mechanism claims cited below so they can be verified directly. I've verified the behaviour on my own machine (macOS, Apple Silicon) and I'm happy to answer questions — same tooling in the loop.

What happens

With a large folder open in the sidebar (thousands of files), the whole window becomes unresponsive (macOS beachball) for a noticeable stretch at startup, and again every time another folder is added. It recovers on its own — no crash. Reproduced on macOS (Apple Silicon, internal SSD, ~10k-file root); I haven't measured exact timings, but the freeze is long enough to beachball and scales with tree size.

This is adjacent to but distinct from #118 (the drive-root startup hang) and its merged fix #119: 1.6.2 now rejects filesystem roots outright via isFilesystemRoot, but the same freeze still happens for any large-but-legal folder, so root filtering doesn't reach it.

Why (traced through the vendored crates, tauri-plugin-fs 2.5.1 / notify-debouncer-full 0.6)

useFolderWatcher calls the fs plugin's watch() with delayMs, which selects the debounced path. That debouncer maintains a file-ID cache: on macOS and Windows (RecommendedCache = FileIdMap — notify-debouncer-full src/cache.rs), debouncer.watch() walks the entire tree with walkdir (following symlinks, unbounded depth) and calls get_file_id — a stat per entry on macOS, a file-handle open per entry on Windows. On Linux the cache is NoCache, so this specific cost doesn't exist there.

The walk runs inside plugin:fs|watch, a non-async Tauri command (src/watcher.rs), and non-async commands execute on the main thread — which is why the window freezes rather than just being slow.

Two compounding factors in the hook itself: the watcher effect tears down and re-creates watchers for every open root whenever the folder list changes (so adding one folder re-walks all of them), and the walk cost repeats on every startup.

Proposed fix

Switch to watchImmediate (skips the debouncer and its cache; both variants deliver the same serialized notify::Event shape, so isDirectoryChangeEvent is unaffected), do the coalescing JS-side with a fixed 350 ms window, and manage watchers incrementally so adding a folder only creates that folder's watcher. Tradeoff to be aware of: raw events now cross IPC uncoalesced — the JS window bounds refresh work, not message volume.

I have this working in my fork with unit tests over the new watcher lifecycle — happy to open a PR. (Longer-term, the root cause arguably belongs upstream in tauri-plugin-fs: the synchronous command doing unbounded I/O, and no way to opt out of the file-ID cache.)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions