first commit
This commit is contained in:
@@ -0,0 +1,76 @@
|
||||
# `scheduler`
|
||||
|
||||
## Purpose
|
||||
|
||||
`scheduler.ts` exposes `pickSlot`, the pure function `WorkerPool` calls to
|
||||
decide which slot receives the next submit. The function takes a
|
||||
projection of the pool's slots (only the data the scheduler needs:
|
||||
`name` + `pendingCount`) and the strategy name, and returns the chosen
|
||||
slot plus the next round-robin index.
|
||||
|
||||
Isolating the scheduling decision in a pure function makes it independently
|
||||
testable, swappable (a future scheduler can be added without touching the
|
||||
pool), and free of side effects.
|
||||
|
||||
## Features
|
||||
|
||||
- Two scheduling strategies: `round-robin` and `least-busy`.
|
||||
- `SchedulerSlot` projection — lightweight view of the pool's slots so the
|
||||
scheduler doesn't depend on `WorkerPool`'s internals.
|
||||
- Returns `undefined` when the slot list is empty, letting the caller
|
||||
decide what to do (typically reject with `RESTART_EXHAUSTED`).
|
||||
- Round-robin returns the next index so the caller can persist it.
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import { pickSlot } from "@suaveplan/web-workers";
|
||||
|
||||
const slots = [
|
||||
{ name: "w0", pendingCount: 0 },
|
||||
{ name: "w1", pendingCount: 0 },
|
||||
];
|
||||
const pick = pickSlot("round-robin", slots, 0);
|
||||
// pick.slot.name === "w0", pick.nextIndex === 1
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import {
|
||||
pickSlot,
|
||||
type SchedulerSlot,
|
||||
type SchedulerStrategy,
|
||||
} from "@suaveplan/web-workers";
|
||||
|
||||
class CustomPool {
|
||||
private slots: Array<{ name: string; pendingCount: number }> = [];
|
||||
private nextRR = 0;
|
||||
pick(strategy: SchedulerStrategy): SchedulerSlot | undefined {
|
||||
const projection = this.slots.map((s) => ({
|
||||
name: s.name,
|
||||
pendingCount: s.pendingCount,
|
||||
}));
|
||||
const result = pickSlot(strategy, projection, this.nextRR);
|
||||
if (result === undefined) return undefined;
|
||||
this.nextRR = result.nextIndex;
|
||||
return result.slot;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `type SchedulerStrategy = "round-robin" | "least-busy"`.
|
||||
- `interface SchedulerSlot { name: string; pendingCount: number }`.
|
||||
- `pickSlot(strategy, slots, nextRoundRobinIndex): { slot, nextIndex } | undefined`.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
`round-robin` uses `nextRoundRobinIndex % slots.length` so the caller can
|
||||
keep a monotonically-increasing counter without worrying about wraparound.
|
||||
`least-busy` linear-scans the slot list; ties go to the lowest-index slot
|
||||
(deterministic). The pure-function shape means the same slot projection
|
||||
always returns the same result — no hidden state, no randomness. For
|
||||
large pools (> 100 slots) a heap-based least-busy could be faster, but
|
||||
the typical pool size (4–16 slots) makes the linear scan negligible.
|
||||
Reference in New Issue
Block a user