40 lines
1.5 KiB
TypeScript
40 lines
1.5 KiB
TypeScript
/**
|
||
* Idle-preferring worker selection helper.
|
||
*
|
||
* Workers run as in-process instances that each poll the DB for jobs. Without
|
||
* coordination, whichever worker's poll timer fires first claims the next job —
|
||
* so a busy worker can grab work while an idle sibling sits at 0/n. This helper
|
||
* implements the "most-free-wins" rule: before claiming, a worker checks its
|
||
* siblings and yields to one that has STRICTLY more free slots (scoped to
|
||
* siblings that are available and actually serve the job's role). A 0/n idle
|
||
* worker therefore always beats a partially-loaded one.
|
||
*
|
||
* Returns the index of the idlest qualifying competitor, or -1 when the caller
|
||
* is already (tied for) the most free and should claim the job itself.
|
||
*/
|
||
export interface ClaimCandidate {
|
||
/** max_concurrency − inflight for this candidate. */
|
||
freeSlots: number;
|
||
/** Running, healthy, enabled — would actually claim if poked. */
|
||
availableForClaim: boolean;
|
||
/** Serves the role of the job about to be claimed. */
|
||
servesRole: boolean;
|
||
}
|
||
|
||
export function pickIdlerIndex(
|
||
selfFreeSlots: number,
|
||
candidates: readonly ClaimCandidate[],
|
||
): number {
|
||
let bestIdx = -1;
|
||
let bestFree = selfFreeSlots; // a competitor must STRICTLY exceed this to win
|
||
for (let i = 0; i < candidates.length; i++) {
|
||
const c = candidates[i];
|
||
if (!c.availableForClaim || !c.servesRole) continue;
|
||
if (c.freeSlots > bestFree) {
|
||
bestIdx = i;
|
||
bestFree = c.freeSlots;
|
||
}
|
||
}
|
||
return bestIdx;
|
||
}
|