perf(files): move blocking files.rs commands off the GTK main thread#89
Merged
Zeus-Deus merged 2 commits intoJun 10, 2026
Conversation
Eight files.rs commands were sync #[tauri::command] fns that either spawn subprocesses (git check-ignore, rg/grep, fd/find, which+xdg-open) or do blocking file I/O. In Tauri v2 a sync command runs on the GTK main thread, so a slow disk or wedged subprocess froze the whole UI — the subprocess-spawning ones can hang indefinitely. - Convert list_directory, search_in_files, search_file_names, reveal_in_file_manager, read_file, write_file, save_clipboard_image_bytes, and paste_clipboard_image_to_file to async fn wrapping the blocking work in tokio::task::spawn_blocking. - paste_clipboard_image_to_file also moves the clipboard read (an OS round-trip) and the CPU-heavy PNG encode onto the blocking pool. - Add functional tests driving the real subprocess paths (git check-ignore gitignore flagging, rg/grep matches + offsets, fd/find relative paths) plus read/write round-trip and rejection paths. - Add a FIFO-based regression test on a current_thread runtime proving a wedged blocking write no longer stalls the executor thread (a reader thread un-wedges a regressed build so the test fails fast instead of hanging); validated by mutating write_file back to inline blocking and watching it fail. - Add tests/files_commands_ipc.rs: drives the converted commands through the real generate_handler!/IPC dispatch via tauri::test, locking the camelCase wire args (showHidden, maxResults) and the error channel. Same pattern as commands/git.rs (see its header note). Frontend invoke() already returns a Promise, so no caller changes are needed. Closes #79
The Windows CI leg failed list_directory_marks_gitignored_entries:
git_ignored_set fed ABSOLUTE paths to `git check-ignore --stdin`, and
git C-quotes any echoed output line containing "unusual" bytes — the
backslashes of every Windows absolute path, or non-ASCII bytes via
core.quotepath on all platforms. The quoted line ("C:\\Users\\...")
yields a basename with a stray trailing quote that never matches the
entry name, so ignored entries silently lost their flag. Confirmed via
a temporary diagnostic run on the Windows runner: check-ignore matched
the absolute path fine (exit 0) but returned it C-quoted.
Fix: feed cwd-relative entry names and switch to `-z` (NUL-separated
input/output, quoting disabled) so the echoed bytes match the fed
names exactly on every platform, regardless of how the caller spelled
the directory path.
Tests:
- list_directory_marks_gitignored_entries_with_non_ascii_names
reproduces the Windows failure class on Linux (fails on the old
parser, passes now).
- list_directory_marks_gitignored_entries_via_symlinked_path locks in
flagging through symlinked paths (macOS /tmp → /private/tmp shape).
9546d60 to
722431c
Compare
This was referenced Jun 10, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Eight
files.rscommands were sync#[tauri::command]fns that either spawn subprocesses (git check-ignore,rg/grep,fd/find,which+xdg-open) or do blocking file I/O. In Tauri v2 a sync command runs on the GTK main thread, so a slow disk or wedged subprocess froze the whole UI — the subprocess-spawning ones can hang indefinitely.This converts all of them to
async fn+tokio::task::spawn_blocking, the same pattern already applied incommands/git.rs(see its header note) andcommands/workspace.rs(#85). Frontendinvoke()already returns a Promise, so no caller changes are needed.Converted commands:
list_directory,search_in_files,search_file_names,reveal_in_file_managerread_file,write_file,save_clipboard_image_bytes,paste_clipboard_image_to_file— the latter also moves the OS clipboard round-trip and the CPU-heavy PNG encode (~8 MB RGBA for a 1080p screenshot) onto the blocking poolNo
Stateguards exist in these commands, so nothing non-Sendis held across an.await.Testing
git check-ignore(gitignore flagging), realrg/grep(match offsets, line numbers), realfd/find(relative paths), plus read/write round-trip and binary/oversize/missing rejections. Gated for Windows CI where needed (nofd/POSIXfindthere).write_fileagainst a FIFO with no reader on acurrent_threadruntime — a timer sharing the executor thread must fire while the write is wedged inopen(2). A rescuer thread un-wedges a regressed (inline-blocking) build so the test fails fast instead of hanging. Validated by mutation: revertingwrite_fileto inline blocking makes the test fail with the exact diagnostic.tests/files_commands_ipc.rs): drives the converted commands through the actualgenerate_handler!pipeline viatauri::test::get_ipc_response, locking the camelCase wire args (showHidden,maxResults), async dispatch, result serialization, and the error channel.npm run tauri:dev(real GTK main thread, real IPC) — clean startup, control socket responsive, no panics.cargo check, all 24 integration test targets,npm run check, and 1826/1826 vitest tests pass. The one local lib-test failure (mcp::project_codemux_entry_is_filtered_out) reproduces identically on the pristine baseline — it's host-config pollution (a user-scopecodemuxMCP server entry), unrelated to this change and green on CI's clean HOME.Closes #79