feat(gateway): add configurable lane queue mode, cap, and overflow

This commit is contained in:
William Valentin
2026-02-16 11:48:45 -08:00
parent 527602fd8a
commit f7284a4ef1
9 changed files with 178 additions and 5 deletions
+39 -4
View File
@@ -21,8 +21,26 @@ interface Lane {
queue: QueueEntry[];
}
export type LaneQueueMode = 'collect' | 'steer' | 'interrupt';
export type LaneQueueOverflow = 'drop_old' | 'drop_new';
export interface LaneQueueConfig {
mode: LaneQueueMode;
cap: number;
overflow: LaneQueueOverflow;
}
export class LaneQueue {
private lanes: Map<string, Lane> = new Map();
private config: LaneQueueConfig;
constructor(config?: Partial<LaneQueueConfig>) {
this.config = {
mode: config?.mode ?? 'collect',
cap: Math.max(1, config?.cap ?? 50),
overflow: config?.overflow ?? 'drop_old',
};
}
/**
* Enqueue a unit of work for the given lane.
@@ -47,6 +65,19 @@ export class LaneQueue {
}
}
if (this.config.mode === 'steer' || this.config.mode === 'interrupt') {
this.rejectPending(lane, 'Superseded by newer request');
}
if (lane.queue.length >= this.config.cap) {
if (this.config.overflow === 'drop_new') {
return Promise.reject(new Error('Lane queue full (drop_new)'));
}
// drop_old
const dropped = lane.queue.shift();
dropped?.reject(new Error('Lane queue overflow (drop_old)'));
}
// Otherwise, queue the work and return a deferred promise
return new Promise<T>((resolve, reject) => {
lane.queue.push({
@@ -85,10 +116,7 @@ export class LaneQueue {
const lane = this.lanes.get(laneId);
if (!lane) {return;}
const pending = lane.queue.splice(0);
for (const entry of pending) {
entry.reject(new Error('Lane cancelled'));
}
this.rejectPending(lane, 'Lane cancelled');
// Clean up empty idle lanes
if (!lane.active && lane.queue.length === 0) {
@@ -96,6 +124,13 @@ export class LaneQueue {
}
}
private rejectPending(lane: Lane, reason: string): void {
const pending = lane.queue.splice(0);
for (const entry of pending) {
entry.reject(new Error(reason));
}
}
/**
* Process the next queued entry for a lane (called after current work finishes).
* Runs asynchronously so the caller's finally block completes first.