fix: settle Pi child capacity reservations
This commit is contained in:
@@ -58,7 +58,7 @@ export interface PiSubagentDispatchOptions {
|
||||
export interface PiSubagentSchedulerOptions {
|
||||
openChild(input: PiSubagentChildOpenInput): Promise<PiSubagentChild>;
|
||||
processBudget: PiProcessBudget;
|
||||
reclaimProcessCapacity?(): Promise<boolean>;
|
||||
reclaimProcessCapacity?(signal?: AbortSignal): Promise<boolean>;
|
||||
createId?: (kind: 'dispatch' | 'task') => string;
|
||||
}
|
||||
|
||||
@@ -216,7 +216,7 @@ export class PiSubagentScheduler {
|
||||
private readonly openChild: PiSubagentSchedulerOptions['openChild'];
|
||||
private readonly processBudget: PiProcessBudget;
|
||||
private readonly childPermits: FifoSemaphore;
|
||||
private readonly reclaimProcessCapacity: (() => Promise<boolean>) | undefined;
|
||||
private readonly reclaimProcessCapacity: ((signal?: AbortSignal) => Promise<boolean>) | undefined;
|
||||
private readonly createId: NonNullable<PiSubagentSchedulerOptions['createId']>;
|
||||
private readonly dispatches = new Map<string, DispatchRecord>();
|
||||
private readonly parentDispatches = new Map<string, Set<string>>();
|
||||
@@ -353,12 +353,7 @@ export class PiSubagentScheduler {
|
||||
let child: PiSubagentChild | undefined;
|
||||
try {
|
||||
releaseChild = await this.childPermits.acquire(record.controller.signal);
|
||||
const budgetWasFull = this.processBudget.activeCount >= this.processBudget.maxProcesses;
|
||||
const processLeaseFlight = this.processBudget.acquire(record.controller.signal, 'child');
|
||||
if (budgetWasFull && this.reclaimProcessCapacity) {
|
||||
await this.reclaimProcessCapacity();
|
||||
}
|
||||
processLease = await processLeaseFlight;
|
||||
processLease = await this.acquireProcessLease(record.controller.signal);
|
||||
if (record.controller.signal.aborted) throw new PiSubagentChildError('SUBAGENT_ABORTED');
|
||||
projected.status = 'running';
|
||||
this.emit(details, onUpdate);
|
||||
@@ -395,6 +390,33 @@ export class PiSubagentScheduler {
|
||||
}
|
||||
}
|
||||
|
||||
private async acquireProcessLease(signal: AbortSignal): Promise<PiProcessLease> {
|
||||
const reservation = new AbortController();
|
||||
const abortReservation = () => reservation.abort();
|
||||
signal.addEventListener('abort', abortReservation, { once: true });
|
||||
if (signal.aborted) reservation.abort();
|
||||
const budgetWasFull = this.processBudget.activeCount >= this.processBudget.maxProcesses;
|
||||
const processLeaseFlight = this.processBudget.acquire(reservation.signal, 'child');
|
||||
try {
|
||||
if (budgetWasFull && this.reclaimProcessCapacity) {
|
||||
const outcome = await Promise.race([
|
||||
processLeaseFlight.then((lease) => ({ lease })),
|
||||
this.reclaimProcessCapacity(reservation.signal).then(() => null),
|
||||
]);
|
||||
if (outcome) return outcome.lease;
|
||||
}
|
||||
return await processLeaseFlight;
|
||||
} catch (error) {
|
||||
reservation.abort();
|
||||
const orphanedLease = await processLeaseFlight.catch(() => undefined);
|
||||
orphanedLease?.release();
|
||||
throw error;
|
||||
} finally {
|
||||
signal.removeEventListener('abort', abortReservation);
|
||||
reservation.abort();
|
||||
}
|
||||
}
|
||||
|
||||
private markRemaining(
|
||||
details: SubagentDetailsV1,
|
||||
from: number,
|
||||
|
||||
Reference in New Issue
Block a user