From dcc100450ab455415fa5038e7293f81f7584fd4f Mon Sep 17 00:00:00 2001 From: Devin Ivy Date: Tue, 14 Apr 2026 11:52:39 -0400 Subject: [PATCH] feat: roundRobin() and leastBusy() balancer factories Co-Authored-By: Claude Opus 4.6 (1M context) --- src/balancers.ts | 35 +++++++++++++++++++++++++++++++++++ src/index.ts | 3 ++- test/balancers.test.ts | 36 ++++++++++++++++++++++++++++++++++++ 3 files changed, 73 insertions(+), 1 deletion(-) create mode 100644 src/balancers.ts create mode 100644 test/balancers.test.ts diff --git a/src/balancers.ts b/src/balancers.ts new file mode 100644 index 0000000..6c02493 --- /dev/null +++ b/src/balancers.ts @@ -0,0 +1,35 @@ +import type { Balancer, WorkerHandle } from './runner.ts'; + +/** + * Creates a round-robin balancer that cycles through workers in order. + * @returns A fresh Balancer instance. + */ +export function roundRobin(): Balancer { + let next = 0; + return { + select(workers: readonly WorkerHandle[]): WorkerHandle { + const worker = workers[next % workers.length]; + next++; + return worker; + }, + }; +} + +/** + * Creates a least-busy balancer that picks the worker with the lowest activeCount. + * Ties are broken by index (first wins). + * @returns A fresh Balancer instance. + */ +export function leastBusy(): Balancer { + return { + select(workers: readonly WorkerHandle[]): WorkerHandle { + let best = workers[0]; + for (let i = 1; i < workers.length; i++) { + if (workers[i].activeCount < best.activeCount) { + best = workers[i]; + } + } + return best; + }, + }; +} diff --git a/src/index.ts b/src/index.ts index 7dffba7..6945515 100644 --- a/src/index.ts +++ b/src/index.ts @@ -7,7 +7,8 @@ export type { ChannelOptions } from './channel.ts'; export { workers } from './worker-pool.ts'; export { transfer } from './transfer.ts'; export { assign } from './assign.ts'; -export type { Runner, WorkerHandle, WorkerOptions } from './runner.ts'; +export { roundRobin, leastBusy } from './balancers.ts'; +export type { Balancer, Runner, WorkerHandle, WorkerOptions } from './runner.ts'; export { shared, int8, diff --git a/test/balancers.test.ts b/test/balancers.test.ts new file mode 100644 index 0000000..13a3ec4 --- /dev/null +++ b/test/balancers.test.ts @@ -0,0 +1,36 @@ +import { describe, it } from 'node:test'; +import assert from 'node:assert/strict'; +import { roundRobin, leastBusy } from 'moroutine'; +import type { WorkerHandle } from 'moroutine'; + +function mockHandle(activeCount: number): WorkerHandle { + return { activeCount, thread: {} as any, exec: {} as any }; +} + +describe('roundRobin()', () => { + it('cycles through workers in order', () => { + const b = roundRobin(); + const handles = [mockHandle(0), mockHandle(0), mockHandle(0)]; + const task = { id: 'test', args: [], uid: 0 } as any; + assert.equal(b.select(handles, task), handles[0]); + assert.equal(b.select(handles, task), handles[1]); + assert.equal(b.select(handles, task), handles[2]); + assert.equal(b.select(handles, task), handles[0]); + }); +}); + +describe('leastBusy()', () => { + it('picks worker with lowest activeCount', () => { + const b = leastBusy(); + const handles = [mockHandle(3), mockHandle(1), mockHandle(2)]; + const task = { id: 'test', args: [], uid: 0 } as any; + assert.equal(b.select(handles, task), handles[1]); + }); + + it('breaks ties by index', () => { + const b = leastBusy(); + const handles = [mockHandle(1), mockHandle(1), mockHandle(1)]; + const task = { id: 'test', args: [], uid: 0 } as any; + assert.equal(b.select(handles, task), handles[0]); + }); +}); -- 2.51.2