Queue native collection launches before async placement

This commit is contained in:
2026-10-05 11:35:50 -04:00
parent 1405114a14
commit 7c76851252
34 changed files with 1324 additions and 244 deletions
@@ -4,6 +4,7 @@ import { config } from '../config.js';
import { db } from '../db/index.js';
import type { NativeInstance } from '../backends/native.js';
import type { LaunchRecipePlan } from '../fleet/launch-recipes.js';
import type { PlacementOptions } from '../hosts/capacity.js';
export interface NativeLaunchInput {
game: 't5' | 't6';
@@ -25,7 +26,7 @@ export interface PreparedNativeLaunch {
let worker: Worker | null = null;
let sequence = 0;
const pending = new Map<number, {
resolve: (value: PreparedNativeLaunch) => void;
resolve: (value: unknown) => void;
reject: (error: Error) => void;
timer: NodeJS.Timeout;
}>();
@@ -39,14 +40,14 @@ function stop(error: Error, current = worker): Promise<number> | undefined {
}
/** The reader and JSON encoder run off the controller's request thread. */
export function prepareNativeLaunch(input: NativeLaunchInput): Promise<PreparedNativeLaunch> {
function run<T>(kind: 'launch' | 'placement', input: NativeLaunchInput | PlacementOptions): Promise<T> {
db(); // Migrations must finish before the independent read-only connection opens.
if (!worker) {
const current = worker = new Worker(resolve(process.env.NODE_ENV === 'production'
? 'build/launch/worker.mjs' : '.svelte-kit/launch/worker.mjs'), {
workerData: { dbPath: resolve(config.dbPath) }
});
current.on('message', ({ id, value, error }: { id: number; value: PreparedNativeLaunch; error?: string }) => {
current.on('message', ({ id, value, error }: { id: number; value: unknown; error?: string }) => {
if (current !== worker) return;
const request = pending.get(id);
if (!request) return;
@@ -59,13 +60,18 @@ export function prepareNativeLaunch(input: NativeLaunchInput): Promise<PreparedN
}
worker.ref();
const id = ++sequence;
return new Promise((resolve, reject) => {
return new Promise<T>((resolve, reject) => {
const timer = setTimeout(() => { void stop(new Error('Launch preparation timed out; preparation will retry.')); }, 60_000);
pending.set(id, { resolve, reject, timer });
worker!.postMessage({ id, input });
pending.set(id, { resolve: value => resolve(value as T), reject, timer });
worker!.postMessage({ id, kind, input });
});
}
export function prepareNativeLaunch(input: NativeLaunchInput): Promise<PreparedNativeLaunch> { return run('launch', input); }
export function evaluateNativePlacement(input: PlacementOptions): Promise<{ hostId: string; lastHeartbeat: string | null }> {
return run('placement', input);
}
export async function closeLaunchPreparation(): Promise<void> {
await stop(new Error('Launch preparation stopped.'));
}