Skip to content

Commit 20056d5

Browse files
authored
perf(fmt): bypass worker pool for single files (#393)
1 parent 9ed694f commit 20056d5

3 files changed

Lines changed: 54 additions & 28 deletions

File tree

‎packages/rstack/src/fmt/runner.ts‎

Lines changed: 44 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ interface RunCache {
3939
hashOptions: ReturnType<typeof createOptionsHasher>;
4040
}
4141

42-
interface FmtWorkerPoolResult {
42+
interface FmtFilesResult {
4343
files: FmtFileResult[];
4444
processedFileCount: number;
4545
}
@@ -193,13 +193,38 @@ const runPriorityTasks = async (
193193
return results;
194194
};
195195

196-
/** Processes files in a worker pool while preserving input order. */
197-
const runWithWorkers = async (
196+
/** Collects per-file outcomes while preserving cache and processed-count semantics. */
197+
const collectFmtResults = (
198+
results: FmtFileRun[],
199+
cache?: RunCache,
200+
): FmtFilesResult => {
201+
const processedFiles: FmtFileResult[] = [];
202+
let processedFileCount = 0;
203+
204+
for (const { outcome, key, entry } of results) {
205+
if (key !== undefined && entry) {
206+
cache?.store.set(key, entry);
207+
}
208+
if (outcome === 'unsupported') {
209+
continue;
210+
}
211+
212+
processedFileCount++;
213+
if (outcome !== 'unchanged') {
214+
processedFiles.push(outcome);
215+
}
216+
}
217+
218+
return { files: processedFiles, processedFileCount };
219+
};
220+
221+
/** Processes one pending file locally and multiple pending files in a worker pool. */
222+
const runFmtTasks = async (
198223
files: FmtFileRequest[],
199224
shouldWrite: boolean,
200225
maxWorkers?: number,
201226
cache?: RunCache,
202-
): Promise<FmtWorkerPoolResult> => {
227+
): Promise<FmtFilesResult> => {
203228
const tasks = files.map((file) => createRunTask(file, cache));
204229
const pendingFileCount = tasks.reduce(
205230
(count, task) => count + (isCachedUnsupported(task) ? 0 : 1),
@@ -209,6 +234,19 @@ const runWithWorkers = async (
209234
return { files: [], processedFileCount: 0 };
210235
}
211236

237+
// One pending file cannot benefit from parallelism, so avoid worker startup and IPC overhead.
238+
if (pendingFileCount === 1) {
239+
const { formatFile } = await import('./worker.ts');
240+
const formatFileOnMainThread: FormatFile = (file, write, fileCache) =>
241+
formatFile({ file, shouldWrite: write, cache: fileCache });
242+
const results = await Promise.all(
243+
tasks.map((task) =>
244+
runFmtFile(task, shouldWrite, formatFileOnMainThread),
245+
),
246+
);
247+
return collectFmtResults(results, cache);
248+
}
249+
212250
const { createWorkerPool } = await import('./workerPool.ts');
213251
const workerPool = await createWorkerPool(pendingFileCount, maxWorkers);
214252

@@ -221,24 +259,7 @@ const runWithWorkers = async (
221259
runFmtFile(task, shouldWrite, workerPool.formatFile),
222260
),
223261
);
224-
const processedFiles: FmtFileResult[] = [];
225-
let processedFileCount = 0;
226-
227-
for (const { outcome, key, entry } of results) {
228-
if (key !== undefined && entry) {
229-
cache?.store.set(key, entry);
230-
}
231-
if (outcome === 'unsupported') {
232-
continue;
233-
}
234-
235-
processedFileCount++;
236-
if (outcome !== 'unchanged') {
237-
processedFiles.push(outcome);
238-
}
239-
}
240-
241-
return { files: processedFiles, processedFileCount };
262+
return collectFmtResults(results, cache);
242263
} finally {
243264
await workerPool.terminate();
244265
}
@@ -284,7 +305,7 @@ const runFmtFiles = async ({
284305
const result =
285306
files.length === 0
286307
? { files: [], processedFileCount: 0 }
287-
: await runWithWorkers(files, shouldWrite, maxWorkers, runCache);
308+
: await runFmtTasks(files, shouldWrite, maxWorkers, runCache);
288309
await runCache?.store.save().catch(() => false);
289310

290311
return {

‎packages/rstack/src/fmt/worker.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@ const hashContent = (content: string | Uint8Array): string =>
1616
hash('sha256', content, 'base64url').slice(0, 16);
1717

1818
/**
19-
* Use synchronous direct I/O inside the dedicated worker to avoid libuv
20-
* scheduling overhead. This prioritizes throughput over crash-safe replacement.
19+
* Synchronous file I/O avoids libuv scheduling overhead in workers and single-file
20+
* main-thread runs. This favors throughput over crash-safe file replacement.
2121
*/
2222
const formatFile = async ({
2323
file,

‎packages/rstack/tests/fmt/runnerWorkerPreflight.test.ts‎

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -68,20 +68,25 @@ test('does not start the worker pool when every parser result is cached as unsup
6868
});
6969
});
7070

71-
test('starts the worker pool for a path-only unsupported entry without an extension', async () => {
71+
test('rechecks a path-only unsupported entry on the main thread', async () => {
7272
await withTempProject(async (rootPath) => {
7373
const { cache, file } = await createCachedUnsupportedFile(
7474
rootPath,
7575
'script',
7676
);
77+
writeProjectFile(rootPath, 'script', '#!/usr/bin/env node\nconst value=1');
7778

7879
await expect(
7980
runFmtFiles({
8081
files: [file],
8182
mode: 'check',
8283
cache,
8384
}),
84-
).rejects.toThrow('worker startup failed');
85-
expect(mocks.workerPoolCalls).toEqual([[1, undefined]]);
85+
).resolves.toEqual({
86+
exitCode: 1,
87+
files: [{ path: file.path, status: 'different' }],
88+
processedFileCount: 1,
89+
});
90+
expect(mocks.workerPoolCalls).toEqual([]);
8691
});
8792
});

0 commit comments

Comments
 (0)