@impz/testing 6.1.1 → 6.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +159 -89
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -21955,7 +21955,7 @@ import { join as join47 } from "path";
21955
21955
  // packages/daemon/package.json
21956
21956
  var package_default = {
21957
21957
  name: "@impz/daemon",
21958
- version: "6.1.1",
21958
+ version: "6.2.1",
21959
21959
  private: true,
21960
21960
  description: "impd: the imp host daemon",
21961
21961
  type: "module",
@@ -27277,7 +27277,7 @@ async function startFirecracker(command, paths, cgroupProcs = null, mergeWrapper
27277
27277
  writeRegularFile(paths.pidFile, `${String(child.pid)}
27278
27278
  `);
27279
27279
  const deadline = Date.now() + SOCKET_WAIT_MS;
27280
- while (!existsSync6(paths.apiSocket)) {
27280
+ while (!await probeListening(paths.apiSocket)) {
27281
27281
  if (child.exitCode !== null || Date.now() > deadline) {
27282
27282
  stopPid(child.pid);
27283
27283
  throw new Error(`firecracker did not open its API socket (exit ${String(child.exitCode)}): ${readLogTail(paths.logFile)}`);
@@ -27286,6 +27286,18 @@ async function startFirecracker(command, paths, cgroupProcs = null, mergeWrapper
27286
27286
  }
27287
27287
  return child.pid;
27288
27288
  }
27289
+ async function probeListening(socketPath) {
27290
+ if (!existsSync6(socketPath)) {
27291
+ return false;
27292
+ }
27293
+ try {
27294
+ const connection = await Bun.connect({ unix: socketPath, socket: { data: () => {} } });
27295
+ connection.end();
27296
+ return true;
27297
+ } catch {
27298
+ return false;
27299
+ }
27300
+ }
27289
27301
  function probeProcessAlive(pid, socket, procRoot = "/proc") {
27290
27302
  const state = readStatFields(pid, procRoot)?.[0];
27291
27303
  if (state === undefined || state === "Z" || state === "X") {
@@ -37572,15 +37584,15 @@ function buildDeferredAdmission() {
37572
37584
  };
37573
37585
  }
37574
37586
 
37575
- // packages/daemon/src/platform/process/semaphore.ts
37576
- function createSemaphore(limit) {
37577
- const waiters = [];
37587
+ // packages/daemon/src/platform/process/priority-semaphore.ts
37588
+ function createPrioritySemaphore(limit) {
37589
+ const waiters = { foreground: [], background: [] };
37578
37590
  let active = 0;
37579
37591
  return {
37580
- run: async (task) => {
37592
+ run: async (priority, task) => {
37581
37593
  if (active >= limit) {
37582
37594
  const turn = Promise.withResolvers();
37583
- waiters.push(turn.resolve);
37595
+ waiters[priority].push(turn.resolve);
37584
37596
  await turn.promise;
37585
37597
  } else {
37586
37598
  active += 1;
@@ -37588,7 +37600,7 @@ function createSemaphore(limit) {
37588
37600
  try {
37589
37601
  return await task();
37590
37602
  } finally {
37591
- const next = waiters.shift();
37603
+ const next = waiters.foreground.shift() ?? waiters.background.shift();
37592
37604
  if (next === undefined) {
37593
37605
  active -= 1;
37594
37606
  } else {
@@ -37709,7 +37721,7 @@ var ENFORCE_INTERVAL_MS = 5000;
37709
37721
  var ENFORCE_TRIGGER = "RAM over budget";
37710
37722
  var ORDINARY_TRIGGER = "RAM outside the huge page pool";
37711
37723
  function createRamGovernor(deps) {
37712
- const admission = createSemaphore(1);
37724
+ const admission = createPrioritySemaphore(1);
37713
37725
  const reservations = new Map;
37714
37726
  const now = deps.now ?? Date.now;
37715
37727
  let stuckOver = false;
@@ -37905,7 +37917,7 @@ function createRamGovernor(deps) {
37905
37917
  }
37906
37918
  };
37907
37919
  return {
37908
- admit: (request) => admission.run(async () => {
37920
+ admit: (request) => admission.run(toTaskPriority(request), async () => {
37909
37921
  const hugePagesMib = request.hugePagesMib ?? 0;
37910
37922
  const hostBudgets = readBudgets();
37911
37923
  const budgets = hugePagesMib > 0 ? { ...hostBudgets, ordinaryMib: null } : hostBudgets;
@@ -37967,7 +37979,7 @@ function createRamGovernor(deps) {
37967
37979
  reservations.delete(id);
37968
37980
  pendingHugePages.delete(id);
37969
37981
  },
37970
- admitGrow: (request) => admission.run(async () => {
37982
+ admitGrow: (request) => admission.run("background", async () => {
37971
37983
  const listed = await deps.listAwake();
37972
37984
  if (!listed.some((imp) => imp.id === request.id)) {
37973
37985
  return false;
@@ -38024,7 +38036,7 @@ function createRamGovernor(deps) {
38024
38036
  headroomMib: usage.headroomMib
38025
38037
  };
38026
38038
  },
38027
- enforce: () => admission.run(async () => {
38039
+ enforce: () => admission.run("background", async () => {
38028
38040
  const budgets = readBudgets();
38029
38041
  const room = await makeRoom({
38030
38042
  excludeId: null,
@@ -38051,6 +38063,9 @@ function createRamGovernor(deps) {
38051
38063
  function pickEffectiveShortfall(usage, budgets, requestedMib) {
38052
38064
  return pickShortfall({ ramMib: usage.effectiveMib, ordinaryMib: usage.ordinaryEffectiveMib }, budgets, requestedMib);
38053
38065
  }
38066
+ function toTaskPriority(request) {
38067
+ return request.isBackground === true ? "background" : "foreground";
38068
+ }
38054
38069
 
38055
38070
  // packages/daemon/src/vm/watchdog/agent-watchdog.ts
38056
38071
  var HOUR_MS = 3600000;
@@ -38876,7 +38891,6 @@ function createStorageGate(clock = REAL_CLOCK) {
38876
38891
  }
38877
38892
 
38878
38893
  // packages/daemon/src/vm/templates/boot-templates.ts
38879
- import { createHash as createHash4 } from "crypto";
38880
38894
  import {
38881
38895
  existsSync as existsSync9,
38882
38896
  mkdirSync as mkdirSync12,
@@ -38890,8 +38904,18 @@ import {
38890
38904
  } from "fs";
38891
38905
  import { join as join18 } from "path";
38892
38906
  import { withContext as withContext2 } from "@logtape/logtape";
38907
+ import { match as match10 } from "ts-pattern";
38893
38908
  import * as z75 from "zod";
38894
38909
 
38910
+ // packages/daemon/src/platform/process/semaphore.ts
38911
+ function createSemaphore(limit) {
38912
+ const semaphore = createPrioritySemaphore(limit);
38913
+ return { run: (task) => semaphore.run("foreground", task) };
38914
+ }
38915
+
38916
+ // packages/daemon/src/vm/templates/build-template-key.ts
38917
+ import { createHash as createHash4 } from "crypto";
38918
+
38895
38919
  // packages/daemon/src/vm/vmm/configure-vm.ts
38896
38920
  function createMarks() {
38897
38921
  const marks = {};
@@ -38947,8 +38971,41 @@ async function setupVm(api, config) {
38947
38971
  await api.putBalloon(VM_DEVICES.balloon);
38948
38972
  }
38949
38973
 
38974
+ // packages/daemon/src/vm/templates/build-template-key.ts
38975
+ var TEMPLATE_BOOT_ARGS = [...BASE_BOOT_ARGS, "imp.template=1"].join(" ");
38976
+ function buildTemplateKey(identity, shape) {
38977
+ const fields = {
38978
+ guestKernel: identity.guestKernel,
38979
+ systemDrive: identity.systemDrive,
38980
+ firecrackerVersion: identity.firecrackerVersion,
38981
+ snapshotVersion: identity.snapshotVersion,
38982
+ hostKernel: identity.hostKernel,
38983
+ cpuModel: identity.cpuModel,
38984
+ cpuFlags: identity.cpuFlags,
38985
+ bootArgs: TEMPLATE_BOOT_ARGS,
38986
+ devices: VM_DEVICES,
38987
+ memoryMib: shape.memoryMib,
38988
+ vcpus: shape.vcpus,
38989
+ ...shape.hugePages === "2M" && { hugePages: shape.hugePages }
38990
+ };
38991
+ return createHash4("sha256").update(JSON.stringify(fields)).digest("hex");
38992
+ }
38993
+
38994
+ // packages/daemon/src/vm/templates/pick-restore-failure-outcome.ts
38995
+ import { match as match7 } from "ts-pattern";
38996
+ function pickRestoreFailureOutcome(facts) {
38997
+ if (!facts.isCurrentBuild) {
38998
+ return "ignore";
38999
+ }
39000
+ return match7(facts.fault).with(null, () => "ignore").with("agent", () => facts.restoredNow > facts.restoredBefore ? "keep" : pickCountedOutcome(facts)).with("load", () => pickCountedOutcome(facts)).exhaustive();
39001
+ }
39002
+ var MAX_RESTORE_FAILURES = 3;
39003
+ function pickCountedOutcome(facts) {
39004
+ return facts.failuresBefore + 1 >= MAX_RESTORE_FAILURES && !facts.isOff ? "disable" : "remove";
39005
+ }
39006
+
38950
39007
  // packages/daemon/src/vm/templates/template-locks.ts
38951
- import { match as match8 } from "ts-pattern";
39008
+ import { match as match9 } from "ts-pattern";
38952
39009
 
38953
39010
  // packages/daemon/src/vm/templates/can-admit-lock.ts
38954
39011
  var MAX_LOCKED_PERCENT = 10;
@@ -38959,10 +39016,10 @@ function canAdmitLock(admission) {
38959
39016
  }
38960
39017
 
38961
39018
  // packages/daemon/src/vm/templates/pick-lock-outcome.ts
38962
- import { P as P3, match as match7 } from "ts-pattern";
39019
+ import { P as P3, match as match8 } from "ts-pattern";
38963
39020
  var MIB4 = 1024 * 1024;
38964
39021
  function pickLockOutcome(result, isCurrent) {
38965
- return match7(result).with({ errno: P3.number }, (failed) => ({ kind: "failed", errno: failed.errno })).with({ declinedBytes: P3.number }, (declined) => ({
39022
+ return match8(result).with({ errno: P3.number }, (failed) => ({ kind: "failed", errno: failed.errno })).with({ declinedBytes: P3.number }, (declined) => ({
38966
39023
  kind: "declined",
38967
39024
  lockMib: Math.ceil(declined.declinedBytes / MIB4)
38968
39025
  })).with({ lock: P3.select() }, (lock) => isCurrent ? { kind: "held", lock, lockMib: Math.ceil(lock.lockedBytes / MIB4) } : { kind: "stale", lock }).exhaustive();
@@ -38990,7 +39047,7 @@ function createTemplateLocks(deps) {
38990
39047
  }
38991
39048
  return isAdmitted;
38992
39049
  });
38993
- match8(pickLockOutcome(result, deps.probeCurrent(target))).with({ kind: "failed" }, (failed) => {
39050
+ match9(pickLockOutcome(result, deps.probeCurrent(target))).with({ kind: "failed" }, (failed) => {
38994
39051
  deps.logger.warning("boot template {key} memory not locked (errno {errno}): a restore reads what the host dropped", { key, errno: String(failed.errno) });
38995
39052
  }).with({ kind: "declined" }, (declined) => {
38996
39053
  deps.logger.info("boot template {key} memory not locked: {lockMib} MiB would take the locks past a tenth of the RAM budget", { key, lockMib: String(declined.lockMib) });
@@ -39031,7 +39088,6 @@ function createTemplateLocks(deps) {
39031
39088
  }
39032
39089
 
39033
39090
  // packages/daemon/src/vm/templates/boot-templates.ts
39034
- var TEMPLATE_BOOT_ARGS = [...BASE_BOOT_ARGS, "imp.template=1"].join(" ");
39035
39091
  var TEMPLATE_ADDRESS = {
39036
39092
  slot: -1,
39037
39093
  tap: "imp-tpl",
@@ -39052,7 +39108,6 @@ var MIB6 = 1024 * 1024;
39052
39108
  var MIN_MISSES = 2;
39053
39109
  var MAX_TEMPLATES = 4;
39054
39110
  var MAX_BUILD_FAILURES = 3;
39055
- var MAX_RESTORE_FAILURES = 3;
39056
39111
  var BUILD_RETRY_MS = 60000;
39057
39112
  var TemplateMetaSchema = z75.object({
39058
39113
  buildId: z75.string(),
@@ -39065,23 +39120,6 @@ var TemplateMetaSchema = z75.object({
39065
39120
  class BuildRefusedError extends Error {
39066
39121
  name = "BuildRefusedError";
39067
39122
  }
39068
- function buildTemplateKey(identity, shape) {
39069
- const fields = {
39070
- guestKernel: identity.guestKernel,
39071
- systemDrive: identity.systemDrive,
39072
- firecrackerVersion: identity.firecrackerVersion,
39073
- snapshotVersion: identity.snapshotVersion,
39074
- hostKernel: identity.hostKernel,
39075
- cpuModel: identity.cpuModel,
39076
- cpuFlags: identity.cpuFlags,
39077
- bootArgs: TEMPLATE_BOOT_ARGS,
39078
- devices: VM_DEVICES,
39079
- memoryMib: shape.memoryMib,
39080
- vcpus: shape.vcpus,
39081
- ...shape.hugePages === "2M" && { hugePages: shape.hugePages }
39082
- };
39083
- return createHash4("sha256").update(JSON.stringify(fields)).digest("hex");
39084
- }
39085
39123
  function createBootTemplates(deps) {
39086
39124
  const root = join18(deps.dataDir, "templates");
39087
39125
  const now = deps.now ?? Date.now;
@@ -39089,6 +39127,7 @@ function createBootTemplates(deps) {
39089
39127
  const building = createSingleFlight();
39090
39128
  const records = new Map;
39091
39129
  const lastUsed = new Map;
39130
+ const restoredByBuild = new Map;
39092
39131
  const lane = createSemaphore(1);
39093
39132
  const getRecord = (key) => {
39094
39133
  const found = records.get(key);
@@ -39262,7 +39301,8 @@ function createBootTemplates(deps) {
39262
39301
  reserveMib: Math.ceil(shape.memoryMib * deps.bootReservePercent / 100),
39263
39302
  memoryMib: shape.memoryMib,
39264
39303
  hugePagesMib: shape.hugePages === "2M" ? shape.memoryMib : 0,
39265
- maySleepImps: false
39304
+ maySleepImps: false,
39305
+ isBackground: true
39266
39306
  });
39267
39307
  return await (deps.diskBudget === undefined ? writeAdmitted() : deps.diskBudget.withRoom(shape.memoryMib * MIB6, writeAdmitted));
39268
39308
  } catch (error) {
@@ -39345,21 +39385,33 @@ function createBootTemplates(deps) {
39345
39385
  return null;
39346
39386
  },
39347
39387
  buildTemplate: createTemplateFiles,
39348
- reportFailure: (files, isTemplateFault) => {
39349
- if (!isTemplateFault || readMeta(files.key)?.buildId !== files.buildId) {
39350
- return;
39351
- }
39388
+ reportFailure: (files, failure) => {
39352
39389
  const record = getRecord(files.key);
39353
- record.restoreFailures += 1;
39354
- removeDir(files.key);
39355
- if (record.restoreFailures >= MAX_RESTORE_FAILURES && !record.isOff) {
39390
+ const outcome = pickRestoreFailureOutcome({
39391
+ fault: failure.fault,
39392
+ isCurrentBuild: readMeta(files.key)?.buildId === files.buildId,
39393
+ restoredBefore: failure.restoredBefore,
39394
+ restoredNow: restoredByBuild.get(files.buildId) ?? 0,
39395
+ failuresBefore: record.restoreFailures,
39396
+ isOff: record.isOff
39397
+ });
39398
+ const removeFailed = () => {
39399
+ record.restoreFailures += 1;
39400
+ removeDir(files.key);
39401
+ };
39402
+ match10(outcome).with("ignore", () => {}).with("keep", () => {
39403
+ getImpdLogger("boot").info("boot template {key} kept: its agent timed out while another restore of it succeeded", { key: files.key.slice(0, 12) });
39404
+ }).with("remove", removeFailed).with("disable", () => {
39405
+ removeFailed();
39356
39406
  record.isOff = true;
39357
39407
  getImpdLogger("boot").warning("boot template {key} off until impd restarts: {restoreFailures} restores failed", { key: files.key.slice(0, 12), restoreFailures: String(record.restoreFailures) });
39358
- }
39408
+ }).exhaustive();
39359
39409
  },
39360
39410
  reportRestored: (files) => {
39361
39411
  getRecord(files.key).restoreFailures = 0;
39412
+ restoredByBuild.set(files.buildId, (restoredByBuild.get(files.buildId) ?? 0) + 1);
39362
39413
  },
39414
+ countRestored: (files) => restoredByBuild.get(files.buildId) ?? 0,
39363
39415
  removeStale: () => {
39364
39416
  if (!existsSync9(root)) {
39365
39417
  return [];
@@ -43288,18 +43340,18 @@ async function setupRestoreCommand(plan, jails) {
43288
43340
  }
43289
43341
 
43290
43342
  class TemplateRestoreError extends Error {
43291
- isTemplateFault;
43292
- constructor(message, isTemplateFault, cause) {
43343
+ fault;
43344
+ constructor(message, fault, cause) {
43293
43345
  super(message, { cause });
43294
43346
  this.name = "TemplateRestoreError";
43295
- this.isTemplateFault = isTemplateFault;
43347
+ this.fault = fault;
43296
43348
  }
43297
43349
  }
43298
43350
  async function loadTemplateVm(plan, jails, options = {}) {
43299
43351
  const marks = createMarks();
43300
43352
  const pid = await startRestoreProcess(plan, jails, options.mergeWrapper ?? null);
43301
43353
  marks.setMark("spawn");
43302
- const step = { isTemplateFault: true };
43354
+ const step = { fault: "load" };
43303
43355
  try {
43304
43356
  const api = (options.createClient ?? createFirecrackerClient)(plan.paths.apiSocket);
43305
43357
  const isUffd = plan.hugePages === "2M";
@@ -43326,14 +43378,15 @@ async function loadTemplateVm(plan, jails, options = {}) {
43326
43378
  jails.seal(plan.paths, pid);
43327
43379
  await api.resume();
43328
43380
  marks.setMark("resume");
43381
+ step.fault = "agent";
43329
43382
  await waitForAgent(plan.paths.vsockSocket, {
43330
- deadlineMs: VM_DEADLINES.agentMs,
43383
+ deadlineMs: options.parkedAgentMs ?? VM_DEADLINES.agentMs,
43331
43384
  attemptMs: 200,
43332
43385
  maxDelayMs: RESTORE_PING_DELAY_MS,
43333
43386
  isParked: true
43334
43387
  });
43335
43388
  marks.setMark("parked");
43336
- step.isTemplateFault = false;
43389
+ step.fault = null;
43337
43390
  const diskBytes = await plan.diskReady;
43338
43391
  if (plan.jail !== null) {
43339
43392
  jails.setupDiskOwner(plan.paths, plan.jail);
@@ -43368,7 +43421,7 @@ async function loadTemplateVm(plan, jails, options = {}) {
43368
43421
  await waitForExit(pid, plan.paths.apiSocket, VM_DEADLINES.killMs);
43369
43422
  await jails.release(plan.paths.impId);
43370
43423
  throw new TemplateRestoreError(`template restore failed: ${reason}
43371
- ${readLogTail(plan.paths.logFile, 5)}`, step.isTemplateFault, error);
43424
+ ${readLogTail(plan.paths.logFile, 5)}`, step.fault, error);
43372
43425
  }
43373
43426
  }
43374
43427
 
@@ -43398,6 +43451,7 @@ async function startColdVm(context, start) {
43398
43451
  const diskBytes = readDiskBytes();
43399
43452
  Promise.allSettled([diskBytes]);
43400
43453
  if (template !== null && template !== undefined) {
43454
+ const restoredBefore = context.templates?.countRestored(template) ?? 0;
43401
43455
  try {
43402
43456
  const vm = await context.vms.loadTemplateVm({
43403
43457
  firecrackerBin: context.config.firecrackerBin,
@@ -43441,8 +43495,8 @@ async function startColdVm(context, start) {
43441
43495
  key: template.key.slice(0, 12),
43442
43496
  error: getErrorMessage(error)
43443
43497
  });
43444
- const isTemplateFault = error instanceof TemplateRestoreError && error.isTemplateFault;
43445
- context.templates?.reportFailure(template, isTemplateFault);
43498
+ const fault = error instanceof TemplateRestoreError ? error.fault : null;
43499
+ context.templates?.reportFailure(template, { fault, restoredBefore });
43446
43500
  }
43447
43501
  }
43448
43502
  await diskBytes;
@@ -43476,9 +43530,19 @@ function formatGuestSteps(steps) {
43476
43530
 
43477
43531
  // packages/daemon/src/imps/imps/imp-vm-ops.ts
43478
43532
  var SLEEP_CONCURRENCY = 2;
43533
+ var RESTORE_CONCURRENCY = 4;
43479
43534
  var NO_JAIL_CGROUP = "a jailed VM starts only in its own cgroup, and it has none: the cpu controller is not delegated to /sys/fs/cgroup/imps, or the setup of imps/<id> failed (see the log); set IMP_JAILER=false on a host without cgroup delegation";
43480
- function createImpVmOps(context) {
43535
+ function createImpVmOps(host) {
43481
43536
  const sleepSlots = createSemaphore(SLEEP_CONCURRENCY);
43537
+ const wakeSlots = createSemaphore(RESTORE_CONCURRENCY);
43538
+ const templateSlots = createSemaphore(RESTORE_CONCURRENCY);
43539
+ const context = {
43540
+ ...host,
43541
+ vms: {
43542
+ ...host.vms,
43543
+ loadTemplateVm: (options) => templateSlots.run(() => host.vms.loadTemplateVm(options))
43544
+ }
43545
+ };
43482
43546
  let isStopping = false;
43483
43547
  const updateState = async (imp, change) => {
43484
43548
  const updated = await updateLockedImpState(context.db, imp, change);
@@ -43639,7 +43703,7 @@ function createImpVmOps(context) {
43639
43703
  }
43640
43704
  return imp.isDiskGrowPending ? setGrowPending(imp, false) : imp;
43641
43705
  };
43642
- const wakeImpVm = makeWakeImpVm({
43706
+ const startWake = makeWakeImpVm({
43643
43707
  context,
43644
43708
  isStopping: () => isStopping,
43645
43709
  updateState,
@@ -43648,6 +43712,7 @@ function createImpVmOps(context) {
43648
43712
  startColdImpVm,
43649
43713
  growGuestDisk
43650
43714
  });
43715
+ const wakeImpVm = (imp) => wakeSlots.run(() => startWake(imp));
43651
43716
  const requireRunningImp = async (imp) => {
43652
43717
  if (imp.state === "running") {
43653
43718
  return imp;
@@ -43703,7 +43768,7 @@ function countStepsMs(timings) {
43703
43768
  }
43704
43769
 
43705
43770
  // packages/daemon/src/imps/imps/services/service-api.ts
43706
- import { match as match9 } from "ts-pattern";
43771
+ import { match as match11 } from "ts-pattern";
43707
43772
 
43708
43773
  // packages/daemon/src/imps/imps/services/create-log-decoders.ts
43709
43774
  function createLogDecoders() {
@@ -43927,7 +43992,7 @@ function handleAgentError(service) {
43927
43992
  if (!(error instanceof AgentError)) {
43928
43993
  throw error;
43929
43994
  }
43930
- throw match9(error.code).with("NO_SERVICE", () => buildNotFoundError("service", service)).with("SERVICE_EXISTS", () => buildConflictError("service", service)).with("BAD_REQUEST", () => new ImpdError("BAD_REQUEST", { message: error.detail })).with("AGENT_OUTDATED", () => buildAgentOutdatedApiError(error.message)).otherwise(() => error);
43995
+ throw match11(error.code).with("NO_SERVICE", () => buildNotFoundError("service", service)).with("SERVICE_EXISTS", () => buildConflictError("service", service)).with("BAD_REQUEST", () => new ImpdError("BAD_REQUEST", { message: error.detail })).with("AGENT_OUTDATED", () => buildAgentOutdatedApiError(error.message)).otherwise(() => error);
43931
43996
  };
43932
43997
  }
43933
43998
  function stopStreams(streams) {
@@ -45028,28 +45093,29 @@ function createVmRunner(jails, options = {}) {
45028
45093
  setMark("spawn");
45029
45094
  try {
45030
45095
  const api = createClient(plan.paths.apiSocket);
45031
- const versionPromise = api.readVersion();
45032
- await setupVm(api, {
45033
- kernelPath: plan.kernelPath,
45034
- bootArgs: buildBootArgs(plan),
45035
- vcpus: plan.vcpus,
45036
- memoryMib: plan.memoryMib,
45037
- hugePages: plan.hugePages ?? "None",
45038
- diskPath: plan.paths.disk,
45039
- systemDrivePath: plan.systemDrivePath,
45040
- vsockPath: plan.paths.vsockSocket,
45041
- tap: plan.address.tap,
45042
- guestMac: plan.address.guestMac
45043
- });
45044
- const regionMib = getRegionMib(plan.memoryMib, plan.maxMemoryMib);
45045
- if (regionMib > 0) {
45046
- await api.putHotplugMemory({
45047
- totalSizeMib: regionMib,
45048
- slotSizeMib: SLOT_MIB,
45049
- blockSizeMib: BLOCK_MIB
45096
+ const configure = async () => {
45097
+ await setupVm(api, {
45098
+ kernelPath: plan.kernelPath,
45099
+ bootArgs: buildBootArgs(plan),
45100
+ vcpus: plan.vcpus,
45101
+ memoryMib: plan.memoryMib,
45102
+ hugePages: plan.hugePages ?? "None",
45103
+ diskPath: plan.paths.disk,
45104
+ systemDrivePath: plan.systemDrivePath,
45105
+ vsockPath: plan.paths.vsockSocket,
45106
+ tap: plan.address.tap,
45107
+ guestMac: plan.address.guestMac
45050
45108
  });
45051
- }
45052
- const firecrackerVersion = await versionPromise;
45109
+ const regionMib = getRegionMib(plan.memoryMib, plan.maxMemoryMib);
45110
+ if (regionMib > 0) {
45111
+ await api.putHotplugMemory({
45112
+ totalSizeMib: regionMib,
45113
+ slotSizeMib: SLOT_MIB,
45114
+ blockSizeMib: BLOCK_MIB
45115
+ });
45116
+ }
45117
+ };
45118
+ const [firecrackerVersion] = await Promise.all([api.readVersion(), configure()]);
45053
45119
  setMark("configure");
45054
45120
  jails.removeUffdDevice(plan.paths.impId);
45055
45121
  jails.seal(plan.paths, pid);
@@ -46884,7 +46950,7 @@ function readSettled2(row, imp) {
46884
46950
 
46885
46951
  // packages/daemon/src/imp-ops/moves/move-sender.ts
46886
46952
  import invariant6 from "tiny-invariant";
46887
- import { match as match10 } from "ts-pattern";
46953
+ import { match as match12 } from "ts-pattern";
46888
46954
 
46889
46955
  // packages/daemon/src/platform/db/move-send-steps.ts
46890
46956
  var MOVE_SEND_STEPS = [
@@ -47447,7 +47513,7 @@ function createMoveSender(deps) {
47447
47513
  };
47448
47514
  const recoverSend = async (imp, row) => {
47449
47515
  const log = getImpdLogger("move").with({ imp: imp.name });
47450
- await match10(pickSendRecovery(imp.moveState, row?.step)).with("unmark", () => removeMark(imp)).with("wait", () => {
47516
+ await match12(pickSendRecovery(imp.moveState, row?.step)).with("unmark", () => removeMark(imp)).with("wait", () => {
47451
47517
  log.info("a prepared send waits for its send after a restart");
47452
47518
  }).with("abort", async () => {
47453
47519
  const isUndone = await resetSend(imp, row?.peer_url ?? null, row?.ticket ?? null);
@@ -47569,7 +47635,7 @@ function buildPublicError(name) {
47569
47635
  }
47570
47636
  function pickSendRecovery(moveState, step) {
47571
47637
  if (moveState === "sending") {
47572
- return match10(step).with(undefined, () => "unmark").with("prepared", () => "wait").with("streaming", () => "abort").with("sent", () => "commit").exhaustive();
47638
+ return match12(step).with(undefined, () => "unmark").with("prepared", () => "wait").with("streaming", () => "abort").with("sent", () => "commit").exhaustive();
47573
47639
  }
47574
47640
  return moveState === "moved" && step === "sent" ? "commit" : "none";
47575
47641
  }
@@ -47621,7 +47687,7 @@ function createMoveService(deps) {
47621
47687
  // packages/daemon/src/surface/moves/create-move-peer-handler.ts
47622
47688
  import { OpenAPIHandler } from "@orpc/openapi/fetch";
47623
47689
  import { ORPCError as ORPCError7, implement as implement3, onError as onError3 } from "@orpc/server";
47624
- import { match as match11 } from "ts-pattern";
47690
+ import { match as match13 } from "ts-pattern";
47625
47691
  function createMovePeerHandler(deps) {
47626
47692
  const withTicket = async (step, context, call) => {
47627
47693
  const startedAt = deps.now();
@@ -47672,7 +47738,7 @@ function createMovePeerHandler(deps) {
47672
47738
  const respondReceive = async (context) => {
47673
47739
  try {
47674
47740
  const received = await withTicket("receive", context, (checked) => deps.calls.receive(checked, context.request));
47675
- return match11(received).with({ kind: "part" }, (sent) => Response.json({ part: sent.part }, { status: 202 })).with({ kind: "receipt" }, (sent) => Response.json(sent.receipt)).exhaustive();
47741
+ return match13(received).with({ kind: "part" }, (sent) => Response.json({ part: sent.part }, { status: 202 })).with({ kind: "receipt" }, (sent) => Response.json(sent.receipt)).exhaustive();
47676
47742
  } catch (error) {
47677
47743
  const failure = toPeerError(error);
47678
47744
  return Response.json(encodePeerError(failure), { status: failure.status });
@@ -51932,14 +51998,14 @@ function expandScopes(asked) {
51932
51998
  }
51933
51999
 
51934
52000
  // packages/daemon/src/platform/http/render-html.ts
51935
- import { P as P4, match as match12 } from "ts-pattern";
52001
+ import { P as P4, match as match14 } from "ts-pattern";
51936
52002
  var MARKUP = Symbol("markup");
51937
52003
  function renderHTML(strings, ...parts) {
51938
52004
  const html = parts.reduce((text, part, index) => `${text}${toHTML(part)}${strings[index + 1] ?? ""}`, strings[0] ?? "");
51939
52005
  return { [MARKUP]: true, html };
51940
52006
  }
51941
52007
  function toHTML(part) {
51942
- return match12(part).with(P4.string, (text) => escapeHTML(text)).with(P4.array(), (list) => list.map((each) => each.html).join("")).with({ html: P4.string }, (markup) => markup.html).exhaustive();
52008
+ return match14(part).with(P4.string, (text) => escapeHTML(text)).with(P4.array(), (list) => list.map((each) => each.html).join("")).with({ html: P4.string }, (markup) => markup.html).exhaustive();
51943
52009
  }
51944
52010
  function escapeHTML(text) {
51945
52011
  return text.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
@@ -58626,15 +58692,19 @@ log tail`);
58626
58692
  loadTemplateVm: (plan) => runInGeneration(async () => {
58627
58693
  restorePlans.push(plan);
58628
58694
  const vm = await startFakeVm("restore", plan.paths).catch((error) => {
58629
- throw new TemplateRestoreError("restore failed", true, error);
58695
+ throw new TemplateRestoreError("restore failed", "load", error);
58630
58696
  });
58697
+ if (await chooseOutcome("parkedAgent") !== "ok") {
58698
+ alive.delete(vm.pid);
58699
+ throw new TemplateRestoreError("the parked agent never answered", "agent", null);
58700
+ }
58631
58701
  await plan.diskReady.catch((error) => {
58632
58702
  alive.delete(vm.pid);
58633
- throw new TemplateRestoreError("disk failed", false, error);
58703
+ throw new TemplateRestoreError("disk failed", null, error);
58634
58704
  });
58635
58705
  if (await chooseOutcome("claim") !== "ok") {
58636
58706
  alive.delete(vm.pid);
58637
- throw new TemplateRestoreError("claim failed", false, null);
58707
+ throw new TemplateRestoreError("claim failed", null, null);
58638
58708
  }
58639
58709
  restores.push({
58640
58710
  hostname: plan.claim.hostname,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@impz/testing",
3
- "version": "6.1.1",
3
+ "version": "6.2.1",
4
4
  "description": "A real impd on stand-ins, for Bun tests of code that drives impd through @impz/client",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -43,7 +43,7 @@
43
43
  "ts-pattern": "^5.9.2",
44
44
  "xstate": "^5.33.3",
45
45
  "zod": "^4.6.7",
46
- "@impz/client": "6.1.1"
46
+ "@impz/client": "6.2.1"
47
47
  },
48
48
  "engines": {
49
49
  "bun": ">=1.4.3"