docs/internal/async-remote-file-explorer.md
The file explorer blocks the main editor thread on remote filesystem I/O. When the remote host becomes unreachable (e.g. DHCP lease change, network drop), the editor freezes permanently because these blocking calls have no timeout.
poll_file_tree_changes() and file_explorer_toggle_expand() (plus 5 other
call sites) use runtime.block_on(...) to perform remote filesystem operations
synchronously on the main thread. The underlying AgentChannel::request() has
no timeout — if the remote never responds, the main thread blocks forever.
file_operations.rs — poll_file_tree_changes()
self.filesystem.metadata() synchronously for every expanded dirruntime.block_on(tree.refresh_node()) for changed dirsfile_explorer.rs — 6 block_on call sites:
| Line | Operation | Context |
|---|---|---|
| 275 | toggle_node | User expands/collapses a dir |
| 406 | refresh_node | User manually refreshes |
| 442 | refresh_node | After creating a file |
| 498 | refresh_node | After creating a directory |
| 586 | refresh_node | After deleting (trash) |
| 707 | refresh_node | After renaming |
Three changes, layered for defense in depth.
Add tokio::time::timeout(Duration::from_secs(30), ...) around the response
wait in AgentChannel::request(). Returns ChannelError::Timeout (variant
already exists) on expiry. request_blocking() inherits this automatically.
This protects all remote operations as a safety net — file open, save, metadata, list_dir, etc. — not just file explorer calls.
poll_file_tree_changes (two-phase)Replace the synchronous polling loop with a background task:
Phase 1 — main thread (non-blocking):
file_tree_poll_in_progress = true, return immediatelyPhase 2 — tokio task:
metadata_async() for each dir (with the 30s channel timeout)AsyncMessage::FileTreePollResult(Vec<(NodeId, PathBuf, SystemTime)>)Phase 3 — main thread (on receiving AsyncMessage):
dir_mod_timesfile_tree_poll_in_progressReplace all 6 block_on calls in file_explorer.rs with spawned tasks.
Prerequisites:
Clone on FileTree (all fields are owned data + Arc<FsManager>)Pattern for toggle_node (expand/collapse):
NodeState::Loading immediately (already exists)FileTreeView and spawn a tokio tasktoggle_node / refresh_node on the cloned treeAsyncMessage with the updated treePattern for post-mutation refreshes (create/delete/rename):
refresh_node call after the mutation becomes asyncNo visible change. Expand/collapse may feel slightly more responsive since the main thread isn't blocked during directory listing.
Editor never freezes. Input, rendering, and all non-remote features continue working normally.
| Feature | Behavior |
|---|---|
| File tree auto-poll | Stops updating silently. Tree shows last known state. Debug log on timeout. |
| Expand/collapse dir | Shows "Loading..." state. Times out after 30s, shows error in status bar, node reverts. |
| Create/delete/rename | Mutation times out after 30s, error in status bar. |
| Open/save file | Times out after 30s, error in status bar. |
A stale file tree is an acceptable degraded state. The user discovers the remote is down when they try an active operation (open, save, expand) and see the timeout error.
poll_file_tree_changes async — fixes the deadlock that prompted this workfile_explorer_toggle_expand + the 5 refresh sites — completes the picture