diff --git a/examples/worker-affinity/key-affinity.ts b/examples/worker-affinity/key-affinity.ts --- a/examples/worker-affinity/key-affinity.ts +++ b/examples/worker-affinity/key-affinity.ts @@ -2,18 +2,29 @@ import type { Balancer, Task, WorkerHandle } from '../../src/index.ts'; import { increment, read } from './counter.ts'; +// The state type produced by roundRobin()'s initialState(), derived rather +// than hardcoded so this example doesn't need to know its shared-memory shape. +type RoundRobinState = ReturnType['initialState']>>; + // Routes tasks to workers based on a key in the task's args, so that // per-worker state (caches, connections, compiled resources) can be reused // across successive calls for the same key. Falls back to round-robin for // any task that doesn't carry a routable key. -export function keyAffinity(): Balancer { +// +// The fallback's state is composed into keyAffinity's own initialState() +// rather than captured in a closure: closure state only stays consistent +// under single-threaded scheduling (a workers() pool), while state produced +// by initialState() and shared via shared-memory types stays consistent +// even when scheduling moves to multiple threads. +export function keyAffinity(): Balancer<{ fallbackState: RoundRobinState }> { const fallback = roundRobin(); return { - select(workers: readonly WorkerHandle[], task: Task): WorkerHandle { + initialState: () => ({ fallbackState: fallback.initialState!() }), + select(workers: readonly WorkerHandle[], task: Task, { fallbackState }): WorkerHandle { let key: string | undefined; if (isTask(increment, task)) key = task.args[0]; else if (isTask(read, task)) key = task.args[0]; - if (key === undefined) return fallback.select(workers, task); + if (key === undefined) return fallback.select(workers, task, fallbackState); return workers[hash(key) % workers.length]; }, }; diff --git a/examples/worker-affinity/main.ts b/examples/worker-affinity/main.ts --- a/examples/worker-affinity/main.ts +++ b/examples/worker-affinity/main.ts @@ -25,7 +25,7 @@ ] as const; const expected = { a: 4, b: 5, c: 5 }; -async function demo(label: string, balance: Balancer) { +async function demo(label: string, balance: Balancer) { using run = workers(4, { balance }); await run(operations.map(([k, n]) => increment(k, n))); const [a, b, c] = await run([read('a'), read('b'), read('c')]);