Skip to content

Refactor file explorer operations to use async background tasks#1228

Open
sinelaw wants to merge 4 commits intomasterfrom
claude/async-remote-file-explorer-9yuLH
Open

Refactor file explorer operations to use async background tasks#1228
sinelaw wants to merge 4 commits intomasterfrom
claude/async-remote-file-explorer-9yuLH

Conversation

@sinelaw
Copy link
Copy Markdown
Owner

@sinelaw sinelaw commented Mar 8, 2026

Summary

This PR refactors file explorer operations (toggle, refresh, create, delete, rename) to use non-blocking async background tasks instead of blocking the main thread with runtime.block_on(). Operations now spawn tokio tasks that send results back via the async message bridge, allowing the UI to remain responsive during file system operations.

Key Changes

  • Async file explorer operations: Converted toggle_node(), refresh_node(), and file tree polling to spawn background tasks instead of blocking on runtime.block_on()

    • Toggle and refresh now take ownership of the file explorer, spawn async work, and restore it via AsyncMessage::FileExplorerToggleComplete and FileExplorerAsyncRefreshComplete
    • Added spawn_file_explorer_refresh() helper method to centralize async refresh spawning
  • Non-blocking file tree polling: Refactored poll_file_tree_changes() to spawn a background task that checks directory mtimes asynchronously

    • Collects expanded directories and their stored mtimes on the main thread
    • Spawns a tokio task to check current mtimes with a 5-second timeout
    • Sends results back via AsyncMessage::FileTreePollResult for batch refresh
    • Added file_tree_poll_in_progress flag to prevent concurrent polls
  • Simplified file creation/deletion: Streamlined file_explorer_new_file(), file_explorer_new_directory(), and delete operations

    • File system operations remain synchronous (protected by channel timeout for remote)
    • Parent directory refresh is now spawned asynchronously via spawn_file_explorer_refresh()
    • Reduced nesting and improved code clarity
  • Request timeout protection: Added 30-second timeout to remote channel requests (REQUEST_TIMEOUT)

    • Prevents indefinite blocking when remote becomes unreachable
    • Applied to request(), request_with_data(), and related methods
  • New async message handlers: Added handlers for the new async completion messages

    • handle_file_explorer_toggle_complete(): Restores view and handles post-toggle logic (gitignore loading, decoration rebuild)
    • handle_file_explorer_async_refresh_complete(): Restores view and updates status
    • handle_file_tree_poll_result(): Processes mtime changes and spawns refreshes for changed directories

Implementation Details

  • File explorer is temporarily taken (Option::take()) during async operations and restored when the background task completes
  • Symlink directory expansion still triggers decoration cache rebuild (moved to toggle completion handler)
  • File tree polling now uses fs_manager.get_single_metadata() for async metadata checks
  • All async operations include proper error handling and logging
  • Maintains selection state and navigation behavior after async operations complete

https://claude.ai/code/session_01Jy9LQt7vBeSgvSRkJ3nTDb

claude and others added 4 commits March 18, 2026 12:04
Three layered changes to prevent the editor from freezing when the
remote filesystem becomes unresponsive:

1. Channel-level request timeout (30s): Wrap response waits in
   AgentChannel::request() and request_with_data() with
   tokio::time::timeout(30s), returning ChannelError::Timeout on
   expiry. This protects all remote operations as a safety net.

2. Async poll_file_tree_changes: Replace the synchronous polling loop
   with a background task that checks directory mtimes via
   FsManager::get_single_metadata() (with a 5s batch timeout), then
   sends changed dirs back via AsyncMessage::FileTreePollResult. The
   main thread spawns async refreshes for changed dirs.

3. Async file explorer operations: Replace all 6 block_on calls in
   file_explorer.rs with spawned tokio tasks. toggle_node takes the
   FileTreeView, runs the toggle async, and sends it back via
   FileExplorerToggleComplete. Post-mutation refreshes (after create,
   delete, rename) use spawn_file_explorer_refresh() helper. The
   mutations themselves remain synchronous since they're one-shot
   user operations protected by the channel timeout.

The code path is identical for local and remote filesystems — only
the underlying FileSystem trait implementation differs.

https://claude.ai/code/session_01Jy9LQt7vBeSgvSRkJ3nTDb
The async refactor moved refresh_node to background tasks but lost the
post-refresh logic: re-selecting next node after delete and navigating
to the renamed path after rename. Add FileExplorerRefreshContext to
carry operation context through the async message, and restore the
post-refresh actions in the handler.

Also fix 4 e2e tests to use semantic waiting (wait_until) instead of
sleep+render, since the file explorer is now temporarily None during
async refresh.

https://claude.ai/code/session_01Jy9LQt7vBeSgvSRkJ3nTDb
poll_file_changes was calling filesystem.metadata() synchronously on the
main thread for every open buffer. For remote filesystems this does a
blocking RPC (request_blocking → Handle::block_on), which freezes the
entire editor when the remote agent is slow or unresponsive.

Rewrite to match the async pattern already used by poll_file_tree_changes:
snapshot paths + stored mtimes on the main thread, spawn a tokio task
with a 5s timeout to check mtimes via FsManager::get_single_metadata,
and deliver results back via AsyncMessage::FileChangePollResult.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…r bug

Add a BlockControl mechanism to SlowFileSystem that can dynamically make
all filesystem operations hang (block on a condvar) until unblocked. This
enables testing editor behavior when the remote connection stops responding.

Add e2e test that reproduces the bug: Editor::save() calls write_file()
synchronously on the main thread, so a hung filesystem freezes the entire
editor until the 30s request timeout fires.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@sinelaw sinelaw force-pushed the claude/async-remote-file-explorer-9yuLH branch from 65ec840 to ea02be7 Compare March 18, 2026 10:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants