4.7 KiB
WorkerManager
(no submodules)
Purpose
WorkerManager is the public entry point for distributing tasks across a WorkerPool. It owns task assignment (load-balancing, retry, dead-letter), supervision (periodic health checks, auto-restart of unhealthy workers), and option lifecycle (configurable at construction and at runtime via updateOptions). Implementation details — the retry-with-backoff loop and the supervisor — live in manager-dispatch.ts and manager-supervisor.ts so this file stays focused on the surface contract.
Features
assignTask<I, O>(task, input, signal?): Promise<O>— pick a worker, execute, retry up tomaxTaskRetries, record dead-letter on exhaustion.assignTasks<I, O>(tasks[], inputs[], signal?)— fan-out wrapper overassignTask.- Auto health-check loop driven by
setIntervaloverhealthCheckIntervalMs; toggleable at runtime. - Auto-restart of
UNHEALTHYworkers up tomaxWorkerRestarts. failedTasksSet and per-task retry counter for observability.- Injectable
LoadBalancingStrategy(defaultRoundRobinStrategy),WorkerHealthChecker(defaultPeriodicHealthChecker),Clock,LoggerLike. - AbortSignal threaded through every public async method.
- Throws
InvalidOptionsErrorfor negative numeric options at construction and onupdateOptions.
Usage: Basic Example
import { WorkerManager } from "@suaveplan/queue-distribution/manager/manager";
import { WorkerPool } from "@suaveplan/queue-distribution/pool/pool";
import { LocalWorker } from "@suaveplan/queue-distribution/worker/local/local";
const pool = new WorkerPool();
pool.addWorker(new LocalWorker({}, { id: "a" }));
const manager = new WorkerManager(pool, {
autoHealthCheck: false,
maxTaskRetries: 2,
});
const result = await manager.assignTask(task, input);
Usage: Advanced Example
Stand up a production-shaped manager with a least-busy strategy, performance bias, a custom health checker, and a deterministic clock for tests.
import { WorkerManager } from "@suaveplan/queue-distribution/manager/manager";
import { WorkerPool } from "@suaveplan/queue-distribution/pool/pool";
import { LeastBusyStrategy } from "@suaveplan/queue-distribution/load-balancing/least-busy/least-busy";
import { PeriodicHealthChecker } from "@suaveplan/queue-distribution/health-checker/health-checker";
const clock = (): number => Date.now();
const pool = new WorkerPool();
// ... add workers ...
const manager = new WorkerManager(
pool,
{
balancer: new LeastBusyStrategy({ usePerformanceMetrics: true }),
healthChecker: new PeriodicHealthChecker({ unhealthyThreshold: 3 }),
autoHealthCheck: true,
healthCheckIntervalMs: 10_000,
maxTaskRetries: 5,
taskRetryDelayMs: 250,
autoRestartUnhealthy: true,
maxWorkerRestarts: 5,
minOnlineWorkers: 2,
},
{ clock, logger: console as never },
);
const ctrl = new AbortController();
setTimeout(() => ctrl.abort(), 30_000); // hard ceiling
await manager.assignTasks(tasks, inputs, ctrl.signal);
API Reference
interface WorkerManagerOptions—healthCheckIntervalMs?,autoHealthCheck?,taskRetryDelayMs?,maxTaskRetries?,autoRestartUnhealthy?,maxWorkerRestarts?,minOnlineWorkers?.interface WorkerManagerDeps—clock?,logger?.class WorkerManager.constructor(pool, options?: { balancer?, healthChecker?, ...WorkerManagerOptions }, deps?: WorkerManagerDeps).assignTask<I, O>(task, input, signal?): Promise<O>.assignTasks<I, O>(tasks[], inputs[], signal?): Promise<O[]>.startHealthChecks(): void,stopHealthChecks(): void.checkAllWorkersHealth(signal?): Promise<Map<string, WorkerHealth>>.handleWorkerFailure(worker, error): Promise<void>.getWorkerPool(): WorkerPool,getLoadBalancer(): LoadBalancingStrategy,setLoadBalancer(b): void,getHealthChecker(): WorkerHealthChecker.getFailedTasks(): ReadonlySet<DistributedTask>,clearFailedTasks(): void.getTaskRetries(taskId): number.getOptions(): Required<WorkerManagerOptions>,updateOptions(partial): void.
Implementation Notes
updateOptions performs validate-then-apply: a negative maxTaskRetries is rejected before any mutation, so the manager remains in a consistent state across failed updates. Toggling autoHealthCheck mid-run actually starts or stops the interval — passing the same value as the current state is a no-op, which lets callers idempotently bounce settings without disrupting an active loop. The dispatch and supervisor configs are recreated on each call to assignTask / checkAllWorkersHealth so changes via setLoadBalancer are picked up immediately without rebuilding the manager.