From 76f954353673e7e1e46bd73087039dba124027e8 Mon Sep 17 00:00:00 2001 From: Bilal Godil Date: Wed, 15 Apr 2026 14:42:20 -0700 Subject: [PATCH] simplify emulator fast-start: tighter polls, drop dead wrappers - run-emulator.sh: drop wait_for_condition poll interval from 1s to 0.2s - emulator.ts: replace existsSync+readFileSync TOCTOU in readInternalPck with try/ENOENT; tighten initial backoff to 50ms; drop redundant mkdirSync in startEmulator; surface stop-failure on stderr instead of swallowing silently - iso.ts: inline trivial buildRootDirRecordInVD wrapper --- docker/local-emulator/qemu/run-emulator.sh | 2 +- packages/stack-cli/src/commands/emulator.ts | 16 +++++++++------- packages/stack-cli/src/lib/iso.ts | 14 +++++--------- 3 files changed, 15 insertions(+), 17 deletions(-) diff --git a/docker/local-emulator/qemu/run-emulator.sh b/docker/local-emulator/qemu/run-emulator.sh index a49b10b42..75cbd3a4b 100755 --- a/docker/local-emulator/qemu/run-emulator.sh +++ b/docker/local-emulator/qemu/run-emulator.sh @@ -212,7 +212,7 @@ wait_for_condition() { log "${label} ready in ${elapsed}s" return 0 fi - sleep 1 + sleep 0.2 elapsed=$((SECONDS - started)) printf "\r [%3ds] %s..." "$elapsed" "$label" done diff --git a/packages/stack-cli/src/commands/emulator.ts b/packages/stack-cli/src/commands/emulator.ts index 00e8fdae6..3833cffee 100644 --- a/packages/stack-cli/src/commands/emulator.ts +++ b/packages/stack-cli/src/commands/emulator.ts @@ -57,11 +57,13 @@ function internalPckPath(): string { async function readInternalPck(timeoutMs = 60_000): Promise { const path = internalPckPath(); const deadline = Date.now() + timeoutMs; - let delay = 250; + let delay = 50; while (Date.now() < deadline) { - if (existsSync(path)) { + try { const contents = readFileSync(path, "utf-8").trim(); if (contents) return contents; + } catch (e) { + if ((e as NodeJS.ErrnoException).code !== "ENOENT") throw e; } await new Promise((r) => setTimeout(r, delay)); delay = Math.min(delay * 2, 2000); @@ -223,7 +225,6 @@ function isEmulatorRunning(): boolean { } async function startEmulator(arch: "arm64" | "amd64"): Promise { - mkdirSync(emulatorImageDir(), { recursive: true }); const img = join(emulatorImageDir(), `stack-emulator-${arch}.qcow2`); if (!existsSync(img)) { console.log("No emulator image found. Pulling latest..."); @@ -518,9 +519,6 @@ export function registerEmulatorCommand(program: Command) { } if (!existsSync(dest)) throw new CliError(`Expected image not found at ${dest} after download.`); console.log(`Downloaded: ${dest}`); - // CI publishes both files inside the single qemu-emulator-${arch} - // artifact, so the first download already extracts the snapshot when - // present. Older builds may not include it. if (existsSync(snapshotDest)) { console.log(`Downloaded: ${snapshotDest}`); } else { @@ -617,8 +615,12 @@ export function registerEmulatorCommand(program: Command) { process.exit(exitCode); } else { console.log("\nStopping emulator..."); + const warnStopFailed = (e: unknown) => { + const msg = e instanceof Error ? e.message : String(e); + process.stderr.write(`Failed to stop emulator cleanly: ${msg}\n`); + }; runEmulator("stop") - .catch(() => { /* best-effort stop */ }) + .catch(warnStopFailed) .finally(() => process.exit(exitCode)); } }); diff --git a/packages/stack-cli/src/lib/iso.ts b/packages/stack-cli/src/lib/iso.ts index b226af0bc..6b8ac1bb1 100644 --- a/packages/stack-cli/src/lib/iso.ts +++ b/packages/stack-cli/src/lib/iso.ts @@ -259,13 +259,6 @@ function buildVolumeDescriptorTerminator(): Buffer { return buf; } -// Builds the 34-byte root directory record that lives inside the volume -// descriptor (BP 157-190 of PVD/SVD). Identical layout to a regular directory -// record but identifier is the single byte 0x00. -function buildRootDirRecordInVD(rootSector: number, rootSize: number, recDate: Buffer): Buffer { - return buildDirRecord(rootSector, rootSize, true, recDate, Buffer.from([0x00])); -} - export type IsoFile = { name: string, data: Buffer }; export function buildIso(volumeId: string, files: IsoFile[]): Buffer { @@ -317,8 +310,11 @@ export function buildIso(volumeId: string, files: IsoFile[]): Buffer { const totalSectors = nextSector; const pathTableSize = 10; - const isoRootDirRecordVD = buildRootDirRecordInVD(isoRootSector, SECTOR, recDate); - const jolietRootDirRecordVD = buildRootDirRecordInVD(jolietRootSector, SECTOR, recDate); + // Root directory record inside the volume descriptor (BP 157-190 of PVD/SVD): + // same layout as a regular dir record but the identifier is the single byte 0x00. + const rootIdent = Buffer.from([0x00]); + const isoRootDirRecordVD = buildDirRecord(isoRootSector, SECTOR, true, recDate, rootIdent); + const jolietRootDirRecordVD = buildDirRecord(jolietRootSector, SECTOR, true, recDate, rootIdent); const pvd = buildVolumeDescriptor({ joliet: false,