Skip to content

Commit b1fd78d

Browse files
authored
refactor(fmt): replace WorkTank with Tinypool (#147)
1 parent f3df538 commit b1fd78d

9 files changed

Lines changed: 56 additions & 189 deletions

File tree

packages/rstack/THIRD_PARTY_NOTICES.md

Lines changed: 0 additions & 114 deletions
Original file line numberDiff line numberDiff line change
@@ -273,63 +273,6 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
273273
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
274274
SOFTWARE.
275275

276-
## isoconcurrency
277-
278-
This package includes bundled code from
279-
[isoconcurrency](https://github.com/fabiospampinato/isoconcurrency).
280-
281-
License: MIT
282-
283-
The MIT License (MIT)
284-
285-
Copyright (c) 2025-present Fabio Spampinato
286-
287-
Permission is hereby granted, free of charge, to any person obtaining a
288-
copy of this software and associated documentation files (the "Software"),
289-
to deal in the Software without restriction, including without limitation
290-
the rights to use, copy, modify, merge, publish, distribute, sublicense,
291-
and/or sell copies of the Software, and to permit persons to whom the
292-
Software is furnished to do so, subject to the following conditions:
293-
294-
The above copyright notice and this permission notice shall be included in
295-
all copies or substantial portions of the Software.
296-
297-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
298-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
299-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
300-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
301-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
302-
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
303-
DEALINGS IN THE SOFTWARE.
304-
305-
## isotimer
306-
307-
This package includes bundled code from [isotimer](https://github.com/fabiospampinato/isotimer).
308-
309-
License: MIT
310-
311-
The MIT License (MIT)
312-
313-
Copyright (c) 2025-present Fabio Spampinato
314-
315-
Permission is hereby granted, free of charge, to any person obtaining a
316-
copy of this software and associated documentation files (the "Software"),
317-
to deal in the Software without restriction, including without limitation
318-
the rights to use, copy, modify, merge, publish, distribute, sublicense,
319-
and/or sell copies of the Software, and to permit persons to whom the
320-
Software is furnished to do so, subject to the following conditions:
321-
322-
The above copyright notice and this permission notice shall be included in
323-
all copies or substantial portions of the Software.
324-
325-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
326-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
327-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
328-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
329-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
330-
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
331-
DEALINGS IN THE SOFTWARE.
332-
333276
## micromatch
334277

335278
This package includes bundled code from [micromatch](https://github.com/micromatch/micromatch).
@@ -577,60 +520,3 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
577520
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
578521
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
579522
DEALINGS IN THE SOFTWARE.
580-
581-
## webworker-shim
582-
583-
This package includes bundled code from
584-
[webworker-shim](https://github.com/fabiospampinato/webworker-shim).
585-
586-
License: MIT
587-
588-
The MIT License (MIT)
589-
590-
Copyright (c) 2022-present Fabio Spampinato
591-
592-
Permission is hereby granted, free of charge, to any person obtaining a
593-
copy of this software and associated documentation files (the "Software"),
594-
to deal in the Software without restriction, including without limitation
595-
the rights to use, copy, modify, merge, publish, distribute, sublicense,
596-
and/or sell copies of the Software, and to permit persons to whom the
597-
Software is furnished to do so, subject to the following conditions:
598-
599-
The above copyright notice and this permission notice shall be included in
600-
all copies or substantial portions of the Software.
601-
602-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
603-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
604-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
605-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
606-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
607-
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
608-
DEALINGS IN THE SOFTWARE.
609-
610-
## worktank
611-
612-
This package includes bundled code from [worktank](https://github.com/fabiospampinato/worktank).
613-
614-
License: MIT
615-
616-
The MIT License (MIT)
617-
618-
Copyright (c) 2021-present Fabio Spampinato
619-
620-
Permission is hereby granted, free of charge, to any person obtaining a
621-
copy of this software and associated documentation files (the "Software"),
622-
to deal in the Software without restriction, including without limitation
623-
the rights to use, copy, modify, merge, publish, distribute, sublicense,
624-
and/or sell copies of the Software, and to permit persons to whom the
625-
Software is furnished to do so, subject to the following conditions:
626-
627-
The above copyright notice and this permission notice shall be included in
628-
all copies or substantial portions of the Software.
629-
630-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
631-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
632-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
633-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
634-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
635-
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
636-
DEALINGS IN THE SOFTWARE.

packages/rstack/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@
5858
"@rslint/core": "catalog:",
5959
"@rstest/core": "catalog:",
6060
"prettier": "catalog:",
61+
"tinypool": "catalog:",
6162
"yuku-parser": "catalog:"
6263
},
6364
"devDependencies": {
@@ -78,8 +79,7 @@
7879
"rslog": "catalog:",
7980
"sort-package-json": "catalog:",
8081
"tiny-readdir": "catalog:",
81-
"typescript": "catalog:",
82-
"worktank": "catalog:"
82+
"typescript": "catalog:"
8383
},
8484
"peerDependencies": {
8585
"@rspress/core": "^2.0.17"

packages/rstack/src/fmt/runner.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ const runFmtFilesInWorkerPool = async (
5050
);
5151
return results.filter((result): result is FmtFileResult => result !== undefined);
5252
} finally {
53-
workerPool.terminate();
53+
await workerPool.terminate();
5454
}
5555
};
5656

packages/rstack/src/fmt/worker.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,11 @@ import type { FmtFileRequest } from './types.ts';
1313
type PrettierPlugins = NonNullable<PrettierOptions['plugins']>;
1414
type FormatFileResult = 'changed' | 'unchanged' | 'unsupported';
1515

16+
interface FormatFileTask {
17+
file: FmtFileRequest;
18+
shouldWrite: boolean;
19+
}
20+
1621
const fileInfoOptions = {
1722
ignorePath: [],
1823
resolveConfig: false,
@@ -37,10 +42,10 @@ const resolveFmtParser = async (
3742
* Use synchronous direct I/O inside the dedicated worker to avoid libuv
3843
* scheduling overhead. This prioritizes throughput over crash-safe replacement.
3944
*/
40-
const formatFile = async (
41-
{ path, options }: FmtFileRequest,
42-
shouldWrite: boolean,
43-
): Promise<FormatFileResult> => {
45+
const formatFile = async ({
46+
file: { path, options },
47+
shouldWrite,
48+
}: FormatFileTask): Promise<FormatFileResult> => {
4449
const plugins = await getPrettierPlugins(options, path);
4550
const parser = await resolveFmtParser(path, options, plugins);
4651
if (!parser) {

packages/rstack/src/fmt/workerPool.ts

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
11
// Derived from @prettier/cli, see THIRD_PARTY_NOTICES.md
22

33
import { availableParallelism } from 'node:os';
4-
import WorkTank from 'worktank';
4+
import Tinypool from 'tinypool';
5+
import type { FmtFileRequest } from './types.ts';
56

67
type FmtWorkerMethods = typeof import('./worker.ts');
78

89
interface FmtWorkerPool {
9-
formatFile: FmtWorkerMethods['formatFile'];
10-
terminate: () => void;
10+
formatFile: (
11+
file: FmtFileRequest,
12+
shouldWrite: boolean,
13+
) => ReturnType<FmtWorkerMethods['formatFile']>;
14+
terminate: () => Promise<void>;
1115
}
1216

1317
const getFmtWorkerCount = (fileCount: number, maxWorkers?: number): number =>
@@ -27,30 +31,27 @@ const createFmtWorkerPool = async (
2731
maxWorkers?: number,
2832
): Promise<FmtWorkerPool> => {
2933
const workerCount = getFmtWorkerCount(fileCount, maxWorkers);
30-
const pool = new WorkTank<FmtWorkerMethods>({
31-
pool: {
32-
name: 'rstack-fmt',
33-
size: workerCount,
34-
},
35-
worker: {
36-
autoInstantiate: true,
37-
methods: getFmtWorkerUrl(),
38-
},
34+
const pool = new Tinypool({
35+
filename: getFmtWorkerUrl().href,
36+
name: 'initializeFmtWorker',
37+
minThreads: workerCount,
38+
maxThreads: workerCount,
3939
});
4040

4141
try {
42-
// Concurrent handshakes make WorkTank assign one task to every worker.
4342
await Promise.all(
44-
Array.from({ length: workerCount }, () => pool.exec('initializeFmtWorker', [])),
43+
Array.from({ length: workerCount }, () =>
44+
pool.run(undefined, { name: 'initializeFmtWorker' }),
45+
),
4546
);
4647
} catch (error) {
47-
pool.terminate();
48+
await pool.destroy();
4849
throw error;
4950
}
5051

5152
return {
52-
formatFile: (file, shouldWrite) => pool.exec('formatFile', [file, shouldWrite]),
53-
terminate: pool.terminate,
53+
formatFile: (file, shouldWrite) => pool.run({ file, shouldWrite }, { name: 'formatFile' }),
54+
terminate: () => pool.destroy(),
5455
};
5556
};
5657

packages/rstack/tests/fmt/runnerWriteFailure.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ rs.mock('../../src/fmt/workerPool.ts', () => ({
1111
formatFile: () => Promise.reject(new Error('file write failed')),
1212
terminate: () => {
1313
mocks.terminateCalls++;
14+
return Promise.resolve();
1415
},
1516
}),
1617
}));

packages/rstack/tests/fmt/worker.test.ts

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,15 @@ test('writes formatted files', async () => {
99
const filePath = writeProjectFile(rootPath, 'example.ts', 'const value=1');
1010

1111
await expect(
12-
formatFile(
13-
{
12+
formatFile({
13+
file: {
1414
path: filePath,
1515
options: {
1616
parser: 'typescript',
1717
},
1818
},
19-
true,
20-
),
19+
shouldWrite: true,
20+
}),
2121
).resolves.toBe('changed');
2222

2323
expect(readFileSync(filePath, 'utf8')).toBe('const value = 1;\n');
@@ -30,13 +30,13 @@ test('infers the parser for an explicitly provided node_modules file', async ()
3030
const filePath = writeProjectFile(rootPath, 'node_modules/example/index.ts', source);
3131

3232
await expect(
33-
formatFile(
34-
{
33+
formatFile({
34+
file: {
3535
path: filePath,
3636
options: {},
3737
},
38-
false,
39-
),
38+
shouldWrite: false,
39+
}),
4040
).resolves.toBe('changed');
4141

4242
expect(readFileSync(filePath, 'utf8')).toBe(source);
@@ -48,13 +48,13 @@ test('skips unsupported files before reading them', async () => {
4848
const filePath = path.join(rootPath, 'missing.unknown');
4949

5050
await expect(
51-
formatFile(
52-
{
51+
formatFile({
52+
file: {
5353
path: filePath,
5454
options: {},
5555
},
56-
true,
57-
),
56+
shouldWrite: true,
57+
}),
5858
).resolves.toBe('unsupported');
5959
});
6060
});

0 commit comments

Comments
 (0)