diff --git a/packages/server/src/utils/directory-suggestions.test.ts b/packages/server/src/utils/directory-suggestions.test.ts index 9d9015aed0..68a2693562 100644 --- a/packages/server/src/utils/directory-suggestions.test.ts +++ b/packages/server/src/utils/directory-suggestions.test.ts @@ -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); + }); }); diff --git a/packages/server/src/utils/directory-suggestions.ts b/packages/server/src/utils/directory-suggestions.ts index 39baf3a44c..049b1011ac 100644 --- a/packages/server/src/utils/directory-suggestions.ts +++ b/packages/server/src/utils/directory-suggestions.ts @@ -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>(); + +function sharedFilesystemCall(key: string, call: () => Promise): Promise { + const pending = pendingFilesystemCalls.get(key); + if (pending) return pending as Promise; + const started = filesystemCalls(call).finally(() => pendingFilesystemCalls.delete(key)); + pendingFilesystemCalls.set(key, started); + return started; +} function readdir(directory: string): Promise { - return filesystemCalls(() => fs.readdir(directory, { withFileTypes: true })); + return sharedFilesystemCall(`readdir:${directory}`, () => + fs.readdir(directory, { withFileTypes: true }), + ); } function realpath(target: string): Promise { - return filesystemCalls(() => fs.realpath(target)); + return sharedFilesystemCall(`realpath:${target}`, () => fs.realpath(target)); } function stat(target: string): Promise { - return filesystemCalls(() => fs.stat(target)); + return sharedFilesystemCall(`stat:${target}`, () => fs.stat(target)); } export const WORKSPACE_SEARCH_HIDDEN_DIRECTORIES = [