@impz/testing 6.2.0 → 6.3.0

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 +214 -95
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -20107,7 +20107,8 @@ var ImpEventDetailSchema = z17.object({
20107
20107
  trigger: z17.string().optional(),
20108
20108
  coldBootReason: z17.string().optional(),
20109
20109
  steps: z17.record(z17.string(), z17.int()).readonly().optional(),
20110
- released: z17.int().positive().optional()
20110
+ released: z17.int().positive().optional(),
20111
+ memFileMib: z17.int().nonnegative().optional()
20111
20112
  }).readonly();
20112
20113
  var envelope = { v: z17.literal(EVENT_VERSION), at: z17.date() };
20113
20114
  var ImpEventSchema = z17.discriminatedUnion("ev", [
@@ -20623,6 +20624,14 @@ var GuestInfoSchema = z33.object({
20623
20624
  scratchDir: z33.string(),
20624
20625
  isClearedOnBoot: z33.boolean()
20625
20626
  });
20627
+ var HostInfoSchema = z33.object({
20628
+ kernel: z33.string(),
20629
+ cpuModel: z33.string(),
20630
+ isNested: z33.boolean(),
20631
+ hugePages: z33.enum(["off", "2M"]),
20632
+ hugePool: z33.object({ totalMib: CountSchema, freeMib: CountSchema }).nullable(),
20633
+ bootTemplates: CountSchema.nullable()
20634
+ });
20626
20635
  var SystemInfoSchema = z33.object({
20627
20636
  version: z33.string(),
20628
20637
  ramBudgetMib: z33.int().nonnegative(),
@@ -20668,7 +20677,8 @@ var SystemInfoSchema = z33.object({
20668
20677
  egress: z33.object({ isEnforced: z33.boolean() }).optional(),
20669
20678
  public: PublicInfoSchema.nullable().optional(),
20670
20679
  https: HttpsInfoSchema.nullable().optional(),
20671
- features: SystemFeaturesSchema.optional()
20680
+ features: SystemFeaturesSchema.optional(),
20681
+ host: HostInfoSchema.optional()
20672
20682
  });
20673
20683
  var SystemFeaturesInfoSchema = z33.object({
20674
20684
  version: z33.string(),
@@ -21950,17 +21960,19 @@ function parseKsmConfig(parsed) {
21950
21960
  }
21951
21961
 
21952
21962
  // packages/daemon/src/create-impd.ts
21963
+ import { readFileSync as readFileSync36 } from "fs";
21953
21964
  import { release as release2 } from "os";
21954
21965
  import { join as join47 } from "path";
21955
21966
  // packages/daemon/package.json
21956
21967
  var package_default = {
21957
21968
  name: "@impz/daemon",
21958
- version: "6.2.0",
21969
+ version: "6.3.0",
21959
21970
  private: true,
21960
21971
  description: "impd: the imp host daemon",
21961
21972
  type: "module",
21962
21973
  exports: {
21963
21974
  ".": "./src/index.ts",
21975
+ "./real-clock": "./src/platform/process/real-clock.ts",
21964
21976
  "./testing": "./testing/index.ts",
21965
21977
  "./testing/build-stub-clock": "./testing/platform/build-stub-clock.ts",
21966
21978
  "./testing/data-layout": "./src/vm/storage/data-layout.ts",
@@ -27277,7 +27289,7 @@ async function startFirecracker(command, paths, cgroupProcs = null, mergeWrapper
27277
27289
  writeRegularFile(paths.pidFile, `${String(child.pid)}
27278
27290
  `);
27279
27291
  const deadline = Date.now() + SOCKET_WAIT_MS;
27280
- while (!existsSync6(paths.apiSocket)) {
27292
+ while (!await probeListening(paths.apiSocket)) {
27281
27293
  if (child.exitCode !== null || Date.now() > deadline) {
27282
27294
  stopPid(child.pid);
27283
27295
  throw new Error(`firecracker did not open its API socket (exit ${String(child.exitCode)}): ${readLogTail(paths.logFile)}`);
@@ -27286,6 +27298,18 @@ async function startFirecracker(command, paths, cgroupProcs = null, mergeWrapper
27286
27298
  }
27287
27299
  return child.pid;
27288
27300
  }
27301
+ async function probeListening(socketPath) {
27302
+ if (!existsSync6(socketPath)) {
27303
+ return false;
27304
+ }
27305
+ try {
27306
+ const connection = await Bun.connect({ unix: socketPath, socket: { data: () => {} } });
27307
+ connection.end();
27308
+ return true;
27309
+ } catch {
27310
+ return false;
27311
+ }
27312
+ }
27289
27313
  function probeProcessAlive(pid, socket, procRoot = "/proc") {
27290
27314
  const state = readStatFields(pid, procRoot)?.[0];
27291
27315
  if (state === undefined || state === "Z" || state === "X") {
@@ -30507,7 +30531,8 @@ async function readSystemInfo(deps) {
30507
30531
  ksm: readKsmInfo(deps, running, usage.headroomMib),
30508
30532
  public: readPublicInfo(deps.config, imps, deps.publicRecords.readStatus()),
30509
30533
  https: await readHttpsInfo(deps),
30510
- features: SYSTEM_FEATURES
30534
+ features: SYSTEM_FEATURES,
30535
+ host: deps.readHostInfo()
30511
30536
  };
30512
30537
  }
30513
30538
  function readKsmInfo(deps, running, headroomMib) {
@@ -32900,6 +32925,29 @@ function readLimit(request, maxBytes) {
32900
32925
  return declared;
32901
32926
  }
32902
32927
 
32928
+ // packages/daemon/src/surface/read-host-info.ts
32929
+ function readHostInfo(sources) {
32930
+ const cpuInfo = readCpuInfoIfPresent(sources);
32931
+ return {
32932
+ kernel: sources.readKernel(),
32933
+ cpuModel: cpuInfo === null ? UNKNOWN_CPU : parseCpuInfo(cpuInfo).cpuModel,
32934
+ isNested: cpuInfo !== null && isNestedCpu(cpuInfo),
32935
+ hugePages: sources.isHugePages ? "2M" : "off",
32936
+ hugePool: sources.readHugePagePool(),
32937
+ bootTemplates: sources.readBootTemplateCount?.() ?? null
32938
+ };
32939
+ }
32940
+ function readCpuInfoIfPresent(sources) {
32941
+ try {
32942
+ return sources.readCpuInfo();
32943
+ } catch {
32944
+ return null;
32945
+ }
32946
+ }
32947
+ function isNestedCpu(cpuInfo) {
32948
+ return /^flags\s*:.*\bhypervisor\b/mu.test(cpuInfo);
32949
+ }
32950
+
32903
32951
  // packages/daemon/src/create-impd-app.ts
32904
32952
  function createImpdApp(config, deps, parts) {
32905
32953
  const now = deps.now ?? Date.now;
@@ -32956,7 +33004,12 @@ function createImpdApp(config, deps, parts) {
32956
33004
  }),
32957
33005
  imageKeepaliveMs: keepaliveMs,
32958
33006
  ...deps.keepaliveRepeat !== undefined && { imageKeepaliveRepeat: deps.keepaliveRepeat },
32959
- moves: parts.moves
33007
+ moves: parts.moves,
33008
+ readHostInfo: () => readHostInfo({
33009
+ isHugePages: config.hugePages !== null,
33010
+ ...parts.hostReaders,
33011
+ readBootTemplateCount: parts.imps.bootTemplates?.readReadyCount ?? null
33012
+ })
32960
33013
  });
32961
33014
  }
32962
33015
 
@@ -37572,15 +37625,15 @@ function buildDeferredAdmission() {
37572
37625
  };
37573
37626
  }
37574
37627
 
37575
- // packages/daemon/src/platform/process/semaphore.ts
37576
- function createSemaphore(limit) {
37577
- const waiters = [];
37628
+ // packages/daemon/src/platform/process/priority-semaphore.ts
37629
+ function createPrioritySemaphore(limit) {
37630
+ const waiters = { foreground: [], background: [] };
37578
37631
  let active = 0;
37579
37632
  return {
37580
- run: async (task) => {
37633
+ run: async (priority, task) => {
37581
37634
  if (active >= limit) {
37582
37635
  const turn = Promise.withResolvers();
37583
- waiters.push(turn.resolve);
37636
+ waiters[priority].push(turn.resolve);
37584
37637
  await turn.promise;
37585
37638
  } else {
37586
37639
  active += 1;
@@ -37588,7 +37641,7 @@ function createSemaphore(limit) {
37588
37641
  try {
37589
37642
  return await task();
37590
37643
  } finally {
37591
- const next = waiters.shift();
37644
+ const next = waiters.foreground.shift() ?? waiters.background.shift();
37592
37645
  if (next === undefined) {
37593
37646
  active -= 1;
37594
37647
  } else {
@@ -37709,7 +37762,7 @@ var ENFORCE_INTERVAL_MS = 5000;
37709
37762
  var ENFORCE_TRIGGER = "RAM over budget";
37710
37763
  var ORDINARY_TRIGGER = "RAM outside the huge page pool";
37711
37764
  function createRamGovernor(deps) {
37712
- const admission = createSemaphore(1);
37765
+ const admission = createPrioritySemaphore(1);
37713
37766
  const reservations = new Map;
37714
37767
  const now = deps.now ?? Date.now;
37715
37768
  let stuckOver = false;
@@ -37905,7 +37958,7 @@ function createRamGovernor(deps) {
37905
37958
  }
37906
37959
  };
37907
37960
  return {
37908
- admit: (request) => admission.run(async () => {
37961
+ admit: (request) => admission.run(toTaskPriority(request), async () => {
37909
37962
  const hugePagesMib = request.hugePagesMib ?? 0;
37910
37963
  const hostBudgets = readBudgets();
37911
37964
  const budgets = hugePagesMib > 0 ? { ...hostBudgets, ordinaryMib: null } : hostBudgets;
@@ -37967,7 +38020,7 @@ function createRamGovernor(deps) {
37967
38020
  reservations.delete(id);
37968
38021
  pendingHugePages.delete(id);
37969
38022
  },
37970
- admitGrow: (request) => admission.run(async () => {
38023
+ admitGrow: (request) => admission.run("background", async () => {
37971
38024
  const listed = await deps.listAwake();
37972
38025
  if (!listed.some((imp) => imp.id === request.id)) {
37973
38026
  return false;
@@ -38024,7 +38077,7 @@ function createRamGovernor(deps) {
38024
38077
  headroomMib: usage.headroomMib
38025
38078
  };
38026
38079
  },
38027
- enforce: () => admission.run(async () => {
38080
+ enforce: () => admission.run("background", async () => {
38028
38081
  const budgets = readBudgets();
38029
38082
  const room = await makeRoom({
38030
38083
  excludeId: null,
@@ -38051,6 +38104,9 @@ function createRamGovernor(deps) {
38051
38104
  function pickEffectiveShortfall(usage, budgets, requestedMib) {
38052
38105
  return pickShortfall({ ramMib: usage.effectiveMib, ordinaryMib: usage.ordinaryEffectiveMib }, budgets, requestedMib);
38053
38106
  }
38107
+ function toTaskPriority(request) {
38108
+ return request.isBackground === true ? "background" : "foreground";
38109
+ }
38054
38110
 
38055
38111
  // packages/daemon/src/vm/watchdog/agent-watchdog.ts
38056
38112
  var HOUR_MS = 3600000;
@@ -38876,7 +38932,6 @@ function createStorageGate(clock = REAL_CLOCK) {
38876
38932
  }
38877
38933
 
38878
38934
  // packages/daemon/src/vm/templates/boot-templates.ts
38879
- import { createHash as createHash4 } from "crypto";
38880
38935
  import {
38881
38936
  existsSync as existsSync9,
38882
38937
  mkdirSync as mkdirSync12,
@@ -38890,8 +38945,18 @@ import {
38890
38945
  } from "fs";
38891
38946
  import { join as join18 } from "path";
38892
38947
  import { withContext as withContext2 } from "@logtape/logtape";
38948
+ import { match as match10 } from "ts-pattern";
38893
38949
  import * as z75 from "zod";
38894
38950
 
38951
+ // packages/daemon/src/platform/process/semaphore.ts
38952
+ function createSemaphore(limit) {
38953
+ const semaphore = createPrioritySemaphore(limit);
38954
+ return { run: (task) => semaphore.run("foreground", task) };
38955
+ }
38956
+
38957
+ // packages/daemon/src/vm/templates/build-template-key.ts
38958
+ import { createHash as createHash4 } from "crypto";
38959
+
38895
38960
  // packages/daemon/src/vm/vmm/configure-vm.ts
38896
38961
  function createMarks() {
38897
38962
  const marks = {};
@@ -38947,8 +39012,41 @@ async function setupVm(api, config) {
38947
39012
  await api.putBalloon(VM_DEVICES.balloon);
38948
39013
  }
38949
39014
 
39015
+ // packages/daemon/src/vm/templates/build-template-key.ts
39016
+ var TEMPLATE_BOOT_ARGS = [...BASE_BOOT_ARGS, "imp.template=1"].join(" ");
39017
+ function buildTemplateKey(identity, shape) {
39018
+ const fields = {
39019
+ guestKernel: identity.guestKernel,
39020
+ systemDrive: identity.systemDrive,
39021
+ firecrackerVersion: identity.firecrackerVersion,
39022
+ snapshotVersion: identity.snapshotVersion,
39023
+ hostKernel: identity.hostKernel,
39024
+ cpuModel: identity.cpuModel,
39025
+ cpuFlags: identity.cpuFlags,
39026
+ bootArgs: TEMPLATE_BOOT_ARGS,
39027
+ devices: VM_DEVICES,
39028
+ memoryMib: shape.memoryMib,
39029
+ vcpus: shape.vcpus,
39030
+ ...shape.hugePages === "2M" && { hugePages: shape.hugePages }
39031
+ };
39032
+ return createHash4("sha256").update(JSON.stringify(fields)).digest("hex");
39033
+ }
39034
+
39035
+ // packages/daemon/src/vm/templates/pick-restore-failure-outcome.ts
39036
+ import { match as match7 } from "ts-pattern";
39037
+ function pickRestoreFailureOutcome(facts) {
39038
+ if (!facts.isCurrentBuild) {
39039
+ return "ignore";
39040
+ }
39041
+ return match7(facts.fault).with(null, () => "ignore").with("agent", () => facts.restoredNow > facts.restoredBefore ? "keep" : pickCountedOutcome(facts)).with("load", () => pickCountedOutcome(facts)).exhaustive();
39042
+ }
39043
+ var MAX_RESTORE_FAILURES = 3;
39044
+ function pickCountedOutcome(facts) {
39045
+ return facts.failuresBefore + 1 >= MAX_RESTORE_FAILURES && !facts.isOff ? "disable" : "remove";
39046
+ }
39047
+
38950
39048
  // packages/daemon/src/vm/templates/template-locks.ts
38951
- import { match as match8 } from "ts-pattern";
39049
+ import { match as match9 } from "ts-pattern";
38952
39050
 
38953
39051
  // packages/daemon/src/vm/templates/can-admit-lock.ts
38954
39052
  var MAX_LOCKED_PERCENT = 10;
@@ -38959,10 +39057,10 @@ function canAdmitLock(admission) {
38959
39057
  }
38960
39058
 
38961
39059
  // packages/daemon/src/vm/templates/pick-lock-outcome.ts
38962
- import { P as P3, match as match7 } from "ts-pattern";
39060
+ import { P as P3, match as match8 } from "ts-pattern";
38963
39061
  var MIB4 = 1024 * 1024;
38964
39062
  function pickLockOutcome(result, isCurrent) {
38965
- return match7(result).with({ errno: P3.number }, (failed) => ({ kind: "failed", errno: failed.errno })).with({ declinedBytes: P3.number }, (declined) => ({
39063
+ return match8(result).with({ errno: P3.number }, (failed) => ({ kind: "failed", errno: failed.errno })).with({ declinedBytes: P3.number }, (declined) => ({
38966
39064
  kind: "declined",
38967
39065
  lockMib: Math.ceil(declined.declinedBytes / MIB4)
38968
39066
  })).with({ lock: P3.select() }, (lock) => isCurrent ? { kind: "held", lock, lockMib: Math.ceil(lock.lockedBytes / MIB4) } : { kind: "stale", lock }).exhaustive();
@@ -38990,7 +39088,7 @@ function createTemplateLocks(deps) {
38990
39088
  }
38991
39089
  return isAdmitted;
38992
39090
  });
38993
- match8(pickLockOutcome(result, deps.probeCurrent(target))).with({ kind: "failed" }, (failed) => {
39091
+ match9(pickLockOutcome(result, deps.probeCurrent(target))).with({ kind: "failed" }, (failed) => {
38994
39092
  deps.logger.warning("boot template {key} memory not locked (errno {errno}): a restore reads what the host dropped", { key, errno: String(failed.errno) });
38995
39093
  }).with({ kind: "declined" }, (declined) => {
38996
39094
  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 +39129,6 @@ function createTemplateLocks(deps) {
39031
39129
  }
39032
39130
 
39033
39131
  // packages/daemon/src/vm/templates/boot-templates.ts
39034
- var TEMPLATE_BOOT_ARGS = [...BASE_BOOT_ARGS, "imp.template=1"].join(" ");
39035
39132
  var TEMPLATE_ADDRESS = {
39036
39133
  slot: -1,
39037
39134
  tap: "imp-tpl",
@@ -39052,7 +39149,6 @@ var MIB6 = 1024 * 1024;
39052
39149
  var MIN_MISSES = 2;
39053
39150
  var MAX_TEMPLATES = 4;
39054
39151
  var MAX_BUILD_FAILURES = 3;
39055
- var MAX_RESTORE_FAILURES = 3;
39056
39152
  var BUILD_RETRY_MS = 60000;
39057
39153
  var TemplateMetaSchema = z75.object({
39058
39154
  buildId: z75.string(),
@@ -39065,23 +39161,6 @@ var TemplateMetaSchema = z75.object({
39065
39161
  class BuildRefusedError extends Error {
39066
39162
  name = "BuildRefusedError";
39067
39163
  }
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
39164
  function createBootTemplates(deps) {
39086
39165
  const root = join18(deps.dataDir, "templates");
39087
39166
  const now = deps.now ?? Date.now;
@@ -39089,6 +39168,7 @@ function createBootTemplates(deps) {
39089
39168
  const building = createSingleFlight();
39090
39169
  const records = new Map;
39091
39170
  const lastUsed = new Map;
39171
+ const restoredByBuild = new Map;
39092
39172
  const lane = createSemaphore(1);
39093
39173
  const getRecord = (key) => {
39094
39174
  const found = records.get(key);
@@ -39262,7 +39342,8 @@ function createBootTemplates(deps) {
39262
39342
  reserveMib: Math.ceil(shape.memoryMib * deps.bootReservePercent / 100),
39263
39343
  memoryMib: shape.memoryMib,
39264
39344
  hugePagesMib: shape.hugePages === "2M" ? shape.memoryMib : 0,
39265
- maySleepImps: false
39345
+ maySleepImps: false,
39346
+ isBackground: true
39266
39347
  });
39267
39348
  return await (deps.diskBudget === undefined ? writeAdmitted() : deps.diskBudget.withRoom(shape.memoryMib * MIB6, writeAdmitted));
39268
39349
  } catch (error) {
@@ -39345,21 +39426,33 @@ function createBootTemplates(deps) {
39345
39426
  return null;
39346
39427
  },
39347
39428
  buildTemplate: createTemplateFiles,
39348
- reportFailure: (files, isTemplateFault) => {
39349
- if (!isTemplateFault || readMeta(files.key)?.buildId !== files.buildId) {
39350
- return;
39351
- }
39429
+ reportFailure: (files, failure) => {
39352
39430
  const record = getRecord(files.key);
39353
- record.restoreFailures += 1;
39354
- removeDir(files.key);
39355
- if (record.restoreFailures >= MAX_RESTORE_FAILURES && !record.isOff) {
39431
+ const outcome = pickRestoreFailureOutcome({
39432
+ fault: failure.fault,
39433
+ isCurrentBuild: readMeta(files.key)?.buildId === files.buildId,
39434
+ restoredBefore: failure.restoredBefore,
39435
+ restoredNow: restoredByBuild.get(files.buildId) ?? 0,
39436
+ failuresBefore: record.restoreFailures,
39437
+ isOff: record.isOff
39438
+ });
39439
+ const removeFailed = () => {
39440
+ record.restoreFailures += 1;
39441
+ removeDir(files.key);
39442
+ };
39443
+ match10(outcome).with("ignore", () => {}).with("keep", () => {
39444
+ getImpdLogger("boot").info("boot template {key} kept: its agent timed out while another restore of it succeeded", { key: files.key.slice(0, 12) });
39445
+ }).with("remove", removeFailed).with("disable", () => {
39446
+ removeFailed();
39356
39447
  record.isOff = true;
39357
39448
  getImpdLogger("boot").warning("boot template {key} off until impd restarts: {restoreFailures} restores failed", { key: files.key.slice(0, 12), restoreFailures: String(record.restoreFailures) });
39358
- }
39449
+ }).exhaustive();
39359
39450
  },
39360
39451
  reportRestored: (files) => {
39361
39452
  getRecord(files.key).restoreFailures = 0;
39453
+ restoredByBuild.set(files.buildId, (restoredByBuild.get(files.buildId) ?? 0) + 1);
39362
39454
  },
39455
+ countRestored: (files) => restoredByBuild.get(files.buildId) ?? 0,
39363
39456
  removeStale: () => {
39364
39457
  if (!existsSync9(root)) {
39365
39458
  return [];
@@ -39388,6 +39481,7 @@ function createBootTemplates(deps) {
39388
39481
  }
39389
39482
  },
39390
39483
  readLockedMib: templateLocks.readLockedMib,
39484
+ readReadyCount: () => listTemplateKeys().filter((key) => readReady(key) !== null).length,
39391
39485
  stop: async () => {
39392
39486
  await Promise.all([templateLocks.stop(), building.waitForAll()]);
39393
39487
  }
@@ -42345,11 +42439,13 @@ function makeSleepImpVm(deps) {
42345
42439
  }
42346
42440
  const sleepMs = Math.round(context.sleepTiming.now() - started);
42347
42441
  const waited = waitedMs > 0 ? `, waited ${String(waitedMs)}ms for a young guest` : "";
42442
+ const memFileMib = readDiskMib(paths.memFile);
42348
42443
  slept.detail = {
42349
42444
  trigger: reason,
42350
42445
  durationMs: sleepMs,
42351
42446
  prepareMs: Math.round(started - options.requestedAt),
42352
- steps: timings
42447
+ steps: timings,
42448
+ memFileMib
42353
42449
  };
42354
42450
  const plugged = pluggedMib === null ? "" : `, ${String(pluggedMib)} MiB plugged`;
42355
42451
  getImpdLogger("imps").with({ imp: imp.name }).info("asleep in {sleepMs}ms ({reason}){waited}, ram {ramMib} MiB{plugged}, mem file {memFile} MiB on disk, {timings}", {
@@ -42358,7 +42454,7 @@ function makeSleepImpVm(deps) {
42358
42454
  waited,
42359
42455
  ramMib: String(ramMib),
42360
42456
  plugged,
42361
- memFile: String(readDiskMib(paths.memFile)),
42457
+ memFile: String(memFileMib),
42362
42458
  timings: formatTimings(timings)
42363
42459
  });
42364
42460
  } catch (error) {
@@ -43288,18 +43384,18 @@ async function setupRestoreCommand(plan, jails) {
43288
43384
  }
43289
43385
 
43290
43386
  class TemplateRestoreError extends Error {
43291
- isTemplateFault;
43292
- constructor(message, isTemplateFault, cause) {
43387
+ fault;
43388
+ constructor(message, fault, cause) {
43293
43389
  super(message, { cause });
43294
43390
  this.name = "TemplateRestoreError";
43295
- this.isTemplateFault = isTemplateFault;
43391
+ this.fault = fault;
43296
43392
  }
43297
43393
  }
43298
43394
  async function loadTemplateVm(plan, jails, options = {}) {
43299
43395
  const marks = createMarks();
43300
43396
  const pid = await startRestoreProcess(plan, jails, options.mergeWrapper ?? null);
43301
43397
  marks.setMark("spawn");
43302
- const step = { isTemplateFault: true };
43398
+ const step = { fault: "load" };
43303
43399
  try {
43304
43400
  const api = (options.createClient ?? createFirecrackerClient)(plan.paths.apiSocket);
43305
43401
  const isUffd = plan.hugePages === "2M";
@@ -43326,14 +43422,15 @@ async function loadTemplateVm(plan, jails, options = {}) {
43326
43422
  jails.seal(plan.paths, pid);
43327
43423
  await api.resume();
43328
43424
  marks.setMark("resume");
43425
+ step.fault = "agent";
43329
43426
  await waitForAgent(plan.paths.vsockSocket, {
43330
- deadlineMs: VM_DEADLINES.agentMs,
43427
+ deadlineMs: options.parkedAgentMs ?? VM_DEADLINES.agentMs,
43331
43428
  attemptMs: 200,
43332
43429
  maxDelayMs: RESTORE_PING_DELAY_MS,
43333
43430
  isParked: true
43334
43431
  });
43335
43432
  marks.setMark("parked");
43336
- step.isTemplateFault = false;
43433
+ step.fault = null;
43337
43434
  const diskBytes = await plan.diskReady;
43338
43435
  if (plan.jail !== null) {
43339
43436
  jails.setupDiskOwner(plan.paths, plan.jail);
@@ -43368,7 +43465,7 @@ async function loadTemplateVm(plan, jails, options = {}) {
43368
43465
  await waitForExit(pid, plan.paths.apiSocket, VM_DEADLINES.killMs);
43369
43466
  await jails.release(plan.paths.impId);
43370
43467
  throw new TemplateRestoreError(`template restore failed: ${reason}
43371
- ${readLogTail(plan.paths.logFile, 5)}`, step.isTemplateFault, error);
43468
+ ${readLogTail(plan.paths.logFile, 5)}`, step.fault, error);
43372
43469
  }
43373
43470
  }
43374
43471
 
@@ -43398,6 +43495,7 @@ async function startColdVm(context, start) {
43398
43495
  const diskBytes = readDiskBytes();
43399
43496
  Promise.allSettled([diskBytes]);
43400
43497
  if (template !== null && template !== undefined) {
43498
+ const restoredBefore = context.templates?.countRestored(template) ?? 0;
43401
43499
  try {
43402
43500
  const vm = await context.vms.loadTemplateVm({
43403
43501
  firecrackerBin: context.config.firecrackerBin,
@@ -43441,8 +43539,8 @@ async function startColdVm(context, start) {
43441
43539
  key: template.key.slice(0, 12),
43442
43540
  error: getErrorMessage(error)
43443
43541
  });
43444
- const isTemplateFault = error instanceof TemplateRestoreError && error.isTemplateFault;
43445
- context.templates?.reportFailure(template, isTemplateFault);
43542
+ const fault = error instanceof TemplateRestoreError ? error.fault : null;
43543
+ context.templates?.reportFailure(template, { fault, restoredBefore });
43446
43544
  }
43447
43545
  }
43448
43546
  await diskBytes;
@@ -43476,9 +43574,19 @@ function formatGuestSteps(steps) {
43476
43574
 
43477
43575
  // packages/daemon/src/imps/imps/imp-vm-ops.ts
43478
43576
  var SLEEP_CONCURRENCY = 2;
43577
+ var RESTORE_CONCURRENCY = 4;
43479
43578
  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) {
43579
+ function createImpVmOps(host) {
43481
43580
  const sleepSlots = createSemaphore(SLEEP_CONCURRENCY);
43581
+ const wakeSlots = createSemaphore(RESTORE_CONCURRENCY);
43582
+ const templateSlots = createSemaphore(RESTORE_CONCURRENCY);
43583
+ const context = {
43584
+ ...host,
43585
+ vms: {
43586
+ ...host.vms,
43587
+ loadTemplateVm: (options) => templateSlots.run(() => host.vms.loadTemplateVm(options))
43588
+ }
43589
+ };
43482
43590
  let isStopping = false;
43483
43591
  const updateState = async (imp, change) => {
43484
43592
  const updated = await updateLockedImpState(context.db, imp, change);
@@ -43639,7 +43747,7 @@ function createImpVmOps(context) {
43639
43747
  }
43640
43748
  return imp.isDiskGrowPending ? setGrowPending(imp, false) : imp;
43641
43749
  };
43642
- const wakeImpVm = makeWakeImpVm({
43750
+ const startWake = makeWakeImpVm({
43643
43751
  context,
43644
43752
  isStopping: () => isStopping,
43645
43753
  updateState,
@@ -43648,6 +43756,7 @@ function createImpVmOps(context) {
43648
43756
  startColdImpVm,
43649
43757
  growGuestDisk
43650
43758
  });
43759
+ const wakeImpVm = (imp) => wakeSlots.run(() => startWake(imp));
43651
43760
  const requireRunningImp = async (imp) => {
43652
43761
  if (imp.state === "running") {
43653
43762
  return imp;
@@ -43703,7 +43812,7 @@ function countStepsMs(timings) {
43703
43812
  }
43704
43813
 
43705
43814
  // packages/daemon/src/imps/imps/services/service-api.ts
43706
- import { match as match9 } from "ts-pattern";
43815
+ import { match as match11 } from "ts-pattern";
43707
43816
 
43708
43817
  // packages/daemon/src/imps/imps/services/create-log-decoders.ts
43709
43818
  function createLogDecoders() {
@@ -43927,7 +44036,7 @@ function handleAgentError(service) {
43927
44036
  if (!(error instanceof AgentError)) {
43928
44037
  throw error;
43929
44038
  }
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);
44039
+ 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
44040
  };
43932
44041
  }
43933
44042
  function stopStreams(streams) {
@@ -45028,28 +45137,29 @@ function createVmRunner(jails, options = {}) {
45028
45137
  setMark("spawn");
45029
45138
  try {
45030
45139
  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
45140
+ const configure = async () => {
45141
+ await setupVm(api, {
45142
+ kernelPath: plan.kernelPath,
45143
+ bootArgs: buildBootArgs(plan),
45144
+ vcpus: plan.vcpus,
45145
+ memoryMib: plan.memoryMib,
45146
+ hugePages: plan.hugePages ?? "None",
45147
+ diskPath: plan.paths.disk,
45148
+ systemDrivePath: plan.systemDrivePath,
45149
+ vsockPath: plan.paths.vsockSocket,
45150
+ tap: plan.address.tap,
45151
+ guestMac: plan.address.guestMac
45050
45152
  });
45051
- }
45052
- const firecrackerVersion = await versionPromise;
45153
+ const regionMib = getRegionMib(plan.memoryMib, plan.maxMemoryMib);
45154
+ if (regionMib > 0) {
45155
+ await api.putHotplugMemory({
45156
+ totalSizeMib: regionMib,
45157
+ slotSizeMib: SLOT_MIB,
45158
+ blockSizeMib: BLOCK_MIB
45159
+ });
45160
+ }
45161
+ };
45162
+ const [firecrackerVersion] = await Promise.all([api.readVersion(), configure()]);
45053
45163
  setMark("configure");
45054
45164
  jails.removeUffdDevice(plan.paths.impId);
45055
45165
  jails.seal(plan.paths, pid);
@@ -46884,7 +46994,7 @@ function readSettled2(row, imp) {
46884
46994
 
46885
46995
  // packages/daemon/src/imp-ops/moves/move-sender.ts
46886
46996
  import invariant6 from "tiny-invariant";
46887
- import { match as match10 } from "ts-pattern";
46997
+ import { match as match12 } from "ts-pattern";
46888
46998
 
46889
46999
  // packages/daemon/src/platform/db/move-send-steps.ts
46890
47000
  var MOVE_SEND_STEPS = [
@@ -47447,7 +47557,7 @@ function createMoveSender(deps) {
47447
47557
  };
47448
47558
  const recoverSend = async (imp, row) => {
47449
47559
  const log = getImpdLogger("move").with({ imp: imp.name });
47450
- await match10(pickSendRecovery(imp.moveState, row?.step)).with("unmark", () => removeMark(imp)).with("wait", () => {
47560
+ await match12(pickSendRecovery(imp.moveState, row?.step)).with("unmark", () => removeMark(imp)).with("wait", () => {
47451
47561
  log.info("a prepared send waits for its send after a restart");
47452
47562
  }).with("abort", async () => {
47453
47563
  const isUndone = await resetSend(imp, row?.peer_url ?? null, row?.ticket ?? null);
@@ -47569,7 +47679,7 @@ function buildPublicError(name) {
47569
47679
  }
47570
47680
  function pickSendRecovery(moveState, step) {
47571
47681
  if (moveState === "sending") {
47572
- return match10(step).with(undefined, () => "unmark").with("prepared", () => "wait").with("streaming", () => "abort").with("sent", () => "commit").exhaustive();
47682
+ return match12(step).with(undefined, () => "unmark").with("prepared", () => "wait").with("streaming", () => "abort").with("sent", () => "commit").exhaustive();
47573
47683
  }
47574
47684
  return moveState === "moved" && step === "sent" ? "commit" : "none";
47575
47685
  }
@@ -47621,7 +47731,7 @@ function createMoveService(deps) {
47621
47731
  // packages/daemon/src/surface/moves/create-move-peer-handler.ts
47622
47732
  import { OpenAPIHandler } from "@orpc/openapi/fetch";
47623
47733
  import { ORPCError as ORPCError7, implement as implement3, onError as onError3 } from "@orpc/server";
47624
- import { match as match11 } from "ts-pattern";
47734
+ import { match as match13 } from "ts-pattern";
47625
47735
  function createMovePeerHandler(deps) {
47626
47736
  const withTicket = async (step, context, call) => {
47627
47737
  const startedAt = deps.now();
@@ -47672,7 +47782,7 @@ function createMovePeerHandler(deps) {
47672
47782
  const respondReceive = async (context) => {
47673
47783
  try {
47674
47784
  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();
47785
+ return match13(received).with({ kind: "part" }, (sent) => Response.json({ part: sent.part }, { status: 202 })).with({ kind: "receipt" }, (sent) => Response.json(sent.receipt)).exhaustive();
47676
47786
  } catch (error) {
47677
47787
  const failure = toPeerError(error);
47678
47788
  return Response.json(encodePeerError(failure), { status: failure.status });
@@ -51932,14 +52042,14 @@ function expandScopes(asked) {
51932
52042
  }
51933
52043
 
51934
52044
  // packages/daemon/src/platform/http/render-html.ts
51935
- import { P as P4, match as match12 } from "ts-pattern";
52045
+ import { P as P4, match as match14 } from "ts-pattern";
51936
52046
  var MARKUP = Symbol("markup");
51937
52047
  function renderHTML(strings, ...parts) {
51938
52048
  const html = parts.reduce((text, part, index) => `${text}${toHTML(part)}${strings[index + 1] ?? ""}`, strings[0] ?? "");
51939
52049
  return { [MARKUP]: true, html };
51940
52050
  }
51941
52051
  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();
52052
+ 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
52053
  }
51944
52054
  function escapeHTML(text) {
51945
52055
  return text.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
@@ -58167,6 +58277,11 @@ async function startImpdParts(config, boundaries, plan) {
58167
58277
  checkDnsToken: dnsToken?.check ?? null,
58168
58278
  readTailscale,
58169
58279
  readTailnetNames: tailnetNames === null ? null : tailnetNames.readStatus,
58280
+ hostReaders: {
58281
+ readKernel: () => deps.hostKernelRelease ?? release2(),
58282
+ readCpuInfo: deps.readCpuInfo ?? (() => readFileSync36("/proc/cpuinfo", "utf8")),
58283
+ readHugePagePool: deps.imps?.readHugePagePool ?? (() => readHugePagePool())
58284
+ },
58170
58285
  isReady: () => state.ready,
58171
58286
  readKsmHostStats: readKsmStats,
58172
58287
  ...deps.keepaliveMs !== undefined && { keepaliveMs: deps.keepaliveMs }
@@ -58626,15 +58741,19 @@ log tail`);
58626
58741
  loadTemplateVm: (plan) => runInGeneration(async () => {
58627
58742
  restorePlans.push(plan);
58628
58743
  const vm = await startFakeVm("restore", plan.paths).catch((error) => {
58629
- throw new TemplateRestoreError("restore failed", true, error);
58744
+ throw new TemplateRestoreError("restore failed", "load", error);
58630
58745
  });
58746
+ if (await chooseOutcome("parkedAgent") !== "ok") {
58747
+ alive.delete(vm.pid);
58748
+ throw new TemplateRestoreError("the parked agent never answered", "agent", null);
58749
+ }
58631
58750
  await plan.diskReady.catch((error) => {
58632
58751
  alive.delete(vm.pid);
58633
- throw new TemplateRestoreError("disk failed", false, error);
58752
+ throw new TemplateRestoreError("disk failed", null, error);
58634
58753
  });
58635
58754
  if (await chooseOutcome("claim") !== "ok") {
58636
58755
  alive.delete(vm.pid);
58637
- throw new TemplateRestoreError("claim failed", false, null);
58756
+ throw new TemplateRestoreError("claim failed", null, null);
58638
58757
  }
58639
58758
  restores.push({
58640
58759
  hostname: plan.claim.hostname,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@impz/testing",
3
- "version": "6.2.0",
3
+ "version": "6.3.0",
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.2.0"
46
+ "@impz/client": "6.3.0"
47
47
  },
48
48
  "engines": {
49
49
  "bun": ">=1.4.3"