fix(server): keep a stuck directory search from blocking every other search (#6041)

Directory search shares two filesystem slots across all searches. Searches
over one tree walk it in the same order, so two Add Project searches that
reach the same folder whose read never returns held both slots, and every
later directory search queued behind them: command center file search,
@-file autocomplete, and file-link lookups in unrelated folders all hung
until the read returned.

Searches waiting on the same path now share its pending filesystem call, so
one stuck folder holds one slot and the other stays free.

Refs #4879

Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
This commit is contained in:
paseo-bot[bot]
2026-10-04 13:11:40 +02:00
committed by GitHub
parent 409b8f9312
commit 5285b7e502
2 changed files with 25 additions and 4 deletions
@@ -1078,4 +1078,12 @@ describe("filesystem threads held by concurrent searches", () => {
expect(peak).toBeLessThanOrEqual(2);
});
// Searches over one tree walk it in the same order, so searches stuck on one unreadable folder
// must hold one thread between them and leave the other for unrelated searches.
it("shares one filesystem request among searches waiting on the same path", async () => {
const peak = await peakPendingFilesystemRequests(searchWhileTyping);
expect(peak).toBe(1);
});
});
@@ -106,19 +106,32 @@ const NO_MATCH_TIER = 5;
// Each pending fs call holds one of libuv's shared threadpool threads (4 by default), and a read on
// a hung mount or a protected folder never returns. Searches pile up while the user types, so they
// share two threads and leave the rest of the pool to the daemon.
// share two threads and leave the rest of the pool to the daemon. Searches over one tree walk it
// in the same order and stop at the same stuck folder, so they share its pending call and hold one
// thread between them instead of both.
const filesystemCalls = pLimit(2);
const pendingFilesystemCalls = new Map<string, Promise<unknown>>();
function sharedFilesystemCall<T>(key: string, call: () => Promise<T>): Promise<T> {
const pending = pendingFilesystemCalls.get(key);
if (pending) return pending as Promise<T>;
const started = filesystemCalls(call).finally(() => pendingFilesystemCalls.delete(key));
pendingFilesystemCalls.set(key, started);
return started;
}
function readdir(directory: string): Promise<Dirent[]> {
return filesystemCalls(() => fs.readdir(directory, { withFileTypes: true }));
return sharedFilesystemCall(`readdir:${directory}`, () =>
fs.readdir(directory, { withFileTypes: true }),
);
}
function realpath(target: string): Promise<string> {
return filesystemCalls(() => fs.realpath(target));
return sharedFilesystemCall(`realpath:${target}`, () => fs.realpath(target));
}
function stat(target: string): Promise<Stats> {
return filesystemCalls(() => fs.stat(target));
return sharedFilesystemCall(`stat:${target}`, () => fs.stat(target));
}
export const WORKSPACE_SEARCH_HIDDEN_DIRECTORIES = [