···11export { mo } from './mo.ts';
22export { Task } from './task.ts';
33+export { workerPool } from './worker-pool.ts';
44+export type { Runner } from './runner.ts';
+6
src/runner.ts
···11+import type { Task } from './task.ts';
22+33+export type Runner = {
44+ <T>(task: Task<T>): Promise<T>;
55+ [Symbol.dispose](): void;
66+};
+36
src/worker-pool.ts
···11+import { Worker } from 'node:worker_threads';
22+import { setupWorker, execute } from './execute.ts';
33+import type { Task } from './task.ts';
44+import type { Runner } from './runner.ts';
55+66+const workerEntryUrl = new URL('./worker-entry.ts', import.meta.url);
77+88+export function workerPool(size: number): Runner {
99+ const workers: Worker[] = [];
1010+ for (let i = 0; i < size; i++) {
1111+ const worker = new Worker(workerEntryUrl);
1212+ worker.unref();
1313+ setupWorker(worker);
1414+ workers.push(worker);
1515+ }
1616+1717+ let next = 0;
1818+ let disposed = false;
1919+2020+ const run = <T>(task: Task<T>): Promise<T> => {
2121+ if (disposed) return Promise.reject(new Error('Worker pool is disposed'));
2222+ const worker = workers[next % workers.length];
2323+ next++;
2424+ return execute<T>(worker, task.id, task.args);
2525+ };
2626+2727+ run[Symbol.dispose] = () => {
2828+ disposed = true;
2929+ for (const worker of workers) {
3030+ worker.terminate();
3131+ }
3232+ workers.length = 0;
3333+ };
3434+3535+ return run as Runner;
3636+}
+49
test/pool.test.ts
···11+import { describe, it } from 'node:test';
22+import assert from 'node:assert/strict';
33+import { workerPool } from 'moroutine';
44+import { double, add } from './fixtures/math.ts';
55+66+describe('workerPool', () => {
77+ it('executes a moroutine through the pool', async () => {
88+ const run = workerPool(2);
99+ try {
1010+ const result = await run(double(2));
1111+ assert.equal(result, 4);
1212+ } finally {
1313+ run[Symbol.dispose]();
1414+ }
1515+ });
1616+1717+ it('handles concurrent calls across pool workers', async () => {
1818+ const run = workerPool(2);
1919+ try {
2020+ const results = await Promise.all([
2121+ run(double(1)),
2222+ run(double(2)),
2323+ run(double(3)),
2424+ run(double(4)),
2525+ ]);
2626+ assert.deepEqual(results, [2, 4, 6, 8]);
2727+ } finally {
2828+ run[Symbol.dispose]();
2929+ }
3030+ });
3131+3232+ it('handles multiple argument moroutines', async () => {
3333+ const run = workerPool(1);
3434+ try {
3535+ const result = await run(add(10, 20));
3636+ assert.equal(result, 30);
3737+ } finally {
3838+ run[Symbol.dispose]();
3939+ }
4040+ });
4141+4242+ it('dispose terminates pool workers', async () => {
4343+ const run = workerPool(2);
4444+ await run(double(1));
4545+ run[Symbol.dispose]();
4646+ // After dispose, calls should fail
4747+ await assert.rejects(() => run(double(1)));
4848+ });
4949+});