Add fleet and per-host concurrency controls
This commit is contained in:
@@ -533,9 +533,18 @@ export function pendingHostOperationRows(hostId: string, lane: OperationLane = '
|
||||
// sibling's work, but still recovers a lost receipt or agent restart.
|
||||
.all(hostId, Number(host.readiness === 'ready' || host.mode === 'draining'), Number(host.inventory.capabilities?.includes('instance.cancel-create') ?? false),
|
||||
...(lane === 'health' ? [new Date(Date.now() - 30_000).toISOString()] : [])) as OperationDeliveryRow[];
|
||||
const preparationLimit = fleetSettings().preparationConcurrency;
|
||||
const preparing = new Set((database().prepare(`SELECT DISTINCT host_id FROM ec_host_operations
|
||||
WHERE kind IN ('library.download','library.import') AND last_delivered_at IS NOT NULL
|
||||
AND state IN ('pending','accepted','running','unknown')`).all() as { host_id: string }[]).map(row => row.host_id));
|
||||
const admitted = rows.filter(operation => {
|
||||
if (!['library.download','library.import'].includes(operation.kind) || preparing.has(hostId) || !preparationLimit) return true;
|
||||
if (preparing.size >= preparationLimit) return false;
|
||||
preparing.add(hostId); return true;
|
||||
});
|
||||
const deliveredAt = now();
|
||||
for (const operation of rows) database().prepare('UPDATE ec_host_operations SET last_delivered_at=? WHERE id=?').run(deliveredAt, operation.id);
|
||||
return rows;
|
||||
for (const operation of admitted) database().prepare('UPDATE ec_host_operations SET last_delivered_at=? WHERE id=?').run(deliveredAt, operation.id);
|
||||
return admitted;
|
||||
})();
|
||||
}
|
||||
/** Payloads never change after enqueueing; fetch one delivery's UTF-8 bytes without decoding. */
|
||||
|
||||
@@ -8,7 +8,7 @@ import { scheduleAvailabilityRefresh } from './availability.js';
|
||||
|
||||
const activeStates = ['pending', 'accepted', 'running', 'unknown'];
|
||||
const supersededDownload = 'Superseded by newer game files before delivery to the host.';
|
||||
type PreparationOperation = { id: string; sequence: number; state: HostOperationState; updated_at: string; error: string | null };
|
||||
type PreparationOperation = { id: string; sequence: number; last_delivered_at: string | null; state: HostOperationState; updated_at: string; error: string | null };
|
||||
type Target = { item: HostPreparationItem; image?: string; versionId?: string; requiredBytes: number; operation?: PreparationOperation };
|
||||
type QueuedSelection = { libraryId: string; image: string | null };
|
||||
type LibraryTarget = Pick<ReturnType<typeof latestPublishedLibraries>[number], 'id' | 'sizeBytes' | 'manifestSha256'> & { name: string; queued?: boolean };
|
||||
@@ -50,7 +50,7 @@ function targetLibraries(selections: QueuedSelection[]): LibraryTarget[] {
|
||||
}
|
||||
|
||||
function latestOperation(hostId: string, kind: string, field: string, value: string): PreparationOperation | undefined {
|
||||
return db().prepare(`SELECT id,rowid AS sequence,state,updated_at,error FROM ec_host_operations
|
||||
return db().prepare(`SELECT id,rowid AS sequence,last_delivered_at,state,updated_at,error FROM ec_host_operations
|
||||
WHERE host_id=? AND kind=? AND json_extract(payload,?)=? ORDER BY created_at DESC,rowid DESC LIMIT 1`)
|
||||
.get(hostId, kind, `$.${field}`, value) as PreparationOperation | undefined;
|
||||
}
|
||||
@@ -117,7 +117,9 @@ function targets(host: Host): Target[] {
|
||||
if (blocker) { Object.assign(target.item, blocker); continue; }
|
||||
if (operation && activeStates.includes(operation.state)) {
|
||||
target.item.state = 'preparing';
|
||||
target.item.detail = operation.state === 'unknown' ? 'Waiting for the agent to reconcile interrupted preparation.'
|
||||
target.item.detail = !target.image && operation.state === 'pending' && !operation.last_delivered_at
|
||||
? 'Queued for game-file preparation; waiting for agent delivery or fleet preparation capacity.'
|
||||
: operation.state === 'unknown' ? 'Waiting for the agent to reconcile interrupted preparation.'
|
||||
: target.image ? 'Downloading the runtime automatically.'
|
||||
: target.item.cache?.status === 'verifying' ? 'Download complete. Verifying all game files.' : 'Downloading game files. Verification starts after all files arrive.';
|
||||
} else if (operation && Date.parse(host.lastHeartbeat!) <= Date.parse(operation.updated_at)) {
|
||||
|
||||
Reference in New Issue
Block a user