@agoric/swingset-vat 0.33.0-u16.1 → 0.33.0-u17.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 (76) hide show
  1. package/README.md +1 -1
  2. package/package.json +31 -30
  3. package/src/controller/controller.js +48 -1
  4. package/src/controller/initializeKernel.js +35 -10
  5. package/src/controller/initializeSwingset.js +6 -3
  6. package/src/controller/startXSnap.js +1 -1
  7. package/src/controller/upgradeSwingset.js +212 -0
  8. package/src/devices/bridge/device-bridge.js +1 -1
  9. package/src/devices/bundle/device-bundle.js +1 -1
  10. package/src/devices/command/command.js +1 -2
  11. package/src/devices/command/device-command.js +1 -2
  12. package/src/devices/lib/deviceTools.js +1 -1
  13. package/src/devices/loopbox/device-loopbox.js +1 -1
  14. package/src/devices/loopbox/loopbox.js +1 -1
  15. package/src/devices/mailbox/device-mailbox.js +1 -2
  16. package/src/devices/mailbox/mailbox.js +1 -2
  17. package/src/devices/plugin/device-plugin.js +1 -1
  18. package/src/devices/timer/device-timer.js +1 -1
  19. package/src/devices/timer/timer.js +1 -1
  20. package/src/devices/vat-admin/device-vat-admin.js +4 -2
  21. package/src/index.js +1 -1
  22. package/src/kernel/deviceManager.js +1 -1
  23. package/src/kernel/deviceSlots.js +1 -1
  24. package/src/kernel/deviceTranslator.js +1 -1
  25. package/src/kernel/dummyMeterControl.js +1 -1
  26. package/src/kernel/gc-actions.js +55 -34
  27. package/src/kernel/kernel.js +216 -51
  28. package/src/kernel/kernelSyscall.js +2 -13
  29. package/src/kernel/parseKernelSlots.js +1 -1
  30. package/src/kernel/slogger.js +2 -2
  31. package/src/kernel/state/deviceKeeper.js +1 -1
  32. package/src/kernel/state/kernelKeeper.js +427 -81
  33. package/src/kernel/state/reachable.js +1 -1
  34. package/src/kernel/state/stats.js +1 -1
  35. package/src/kernel/state/storageHelper.js +1 -1
  36. package/src/kernel/state/vatKeeper.js +159 -44
  37. package/src/kernel/vat-admin-hooks.js +4 -1
  38. package/src/kernel/vat-loader/manager-factory.js +1 -2
  39. package/src/kernel/vat-loader/manager-helper.js +1 -1
  40. package/src/kernel/vat-loader/manager-local.js +1 -1
  41. package/src/kernel/vat-loader/manager-subprocess-node.js +1 -1
  42. package/src/kernel/vat-loader/manager-subprocess-xsnap.js +1 -1
  43. package/src/kernel/vat-loader/vat-loader.js +2 -2
  44. package/src/kernel/vat-warehouse.js +5 -1
  45. package/src/kernel/vatTranslator.js +1 -4
  46. package/src/lib/assertOptions.js +1 -1
  47. package/src/lib/capdata.js +1 -1
  48. package/src/lib/id.js +1 -1
  49. package/src/lib/message.js +1 -1
  50. package/src/lib/parseVatSlots.js +1 -1
  51. package/src/lib/recordVatOptions.js +18 -6
  52. package/src/lib/runPolicies.js +50 -4
  53. package/src/lib/storageAPI.js +1 -1
  54. package/src/lib/workerOptions.js +1 -1
  55. package/src/supervisors/subprocess-node/supervisor-subprocess-node.js +1 -1
  56. package/src/types-external.js +71 -22
  57. package/src/types-internal.js +56 -3
  58. package/src/vats/comms/clist-inbound.js +1 -1
  59. package/src/vats/comms/clist-kernel.js +1 -1
  60. package/src/vats/comms/clist-outbound.js +1 -1
  61. package/src/vats/comms/controller.js +1 -1
  62. package/src/vats/comms/delivery.js +1 -1
  63. package/src/vats/comms/dispatch.js +1 -1
  64. package/src/vats/comms/gc-comms.js +1 -1
  65. package/src/vats/comms/parseLocalSlots.js +1 -1
  66. package/src/vats/comms/parseRemoteSlot.js +1 -1
  67. package/src/vats/comms/remote.js +1 -1
  68. package/src/vats/comms/state.js +1 -1
  69. package/src/vats/timer/vat-timer.js +9 -9
  70. package/src/vats/vat-admin/vat-vat-admin.js +23 -8
  71. package/src/vats/vattp/vat-vattp.js +1 -1
  72. package/tools/bootstrap-relay.js +1 -3
  73. package/tools/bundleTool.js +9 -1
  74. package/tools/dvo-test-harness.js +1 -1
  75. package/tools/manual-timer.js +1 -1
  76. package/tools/run-utils.js +1 -1
@@ -1,8 +1,10 @@
1
1
  /* global globalThis */
2
2
 
3
- import { assert, Fail } from '@agoric/assert';
3
+ import { assert, Fail } from '@endo/errors';
4
4
  import { isNat } from '@endo/nat';
5
+ import { mustMatch, M } from '@endo/patterns';
5
6
  import { importBundle } from '@endo/import-bundle';
7
+ import { objectMetaMap, PromiseAllOrErrors } from '@agoric/internal';
6
8
  import { makeUpgradeDisconnection } from '@agoric/internal/src/upgrade-api.js';
7
9
  import { kser, kslot, makeError } from '@agoric/kmarshal';
8
10
  import { assertKnownOptions } from '../lib/assertOptions.js';
@@ -10,7 +12,9 @@ import { foreverPolicy } from '../lib/runPolicies.js';
10
12
  import { makeVatManagerFactory } from './vat-loader/manager-factory.js';
11
13
  import { makeVatWarehouse } from './vat-warehouse.js';
12
14
  import makeDeviceManager from './deviceManager.js';
13
- import makeKernelKeeper from './state/kernelKeeper.js';
15
+ import makeKernelKeeper, {
16
+ CURRENT_SCHEMA_VERSION,
17
+ } from './state/kernelKeeper.js';
14
18
  import {
15
19
  kdebug,
16
20
  kdebugEnable,
@@ -36,6 +40,7 @@ import { notifyTermination } from './notifyTermination.js';
36
40
  import { makeVatAdminHooks } from './vat-admin-hooks.js';
37
41
 
38
42
  /** @import * as liveslots from '@agoric/swingset-liveslots' */
43
+ /** @import {PolicyInputCleanupCounts} from '../types-external.js' */
39
44
 
40
45
  function abbreviateReplacer(_, arg) {
41
46
  if (typeof arg === 'bigint') {
@@ -112,7 +117,11 @@ export default function buildKernel(
112
117
  ? makeSlogger(slogCallbacks, writeSlogObject)
113
118
  : makeDummySlogger(slogCallbacks, makeConsole('disabled slogger'));
114
119
 
115
- const kernelKeeper = makeKernelKeeper(kernelStorage, kernelSlog);
120
+ const kernelKeeper = makeKernelKeeper(
121
+ kernelStorage,
122
+ CURRENT_SCHEMA_VERSION,
123
+ kernelSlog,
124
+ );
116
125
 
117
126
  /** @type {ReturnType<makeVatWarehouse>} */
118
127
  let vatWarehouse;
@@ -252,9 +261,12 @@ export default function buildKernel(
252
261
  */
253
262
  async function terminateVat(vatID, shouldReject, info) {
254
263
  console.log(`kernel terminating vat ${vatID} (failure=${shouldReject})`);
255
- const vatKeeper = kernelKeeper.provideVatKeeper(vatID);
256
- const critical = vatKeeper.getOptions().critical;
257
264
  insistCapData(info);
265
+ // Note that it's important for much of this work to happen within the
266
+ // synchronous prelude. For details, see
267
+ // https://github.com/Agoric/agoric-sdk/pull/10055#discussion_r1754918394
268
+ let critical = false;
269
+ const deferred = [];
258
270
  // ISSUE: terminate stuff in its own crank like creation?
259
271
  // TODO: if a static vat terminates, panic the kernel?
260
272
  // TODO: guard against somebody telling vatAdmin to kill a vat twice
@@ -264,10 +276,22 @@ export default function buildKernel(
264
276
  // check will report 'false'. That's fine, there's no state to
265
277
  // clean up.
266
278
  if (kernelKeeper.vatIsAlive(vatID)) {
279
+ // If there was no vat state, we can't make a vatKeeper to ask for
280
+ // options. For now, pretend the vat was non-critical. This will fail
281
+ // to panic the kernel for startVat failures in critical vats
282
+ // (#9157). The fix will add .critical to CrankResults, populated by a
283
+ // getOptions query in deliveryCrankResults() or copied from
284
+ // dynamicOptions in processCreateVat.
285
+ const vatKeeper = kernelKeeper.provideVatKeeper(vatID);
286
+ critical = vatKeeper.getOptions().critical;
287
+
267
288
  // Reject all promises decided by the vat, making sure to capture the list
268
289
  // of kpids before that data is deleted.
269
290
  const deadPromises = [...kernelKeeper.enumeratePromisesByDecider(vatID)];
270
- kernelKeeper.cleanupAfterTerminatedVat(vatID);
291
+ // remove vatID from the list of live vats, and mark for deletion
292
+ kernelKeeper.deleteVatID(vatID);
293
+ kernelKeeper.markVatAsTerminated(vatID);
294
+ deferred.push(kernelKeeper.removeVatFromSwingStoreExports(vatID));
271
295
  for (const kpid of deadPromises) {
272
296
  resolveToError(kpid, makeError('vat terminated'), vatID);
273
297
  }
@@ -282,9 +306,7 @@ export default function buildKernel(
282
306
  // it's going to be a small cost compared to the trouble you're probably
283
307
  // already in anyway if this happens.
284
308
  panic(`critical vat ${vatID} failed`, Error(info.body));
285
- return;
286
- }
287
- if (vatAdminRootKref) {
309
+ } else if (vatAdminRootKref) {
288
310
  // static vat termination can happen before vat admin vat exists
289
311
  notifyTermination(
290
312
  vatID,
@@ -299,8 +321,11 @@ export default function buildKernel(
299
321
  );
300
322
  }
301
323
 
302
- // worker needs to be stopped, if any
303
- await vatWarehouse.stopWorker(vatID);
324
+ // worker, if present, needs to be stopped
325
+ // (note that this only applies to ephemeral vats)
326
+ deferred.push(vatWarehouse.stopWorker(vatID));
327
+
328
+ await PromiseAllOrErrors(deferred);
304
329
  }
305
330
 
306
331
  function notifyMeterThreshold(meterID) {
@@ -357,6 +382,7 @@ export default function buildKernel(
357
382
  *
358
383
  * @typedef { import('@agoric/swingset-liveslots').MeterConsumption } MeterConsumption
359
384
  * @typedef { import('../types-internal.js').MeterID } MeterID
385
+ * @typedef { import('../types-internal.js').Dirt } Dirt
360
386
  *
361
387
  * Any delivery crank (send, notify, start-vat.. anything which is allowed
362
388
  * to make vat delivery) emits one of these status events if a delivery
@@ -369,13 +395,15 @@ export default function buildKernel(
369
395
  * illegalSyscall: { vatID: VatID, info: SwingSetCapData } | undefined,
370
396
  * vatRequestedTermination: { reject: boolean, info: SwingSetCapData } | undefined,
371
397
  * } } DeliveryStatus
398
+ * @import {PolicyInputCleanupCounts} from '../types-external.js';
372
399
  * @typedef { {
373
400
  * abort?: boolean, // changes should be discarded, not committed
374
401
  * consumeMessage?: boolean, // discard the aborted delivery
375
402
  * didDelivery?: VatID, // we made a delivery to a vat, for run policy and save-snapshot
376
- * computrons?: BigInt, // computron count for run policy
403
+ * computrons?: bigint, // computron count for run policy
404
+ * cleanups?: PolicyInputCleanupCounts, // cleanup budget spent
377
405
  * meterID?: string, // deduct those computrons from a meter
378
- * decrementReapCount?: { vatID: VatID }, // the reap counter should decrement
406
+ * measureDirt?: { vatID: VatID, dirt: Dirt }, // dirt counters should increment
379
407
  * terminate?: { vatID: VatID, reject: boolean, info: SwingSetCapData }, // terminate vat, notify vat-admin
380
408
  * vatAdminMethargs?: RawMethargs, // methargs to notify vat-admin about create/upgrade results
381
409
  * } } CrankResults
@@ -442,16 +470,17 @@ export default function buildKernel(
442
470
  * event handler.
443
471
  *
444
472
  * Two flags influence this:
445
- * `decrementReapCount` is used for deliveries that run userspace code
473
+ * `measureDirt` is used for non-BOYD deliveries
446
474
  * `meterID` means we should check a meter
447
475
  *
448
476
  * @param {VatID} vatID
449
477
  * @param {DeliveryStatus} status
450
- * @param {boolean} decrementReapCount
478
+ * @param {boolean} measureDirt
451
479
  * @param {MeterID} [meterID]
480
+ * @param {number} [gcKrefs]
452
481
  * @returns {CrankResults}
453
482
  */
454
- function deliveryCrankResults(vatID, status, decrementReapCount, meterID) {
483
+ function deliveryCrankResults(vatID, status, measureDirt, meterID, gcKrefs) {
455
484
  let meterUnderrun = false;
456
485
  let computrons;
457
486
  if (status.metering?.compute) {
@@ -495,8 +524,16 @@ export default function buildKernel(
495
524
  results.terminate = { vatID, ...status.vatRequestedTermination };
496
525
  }
497
526
 
498
- if (decrementReapCount && !(results.abort || results.terminate)) {
499
- results.decrementReapCount = { vatID };
527
+ if (measureDirt && !(results.abort || results.terminate)) {
528
+ const dirt = { deliveries: 1 };
529
+ if (computrons) {
530
+ // this is BigInt, but we use plain Number in Dirt records
531
+ dirt.computrons = Number(computrons);
532
+ }
533
+ if (gcKrefs) {
534
+ dirt.gcKrefs = gcKrefs;
535
+ }
536
+ results.measureDirt = { vatID, dirt };
500
537
  }
501
538
 
502
539
  // We leave results.consumeMessage up to the caller. Send failures
@@ -535,7 +572,8 @@ export default function buildKernel(
535
572
  }
536
573
 
537
574
  const status = await deliverAndLogToVat(vatID, kd, vd);
538
- return deliveryCrankResults(vatID, status, true, meterID);
575
+ const gcKrefs = undefined; // TODO maybe increase by number of vrefs in args?
576
+ return deliveryCrankResults(vatID, status, true, meterID, gcKrefs);
539
577
  }
540
578
 
541
579
  /**
@@ -581,7 +619,8 @@ export default function buildKernel(
581
619
  const vd = vatWarehouse.kernelDeliveryToVatDelivery(vatID, kd);
582
620
  vatKeeper.deleteCListEntriesForKernelSlots(targets);
583
621
  const status = await deliverAndLogToVat(vatID, kd, vd);
584
- return deliveryCrankResults(vatID, status, true, meterID);
622
+ const gcKrefs = undefined; // TODO maybe increase by number of vrefs in args?
623
+ return deliveryCrankResults(vatID, status, true, meterID, gcKrefs);
585
624
  }
586
625
 
587
626
  /**
@@ -609,7 +648,9 @@ export default function buildKernel(
609
648
  }
610
649
  const vd = vatWarehouse.kernelDeliveryToVatDelivery(vatID, kd);
611
650
  const status = await deliverAndLogToVat(vatID, kd, vd);
612
- return deliveryCrankResults(vatID, status, false); // no meterID
651
+ const meterID = undefined; // no meterID
652
+ const gcKrefs = krefs.length;
653
+ return deliveryCrankResults(vatID, status, true, meterID, gcKrefs);
613
654
  }
614
655
 
615
656
  /**
@@ -628,7 +669,50 @@ export default function buildKernel(
628
669
  const kd = harden([type]);
629
670
  const vd = vatWarehouse.kernelDeliveryToVatDelivery(vatID, kd);
630
671
  const status = await deliverAndLogToVat(vatID, kd, vd);
631
- return deliveryCrankResults(vatID, status, false); // no meter
672
+ // no gcKrefs, BOYD clears them anyways
673
+ return deliveryCrankResults(vatID, status, false); // no meter, BOYD clears dirt
674
+ }
675
+
676
+ /**
677
+ * Perform a small (budget-limited) amount of dead-vat cleanup work.
678
+ *
679
+ * @param {RunQueueEventCleanupTerminatedVat} message
680
+ * 'message' is the run-queue cleanup action, which includes a
681
+ * vatID and budget. The budget contains work limits for each
682
+ * phase of cleanup (perhaps Infinity to allow unlimited
683
+ * work). Cleanup should not touch more than maybe 5*limit DB
684
+ * rows.
685
+ * @returns {Promise<CrankResults>}
686
+ */
687
+ async function processCleanupTerminatedVat(message) {
688
+ const { vatID, budget } = message;
689
+ const { done, work } = kernelKeeper.cleanupAfterTerminatedVat(
690
+ vatID,
691
+ budget,
692
+ );
693
+ const zeroFreeWorkCounts = objectMetaMap(work, desc =>
694
+ desc.value ? desc : undefined,
695
+ );
696
+ kernelSlog.write({ type: 'vat-cleanup', vatID, work: zeroFreeWorkCounts });
697
+
698
+ /** @type {PolicyInputCleanupCounts} */
699
+ const cleanups = {
700
+ total:
701
+ work.exports +
702
+ work.imports +
703
+ work.kv +
704
+ work.snapshots +
705
+ work.transcripts,
706
+ ...work,
707
+ };
708
+ if (done) {
709
+ kernelKeeper.forgetTerminatedVat(vatID);
710
+ kernelSlog.write({ type: 'vat-cleanup-complete', vatID });
711
+ }
712
+ // We don't perform any deliveries here, so there are no computrons to
713
+ // report, but we do tell the runPolicy know how much kernel-side DB
714
+ // work we did, so it can decide how much was too much.
715
+ return harden({ computrons: 0n, cleanups });
632
716
  }
633
717
 
634
718
  /**
@@ -669,8 +753,9 @@ export default function buildKernel(
669
753
  const status = await deliverAndLogToVat(vatID, kd, vd);
670
754
  // note: if deliveryCrankResults() learns to suspend vats,
671
755
  // startVat errors should still terminate them
756
+ const gcKrefs = undefined; // TODO maybe increase by number of vrefs in args?
672
757
  const results = harden({
673
- ...deliveryCrankResults(vatID, status, true, meterID),
758
+ ...deliveryCrankResults(vatID, status, true, meterID, gcKrefs),
674
759
  consumeMessage: true,
675
760
  });
676
761
  return results;
@@ -735,9 +820,17 @@ export default function buildKernel(
735
820
  function setKernelVatOption(vatID, option, value) {
736
821
  switch (option) {
737
822
  case 'reapInterval': {
823
+ // This still controls reapDirtThreshold.deliveries, and we do not
824
+ // yet offer controls for the other limits (gcKrefs or computrons).
738
825
  if (value === 'never' || isNat(value)) {
739
826
  const vatKeeper = kernelKeeper.provideVatKeeper(vatID);
740
- vatKeeper.updateReapInterval(value);
827
+ const threshold = { ...vatKeeper.getReapDirtThreshold() };
828
+ if (value === 'never') {
829
+ threshold.deliveries = value;
830
+ } else {
831
+ threshold.deliveries = Number(value);
832
+ }
833
+ vatKeeper.setReapDirtThreshold(threshold);
741
834
  } else {
742
835
  console.log(`WARNING: invalid reapInterval value`, value);
743
836
  }
@@ -911,10 +1004,7 @@ export default function buildKernel(
911
1004
  const abandonedObjects = [
912
1005
  ...kernelKeeper.enumerateNonDurableObjectExports(vatID),
913
1006
  ];
914
- for (const { kref, vref } of abandonedObjects) {
915
- /** @see translateAbandonExports in {@link ./vatTranslator.js} */
916
- vatKeeper.deleteCListEntry(kref, vref);
917
- /** @see abandonExports in {@link ./kernelSyscall.js} */
1007
+ for (const { kref } of abandonedObjects) {
918
1008
  kernelKeeper.orphanKernelObject(kref, vatID);
919
1009
  }
920
1010
 
@@ -951,7 +1041,14 @@ export default function buildKernel(
951
1041
  startVatKD,
952
1042
  startVatVD,
953
1043
  );
954
- const startVatResults = deliveryCrankResults(vatID, startVatStatus, false);
1044
+ const gcKrefs = undefined; // TODO maybe increase by number of vrefs in args?
1045
+ const startVatResults = deliveryCrankResults(
1046
+ vatID,
1047
+ startVatStatus,
1048
+ true,
1049
+ meterID,
1050
+ gcKrefs,
1051
+ );
955
1052
  computrons = addComputrons(computrons, startVatResults.computrons);
956
1053
 
957
1054
  if (startVatResults.terminate) {
@@ -1125,6 +1222,7 @@ export default function buildKernel(
1125
1222
  * @typedef { import('../types-internal.js').RunQueueEventRetireImports } RunQueueEventRetireImports
1126
1223
  * @typedef { import('../types-internal.js').RunQueueEventNegatedGCAction } RunQueueEventNegatedGCAction
1127
1224
  * @typedef { import('../types-internal.js').RunQueueEventBringOutYourDead } RunQueueEventBringOutYourDead
1225
+ * @typedef { import('../types-internal.js').RunQueueEventCleanupTerminatedVat } RunQueueEventCleanupTerminatedVat
1128
1226
  * @typedef { import('../types-internal.js').RunQueueEvent } RunQueueEvent
1129
1227
  */
1130
1228
 
@@ -1192,6 +1290,8 @@ export default function buildKernel(
1192
1290
  } else if (message.type === 'negated-gc-action') {
1193
1291
  // processGCActionSet pruned some negated actions, but had no GC
1194
1292
  // action to perform. Record the DB changes in their own crank.
1293
+ } else if (message.type === 'cleanup-terminated-vat') {
1294
+ deliverP = processCleanupTerminatedVat(message);
1195
1295
  } else if (gcMessages.includes(message.type)) {
1196
1296
  deliverP = processGCMessage(message);
1197
1297
  } else {
@@ -1242,18 +1342,13 @@ export default function buildKernel(
1242
1342
  const crankResults = await deliverRunQueueEvent(message);
1243
1343
  // { abort/commit, deduct, terminate+notify, consumeMessage }
1244
1344
 
1245
- if (crankResults.didDelivery) {
1246
- if (message.type === 'create-vat') {
1247
- // TODO: create-vat now gets metering, at least for the
1248
- // dispatch.startVat . We should probably tell the policy about
1249
- // the creation too since there's extra overhead (we're
1250
- // launching a new child process, at least, although that
1251
- // sometimes happens randomly because of vat eviction policy
1252
- // which should not affect the in-consensus policyInput)
1253
- policyInput = ['create-vat', {}];
1254
- } else {
1255
- policyInput = ['crank', {}];
1256
- }
1345
+ if (message.type === 'cleanup-terminated-vat') {
1346
+ const { cleanups } = crankResults;
1347
+ assert(cleanups !== undefined);
1348
+ policyInput = ['cleanup', { cleanups }];
1349
+ } else if (crankResults.didDelivery) {
1350
+ const tag = message.type === 'create-vat' ? 'create-vat' : 'crank';
1351
+ policyInput = [tag, {}];
1257
1352
  }
1258
1353
 
1259
1354
  // Deliveries cause syscalls, syscalls might cause errors
@@ -1292,13 +1387,11 @@ export default function buildKernel(
1292
1387
  }
1293
1388
  }
1294
1389
  }
1295
- if (crankResults.decrementReapCount) {
1390
+ if (crankResults.measureDirt) {
1296
1391
  // deliveries cause garbage, garbage needs collection
1297
- const { vatID } = crankResults.decrementReapCount;
1392
+ const { vatID, dirt } = crankResults.measureDirt;
1298
1393
  const vatKeeper = kernelKeeper.provideVatKeeper(vatID);
1299
- if (vatKeeper.countdownToReap()) {
1300
- kernelKeeper.scheduleReap(vatID);
1301
- }
1394
+ vatKeeper.addDirt(dirt); // might schedule a reap for that vat
1302
1395
  }
1303
1396
 
1304
1397
  // Vat termination (during delivery) is triggered by an illegal
@@ -1572,10 +1665,14 @@ export default function buildKernel(
1572
1665
  'bundleID',
1573
1666
  'enablePipelining',
1574
1667
  'reapInterval',
1668
+ 'reapGCKrefs',
1669
+ 'neverReap',
1575
1670
  ]);
1576
1671
  const {
1577
1672
  bundleID = 'b1-00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000',
1578
1673
  reapInterval = 'never',
1674
+ reapGCKrefs = 'never',
1675
+ neverReap = false,
1579
1676
  enablePipelining,
1580
1677
  } = creationOptions;
1581
1678
  const vatID = kernelKeeper.allocateVatIDForNameIfNeeded(name);
@@ -1587,6 +1684,8 @@ export default function buildKernel(
1587
1684
  const options = {
1588
1685
  name,
1589
1686
  reapInterval,
1687
+ reapGCKrefs,
1688
+ neverReap,
1590
1689
  enablePipelining,
1591
1690
  managerType,
1592
1691
  };
@@ -1617,7 +1716,6 @@ export default function buildKernel(
1617
1716
  throw Error('kernel.start already called');
1618
1717
  }
1619
1718
  started = true;
1620
- kernelKeeper.getInitialized() || Fail`kernel not initialized`;
1621
1719
 
1622
1720
  kernelKeeper.loadStats();
1623
1721
 
@@ -1708,16 +1806,38 @@ export default function buildKernel(
1708
1806
  }
1709
1807
  }
1710
1808
 
1809
+ // match return value of runPolicy.allowCleanup, which is
1810
+ // PolicyOutputCleanupBudget | true | false
1811
+ const allowCleanupShape = M.or(
1812
+ // 'false' will prohibit cleanup
1813
+ false,
1814
+ // 'true' will allow unlimited cleanup
1815
+ true,
1816
+ // otherwise allow cleanup, optionally with a limiting budget
1817
+ M.splitRecord(
1818
+ { default: M.number() },
1819
+ {
1820
+ exports: M.number(),
1821
+ imports: M.number(),
1822
+ kv: M.number(),
1823
+ snapshots: M.number(),
1824
+ transcripts: M.number(),
1825
+ },
1826
+ M.record(),
1827
+ ),
1828
+ );
1829
+
1711
1830
  /**
1712
1831
  * Pulls the next message from the highest-priority queue and returns it
1713
1832
  * along with a corresponding processor.
1714
1833
  *
1834
+ * @param {RunPolicy} [policy] - a RunPolicy to limit the work being done
1715
1835
  * @returns {{
1716
1836
  * message: RunQueueEvent | undefined,
1717
1837
  * processor: (message: RunQueueEvent) => Promise<PolicyInput>,
1718
1838
  * }}
1719
1839
  */
1720
- function getNextMessageAndProcessor() {
1840
+ function getNextMessageAndProcessor(policy) {
1721
1841
  const acceptanceMessage = kernelKeeper.getNextAcceptanceQueueMsg();
1722
1842
  if (acceptanceMessage) {
1723
1843
  return {
@@ -1725,7 +1845,12 @@ export default function buildKernel(
1725
1845
  processor: processAcceptanceMessage,
1726
1846
  };
1727
1847
  }
1848
+ // Absent specific configuration, allow unlimited cleanup.
1849
+ const allowCleanup = policy?.allowCleanup?.() ?? true;
1850
+ mustMatch(harden(allowCleanup), allowCleanupShape);
1851
+
1728
1852
  const message =
1853
+ kernelKeeper.nextCleanupTerminatedVatAction(allowCleanup) ||
1729
1854
  processGCActionSet(kernelKeeper) ||
1730
1855
  kernelKeeper.nextReapAction() ||
1731
1856
  kernelKeeper.getNextRunQueueMsg();
@@ -1733,14 +1858,36 @@ export default function buildKernel(
1733
1858
  }
1734
1859
 
1735
1860
  function changeKernelOptions(options) {
1736
- assertKnownOptions(options, ['defaultReapInterval', 'snapshotInterval']);
1861
+ assertKnownOptions(options, [
1862
+ 'defaultReapInterval',
1863
+ 'defaultReapGCKrefs',
1864
+ 'snapshotInterval',
1865
+ ]);
1737
1866
  kernelKeeper.startCrank();
1738
1867
  try {
1739
1868
  for (const option of Object.getOwnPropertyNames(options)) {
1740
1869
  const value = options[option];
1741
1870
  switch (option) {
1742
1871
  case 'defaultReapInterval': {
1743
- kernelKeeper.setDefaultReapInterval(value);
1872
+ assert(
1873
+ (typeof value === 'number' && value > 0) || value === 'never',
1874
+ `defaultReapInterval ${value} must be a positive number or "never"`,
1875
+ );
1876
+ kernelKeeper.setDefaultReapDirtThreshold({
1877
+ ...kernelKeeper.getDefaultReapDirtThreshold(),
1878
+ deliveries: value,
1879
+ });
1880
+ break;
1881
+ }
1882
+ case 'defaultReapGCKrefs': {
1883
+ assert(
1884
+ (typeof value === 'number' && value > 0) || value === 'never',
1885
+ `defaultReapGCKrefs ${value} must be a positive number or "never"`,
1886
+ );
1887
+ kernelKeeper.setDefaultReapDirtThreshold({
1888
+ ...kernelKeeper.getDefaultReapDirtThreshold(),
1889
+ gcKrefs: value,
1890
+ });
1744
1891
  break;
1745
1892
  }
1746
1893
  case 'snapshotInterval': {
@@ -1813,7 +1960,7 @@ export default function buildKernel(
1813
1960
  kernelKeeper.startCrank();
1814
1961
  try {
1815
1962
  kernelKeeper.establishCrankSavepoint('start');
1816
- const { processor, message } = getNextMessageAndProcessor();
1963
+ const { processor, message } = getNextMessageAndProcessor(policy);
1817
1964
  if (!message) {
1818
1965
  break;
1819
1966
  }
@@ -1835,6 +1982,13 @@ export default function buildKernel(
1835
1982
  case 'crank-failed':
1836
1983
  policyOutput = policy.crankFailed(policyInput[1]);
1837
1984
  break;
1985
+ case 'cleanup': {
1986
+ // Give the policy a chance to interrupt kernel execution,
1987
+ // but default to continuing.
1988
+ const { didCleanup } = policy;
1989
+ policyOutput = didCleanup ? didCleanup(policyInput[1]) : true;
1990
+ break;
1991
+ }
1838
1992
  case 'none':
1839
1993
  policyOutput = policy.emptyCrank();
1840
1994
  break;
@@ -1944,6 +2098,16 @@ export default function buildKernel(
1944
2098
  hooks[hookName] = hook;
1945
2099
  }
1946
2100
 
2101
+ function terminateVatExternally(vatID, reasonCD) {
2102
+ assert(started, 'must do kernel.start() before terminateVatExternally()');
2103
+ insistCapData(reasonCD);
2104
+ assert(reasonCD.slots.length === 0, 'no slots allowed in reason');
2105
+ // this fires a promise when worker is dead, mostly for tests, so don't
2106
+ // give it to external callers
2107
+ void terminateVat(vatID, true, reasonCD);
2108
+ console.log(`scheduled vatID ${vatID} for termination`);
2109
+ }
2110
+
1947
2111
  const kernel = harden({
1948
2112
  // these are meant for the controller
1949
2113
  installBundle,
@@ -2019,6 +2183,7 @@ export default function buildKernel(
2019
2183
  kpStatus,
2020
2184
  kpResolution,
2021
2185
  addDeviceHook,
2186
+ terminateVatExternally,
2022
2187
  });
2023
2188
 
2024
2189
  return kernel;
@@ -1,4 +1,4 @@
1
- import { assert, Fail } from '@agoric/assert';
1
+ import { assert, Fail } from '@endo/errors';
2
2
  import { insistKernelType } from './parseKernelSlots.js';
3
3
  import { insistCapData } from '../lib/capdata.js';
4
4
  import { insistDeviceID, insistVatID } from '../lib/id.js';
@@ -178,24 +178,13 @@ export function makeKernelSyscallHandler(tools) {
178
178
 
179
179
  function retireExports(koids) {
180
180
  Array.isArray(koids) || Fail`retireExports given non-Array ${koids}`;
181
- const newActions = [];
182
- for (const koid of koids) {
183
- const importers = kernelKeeper.getImporters(koid);
184
- for (const vatID of importers) {
185
- newActions.push(`${vatID} retireImport ${koid}`);
186
- }
187
- // TODO: decref and delete any #2069 auxdata
188
- kernelKeeper.deleteKernelObject(koid);
189
- }
190
- kernelKeeper.addGCActions(newActions);
181
+ kernelKeeper.retireKernelObjects(koids);
191
182
  return OKNULL;
192
183
  }
193
184
 
194
185
  function abandonExports(vatID, koids) {
195
186
  Array.isArray(koids) || Fail`abandonExports given non-Array ${koids}`;
196
187
  for (const koid of koids) {
197
- // note that this is effectful and also performed outside of a syscall
198
- // by processUpgradeVat in {@link ./kernel.js}
199
188
  kernelKeeper.orphanKernelObject(koid, vatID);
200
189
  }
201
190
  return OKNULL;
@@ -1,5 +1,5 @@
1
1
  import { Nat } from '@endo/nat';
2
- import { assert, Fail } from '@agoric/assert';
2
+ import { assert, Fail } from '@endo/errors';
3
3
 
4
4
  // Object/promise references (in the kernel) contain a two-tuple of (type,
5
5
  // index). All object references point to entries in the kernel Object
@@ -1,4 +1,4 @@
1
- import { quote } from '@agoric/assert';
1
+ import { q } from '@endo/errors';
2
2
 
3
3
  const IDLE = 'idle';
4
4
  const STARTUP = 'startup';
@@ -66,7 +66,7 @@ function makeCallbackRegistry(callbacks) {
66
66
  if (!cbNames.length) {
67
67
  return;
68
68
  }
69
- console.warn(errorUnusedMsg, cbNames.map(quote).sort().join(', '));
69
+ console.warn(errorUnusedMsg, cbNames.map(q).sort().join(', '));
70
70
  },
71
71
  });
72
72
  }
@@ -3,7 +3,7 @@
3
3
  */
4
4
 
5
5
  import { Nat } from '@endo/nat';
6
- import { assert, Fail } from '@agoric/assert';
6
+ import { assert, Fail } from '@endo/errors';
7
7
  import { parseKernelSlot } from '../parseKernelSlots.js';
8
8
  import { makeVatSlot, parseVatSlot } from '../../lib/parseVatSlots.js';
9
9
  import { insistDeviceID } from '../../lib/id.js';