@agoric/swingset-vat 0.33.0-u16.0 → 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,6 +1,11 @@
1
+ /* eslint-disable no-use-before-define */
1
2
  import { Nat, isNat } from '@endo/nat';
2
- import { assert, Fail } from '@agoric/assert';
3
- import { initializeVatState, makeVatKeeper } from './vatKeeper.js';
3
+ import { assert, Fail } from '@endo/errors';
4
+ import {
5
+ initializeVatState,
6
+ makeVatKeeper,
7
+ DEFAULT_REAP_DIRT_THRESHOLD_KEY,
8
+ } from './vatKeeper.js';
4
9
  import { initializeDeviceState, makeDeviceKeeper } from './deviceKeeper.js';
5
10
  import { parseReachableAndVatSlot } from './reachable.js';
6
11
  import { insistStorageAPI } from '../../lib/storageAPI.js';
@@ -33,14 +38,22 @@ const enableKernelGC = true;
33
38
  * @typedef { import('../../types-external.js').BundleCap } BundleCap
34
39
  * @typedef { import('../../types-external.js').BundleID } BundleID
35
40
  * @typedef { import('../../types-external.js').EndoZipBase64Bundle } EndoZipBase64Bundle
36
- * @typedef { import('../../types-external.js').KernelOptions } KernelOptions
37
41
  * @typedef { import('../../types-external.js').KernelSlog } KernelSlog
38
42
  * @typedef { import('../../types-external.js').ManagerType } ManagerType
39
43
  * @typedef { import('../../types-external.js').SnapStore } SnapStore
40
44
  * @typedef { import('../../types-external.js').TranscriptStore } TranscriptStore
41
45
  * @typedef { import('../../types-external.js').VatKeeper } VatKeeper
46
+ * @typedef { import('../../types-internal.js').InternalKernelOptions } InternalKernelOptions
47
+ * @typedef { import('../../types-internal.js').ReapDirtThreshold } ReapDirtThreshold
48
+ * @import {CleanupBudget, CleanupWork, PolicyOutputCleanupBudget} from '../../types-external.js';
49
+ * @import {RunQueueEventCleanupTerminatedVat} from '../../types-internal.js';
42
50
  */
43
51
 
52
+ export { DEFAULT_REAP_DIRT_THRESHOLD_KEY };
53
+
54
+ // most recent DB schema version
55
+ export const CURRENT_SCHEMA_VERSION = 2;
56
+
44
57
  // Kernel state lives in a key-value store supporting key retrieval by
45
58
  // lexicographic range. All keys and values are strings.
46
59
  // We simulate a tree by concatenating path-name components with ".". When we
@@ -52,13 +65,21 @@ const enableKernelGC = true;
52
65
  // allowed to vary between instances in a consensus machine. Everything else
53
66
  // is required to be deterministic.
54
67
  //
55
- // The schema is:
56
68
  //
69
+ // The schema is indicated by the value of the "version" key, which
70
+ // was added for version 1 (i.e., version 0 had no such key), and is
71
+ // only modified by a call to upgradeSwingset(). See below for
72
+ // deltas/upgrades from one version to the next.
73
+ //
74
+ // The current ("v2") schema keys/values are:
75
+ //
76
+ // version = '2'
57
77
  // vat.names = JSON([names..])
58
78
  // vat.dynamicIDs = JSON([vatIDs..])
59
79
  // vat.name.$NAME = $vatID = v$NN
60
80
  // vat.nextID = $NN
61
81
  // vat.nextUpgradeID = $NN
82
+ // vats.terminated = JSON([vatIDs..])
62
83
  // device.names = JSON([names..])
63
84
  // device.name.$NAME = $deviceID = d$NN
64
85
  // device.nextID = $NN
@@ -68,13 +89,22 @@ const enableKernelGC = true;
68
89
  // bundle.$BUNDLEID = JSON(bundle)
69
90
  //
70
91
  // kernel.defaultManagerType = managerType
71
- // kernel.defaultReapInterval = $NN
92
+ // (old) kernel.defaultReapInterval = $NN
93
+ // kernel.defaultReapDirtThreshold = JSON({ thresholds })
94
+ // thresholds (all optional)
95
+ // deliveries: number or 'never' (default)
96
+ // gcKrefs: number or 'never' (default)
97
+ // computrons: number or 'never' (default)
98
+ // never: boolean (default false)
72
99
  // kernel.relaxDurabilityRules = missing | 'true'
73
100
  // kernel.snapshotInitial = $NN
74
101
  // kernel.snapshotInterval = $NN
75
102
 
76
103
  // v$NN.source = JSON({ bundle }) or JSON({ bundleName })
77
- // v$NN.options = JSON
104
+ // v$NN.options = JSON , options include:
105
+ // .reapDirtThreshold = JSON({ thresholds })
106
+ // thresholds (all optional, default to kernel-wide defaultReapDirtThreshold)
107
+ // (leave room for .snapshotDirtThreshold for #6786)
78
108
  // v$NN.o.nextID = $NN
79
109
  // v$NN.p.nextID = $NN
80
110
  // v$NN.d.nextID = $NN
@@ -83,9 +113,10 @@ const enableKernelGC = true;
83
113
  // $vatSlot is one of: o+$NN/o-$NN/p+$NN/p-$NN/d+$NN/d-$NN
84
114
  // v$NN.c.$vatSlot = $kernelSlot = ko$NN/kp$NN/kd$NN
85
115
  // v$NN.vs.$key = string
86
- // v$NN.meter = m$NN // XXX does this exist?
87
- // v$NN.reapInterval = $NN or 'never'
88
- // v$NN.reapCountdown = $NN or 'never'
116
+ // old (v0): v$NN.reapInterval = $NN or 'never'
117
+ // old (v0): v$NN.reapCountdown = $NN or 'never'
118
+ // v$NN.reapDirt = JSON({ deliveries, gcKrefs, computrons }) // missing keys treated as zero
119
+ // (leave room for v$NN.snapshotDirt and options.snapshotDirtThreshold for #6786)
89
120
  // exclude from consensus
90
121
  // local.*
91
122
 
@@ -132,6 +163,21 @@ const enableKernelGC = true;
132
163
  // Prefix reserved for host written data:
133
164
  // host.
134
165
 
166
+ // Kernel state schema changes:
167
+ //
168
+ // v0: the original
169
+ // * no `version`
170
+ // * uses `initialized = 'true'`
171
+ // v1:
172
+ // * add `version = '1'`
173
+ // * remove `initialized`
174
+ // * replace `kernel.defaultReapInterval` with `kernel.defaultReapDirtThreshold`
175
+ // * replace vat's `vNN.reapInterval`/`vNN.reapCountdown` with `vNN.reapDirt`
176
+ // and a `vNN.reapDirtThreshold` in `vNN.options`
177
+ // v2:
178
+ // * change `version` to `'2'`
179
+ // * add `vats.terminated` with `[]` as initial value
180
+
135
181
  export function commaSplit(s) {
136
182
  if (s === '') {
137
183
  return [];
@@ -139,12 +185,32 @@ export function commaSplit(s) {
139
185
  return s.split(',');
140
186
  }
141
187
 
188
+ export function stripPrefix(prefix, str) {
189
+ assert(str.startsWith(prefix), str);
190
+ return str.slice(prefix.length);
191
+ }
192
+
142
193
  function insistMeterID(m) {
143
194
  assert.typeof(m, 'string');
144
195
  assert.equal(m[0], 'm');
145
196
  Nat(BigInt(m.slice(1)));
146
197
  }
147
198
 
199
+ export const getAllStaticVats = kvStore => {
200
+ const result = [];
201
+ const prefix = 'vat.name.';
202
+ for (const k of enumeratePrefixedKeys(kvStore, prefix)) {
203
+ const vatID = kvStore.get(k) || Fail`getNextKey ensures get`;
204
+ const name = k.slice(prefix.length);
205
+ result.push([name, vatID]);
206
+ }
207
+ return result;
208
+ };
209
+
210
+ export const getAllDynamicVats = getRequired => {
211
+ return JSON.parse(getRequired('vat.dynamicIDs'));
212
+ };
213
+
148
214
  // we use different starting index values for the various vNN/koNN/kdNN/kpNN
149
215
  // slots, to reduce confusing overlap when looking at debug messages (e.g.
150
216
  // seeing both kp1 and ko1, which are completely unrelated despite having the
@@ -163,15 +229,59 @@ const FIRST_PROMISE_ID = 40n;
163
229
  const FIRST_CRANK_NUMBER = 0n;
164
230
  const FIRST_METER_ID = 1n;
165
231
 
232
+ // this default "reap interval" is low for the benefit of tests:
233
+ // applications should set it to something higher (perhaps 200) based
234
+ // on their expected usage
235
+
236
+ export const DEFAULT_DELIVERIES_PER_BOYD = 1;
237
+
238
+ // "20" will trigger a BOYD after 10 krefs are dropped and retired
239
+ // (drops and retires are delivered in separate messages, so
240
+ // 10+10=20). The worst case work-expansion we've seen is in #8401,
241
+ // where one drop breaks one cycle, and each cycle's cleanup causes 50
242
+ // syscalls in the next v9-zoe BOYD. So this should limit each BOYD
243
+ // to cleaning 10 cycles, in 500 syscalls.
244
+
245
+ export const DEFAULT_GC_KREFS_PER_BOYD = 20;
246
+
166
247
  /**
167
248
  * @param {SwingStoreKernelStorage} kernelStorage
168
- * @param {KernelSlog|null} kernelSlog
249
+ * @param {number | 'uninitialized'} expectedVersion
250
+ * @param {KernelSlog} [kernelSlog]
169
251
  */
170
- export default function makeKernelKeeper(kernelStorage, kernelSlog) {
252
+ export default function makeKernelKeeper(
253
+ kernelStorage,
254
+ expectedVersion,
255
+ kernelSlog,
256
+ ) {
171
257
  const { kvStore, transcriptStore, snapStore, bundleStore } = kernelStorage;
172
258
 
173
259
  insistStorageAPI(kvStore);
174
260
 
261
+ // the terminated-vats cache is normally populated from
262
+ // 'vats.terminated', but for initialization purposes we need give
263
+ // it a value here, and then populate it for real if we're dealing
264
+ // with an up-to-date DB
265
+ let terminatedVats = [];
266
+
267
+ const versionString = kvStore.get('version');
268
+ const version = Number(versionString || '0');
269
+ if (expectedVersion === 'uninitialized') {
270
+ if (kvStore.has('initialized')) {
271
+ throw Error(`kernel DB already initialized (v0)`);
272
+ }
273
+ if (versionString) {
274
+ throw Error(`kernel DB already initialized (v${versionString})`);
275
+ }
276
+ } else if (expectedVersion !== version) {
277
+ throw Error(
278
+ `kernel DB is too old: has version v${version}, but expected v${expectedVersion}`,
279
+ );
280
+ } else {
281
+ // DB is up-to-date, so populate any caches we use
282
+ terminatedVats = JSON.parse(getRequired('vats.terminated'));
283
+ }
284
+
175
285
  /**
176
286
  * @param {string} key
177
287
  * @returns {string}
@@ -233,12 +343,10 @@ export default function makeKernelKeeper(kernelStorage, kernelSlog) {
233
343
  deviceKeepers: new Map(), // deviceID -> deviceKeeper
234
344
  });
235
345
 
236
- function getInitialized() {
237
- return !!kvStore.get('initialized');
238
- }
239
-
240
346
  function setInitialized() {
241
- kvStore.set('initialized', 'true');
347
+ assert(!kvStore.has('initialized'));
348
+ assert(!kvStore.has('version'));
349
+ kvStore.set('version', `${CURRENT_SCHEMA_VERSION}`);
242
350
  }
243
351
 
244
352
  function getCrankNumber() {
@@ -290,12 +398,17 @@ export default function makeKernelKeeper(kernelStorage, kernelSlog) {
290
398
  }
291
399
 
292
400
  /**
293
- * @param {KernelOptions} kernelOptions
401
+ * @param {InternalKernelOptions} kernelOptions
294
402
  */
295
403
  function createStartingKernelState(kernelOptions) {
404
+ // this should probably be a standalone function, not a method
296
405
  const {
297
406
  defaultManagerType = 'local',
298
- defaultReapInterval = 1,
407
+ defaultReapDirtThreshold = {
408
+ deliveries: DEFAULT_DELIVERIES_PER_BOYD,
409
+ gcKrefs: DEFAULT_GC_KREFS_PER_BOYD,
410
+ computrons: 'never',
411
+ },
299
412
  relaxDurabilityRules = false,
300
413
  snapshotInitial = 3,
301
414
  snapshotInterval = 200,
@@ -305,6 +418,7 @@ export default function makeKernelKeeper(kernelStorage, kernelSlog) {
305
418
  kvStore.set('vat.dynamicIDs', '[]');
306
419
  kvStore.set('vat.nextID', `${FIRST_VAT_ID}`);
307
420
  kvStore.set('vat.nextUpgradeID', `1`);
421
+ kvStore.set('vats.terminated', '[]');
308
422
  kvStore.set('device.names', '[]');
309
423
  kvStore.set('device.nextID', `${FIRST_DEVICE_ID}`);
310
424
  kvStore.set('ko.nextID', `${FIRST_OBJECT_ID}`);
@@ -317,7 +431,10 @@ export default function makeKernelKeeper(kernelStorage, kernelSlog) {
317
431
  initQueue('acceptanceQueue');
318
432
  kvStore.set('crankNumber', `${FIRST_CRANK_NUMBER}`);
319
433
  kvStore.set('kernel.defaultManagerType', defaultManagerType);
320
- kvStore.set('kernel.defaultReapInterval', `${defaultReapInterval}`);
434
+ kvStore.set(
435
+ DEFAULT_REAP_DIRT_THRESHOLD_KEY,
436
+ JSON.stringify(defaultReapDirtThreshold),
437
+ );
321
438
  kvStore.set('kernel.snapshotInitial', `${snapshotInitial}`);
322
439
  kvStore.set('kernel.snapshotInterval', `${snapshotInterval}`);
323
440
  if (relaxDurabilityRules) {
@@ -352,21 +469,25 @@ export default function makeKernelKeeper(kernelStorage, kernelSlog) {
352
469
 
353
470
  /**
354
471
  *
355
- * @returns {number | 'never'}
472
+ * @returns {ReapDirtThreshold}
356
473
  */
357
- function getDefaultReapInterval() {
358
- const r = getRequired('kernel.defaultReapInterval');
359
- const ri = r === 'never' ? r : Number.parseInt(r, 10);
360
- assert(ri === 'never' || typeof ri === 'number', `k.dri is '${ri}'`);
361
- return ri;
474
+ function getDefaultReapDirtThreshold() {
475
+ return JSON.parse(getRequired(DEFAULT_REAP_DIRT_THRESHOLD_KEY));
362
476
  }
363
477
 
364
- function setDefaultReapInterval(interval) {
365
- assert(
366
- interval === 'never' || isNat(interval),
367
- 'invalid defaultReapInterval value',
368
- );
369
- kvStore.set('kernel.defaultReapInterval', `${interval}`);
478
+ /**
479
+ * @param {ReapDirtThreshold} threshold
480
+ */
481
+ function setDefaultReapDirtThreshold(threshold) {
482
+ assert.typeof(threshold, 'object');
483
+ assert(threshold);
484
+ for (const [key, value] of Object.entries(threshold)) {
485
+ assert(
486
+ (typeof value === 'number' && value > 0) || value === 'never',
487
+ `threshold[${key}] ${value} must be a positive number or "never"`,
488
+ );
489
+ }
490
+ kvStore.set(DEFAULT_REAP_DIRT_THRESHOLD_KEY, JSON.stringify(threshold));
370
491
  }
371
492
 
372
493
  function getNat(key) {
@@ -500,7 +621,9 @@ export default function makeKernelKeeper(kernelStorage, kernelSlog) {
500
621
 
501
622
  function getObjectRefCount(kernelSlot) {
502
623
  const data = kvStore.get(`${kernelSlot}.refCount`);
503
- data || Fail`getObjectRefCount(${kernelSlot}) was missing`;
624
+ if (!data) {
625
+ return { reachable: 0, recognizable: 0 };
626
+ }
504
627
  const [reachable, recognizable] = commaSplit(data).map(Number);
505
628
  reachable <= recognizable ||
506
629
  Fail`refmismatch(get) ${kernelSlot} ${reachable},${recognizable}`;
@@ -578,19 +701,42 @@ export default function makeKernelKeeper(kernelStorage, kernelSlog) {
578
701
  function ownerOfKernelObject(kernelSlot) {
579
702
  insistKernelType('object', kernelSlot);
580
703
  const owner = kvStore.get(`${kernelSlot}.owner`);
581
- if (owner) {
582
- insistVatID(owner);
704
+ if (!owner) {
705
+ return undefined;
706
+ }
707
+ insistVatID(owner);
708
+ if (terminatedVats.includes(owner)) {
709
+ return undefined;
583
710
  }
584
711
  return owner;
585
712
  }
586
713
 
714
+ function retireKernelObjects(koids) {
715
+ Array.isArray(koids) || Fail`retireExports given non-Array ${koids}`;
716
+ const newActions = [];
717
+ for (const koid of koids) {
718
+ const importers = getImporters(koid);
719
+ for (const vatID of importers) {
720
+ newActions.push(`${vatID} retireImport ${koid}`);
721
+ }
722
+ deleteKernelObject(koid);
723
+ }
724
+ addGCActions(newActions);
725
+ }
726
+
587
727
  function orphanKernelObject(kref, oldVat) {
728
+ // termination orphans all exports, upgrade orphans non-durable
729
+ // exports, and syscall.abandonExports orphans specific ones
588
730
  const ownerKey = `${kref}.owner`;
589
731
  const ownerVat = kvStore.get(ownerKey);
590
732
  ownerVat === oldVat || Fail`export ${kref} not owned by old vat`;
591
733
  kvStore.delete(ownerKey);
734
+ const { vatSlot: vref } = getReachableAndVatSlot(oldVat, kref);
735
+ kvStore.delete(`${oldVat}.c.${kref}`);
736
+ kvStore.delete(`${oldVat}.c.${vref}`);
737
+ addMaybeFreeKref(kref);
592
738
  // note that we do not delete the object here: it will be
593
- // collected if/when all other references are dropped
739
+ // retired if/when all other references are dropped
594
740
  }
595
741
 
596
742
  function deleteKernelObject(koid) {
@@ -764,7 +910,6 @@ export default function makeKernelKeeper(kernelStorage, kernelSlog) {
764
910
 
765
911
  let idx = 0;
766
912
  for (const dataSlot of capdata.slots) {
767
- // eslint-disable-next-line no-use-before-define
768
913
  incrementRefCount(dataSlot, `resolve|${kernelSlot}|s${idx}`);
769
914
  idx += 1;
770
915
  }
@@ -785,14 +930,64 @@ export default function makeKernelKeeper(kernelStorage, kernelSlog) {
785
930
  kvStore.set(`${kernelSlot}.data.slots`, capdata.slots.join(','));
786
931
  }
787
932
 
788
- function cleanupAfterTerminatedVat(vatID) {
933
+ async function removeVatFromSwingStoreExports(vatID) {
934
+ // Delete primary swingstore records for this vat, in preparation
935
+ // for (slow) deletion. After this, swingstore exports will omit
936
+ // this vat. This is called from the kernel's terminateVat, which
937
+ // initiates (but does not complete) deletion.
938
+ snapStore.stopUsingLastSnapshot(vatID);
939
+ await transcriptStore.stopUsingTranscript(vatID);
940
+ }
941
+
942
+ /**
943
+ * Perform some cleanup work for a specific (terminated but not
944
+ * fully-deleted) vat, possibly limited by a budget. Returns 'done'
945
+ * (where false means "please call me again", and true means "you
946
+ * can delete the vatID now"), and a count of how much work was done
947
+ * (so the runPolicy can decide when to stop).
948
+ *
949
+ * @param {string} vatID
950
+ * @param {CleanupBudget} budget
951
+ * @returns {{ done: boolean, work: CleanupWork }}
952
+ *
953
+ */
954
+ function cleanupAfterTerminatedVat(vatID, budget) {
955
+ // this is called from terminateVat, which is called from either:
956
+ // * end of processDeliveryMessage, if crankResults.terminate
957
+ // * device-vat-admin (when vat-v-a does adminNode.terminateVat)
958
+ // (which always happens inside processDeliveryMessage)
959
+ // so we're always followed by a call to processRefcounts, at
960
+ // end-of-delivery in processDeliveryMessage, after checking
961
+ // crankResults.terminate
962
+
789
963
  insistVatID(vatID);
790
- // eslint-disable-next-line no-use-before-define
791
- const vatKeeper = provideVatKeeper(vatID);
792
- const exportPrefix = `${vatID}.c.o+`;
793
- const importPrefix = `${vatID}.c.o-`;
964
+ const work = {
965
+ exports: 0,
966
+ imports: 0,
967
+ kv: 0,
968
+ snapshots: 0,
969
+ transcripts: 0,
970
+ };
971
+ let remaining = 0;
972
+
973
+ // TODO: it would be slightly cheaper to walk all kvStore keys in
974
+ // order, and act on each one according to its category (c-list
975
+ // export, c-list import, vatstore, other), so we use a single
976
+ // enumeratePrefixedKeys() call each time. Until we do that, the
977
+ // last phase of the cleanup (where we've deleted all the exports
978
+ // and imports, and are working on the remaining keys) will waste
979
+ // two DB queries on each call. OTOH, those queries will probably
980
+ // hit the same SQLite index page as the successful one, so it
981
+ // probably won't cause any extra disk IO. So we can defer this
982
+ // optimization for a while. Note: when we implement it, be
983
+ // prepared to encounter the clist entries in eiher order (kref
984
+ // first or vref first), and delete the other one in the same
985
+ // call, so we don't wind up with half an entry.
794
986
 
795
- vatKeeper.deleteSnapshotsAndTranscript();
987
+ const vatKeeper = provideVatKeeper(vatID);
988
+ const clistPrefix = `${vatID}.c.`;
989
+ const exportPrefix = `${clistPrefix}o+`;
990
+ const importPrefix = `${clistPrefix}o-`;
796
991
 
797
992
  // Note: ASCII order is "+,-./", and we rely upon this to split the
798
993
  // keyspace into the various o+NN/o-NN/etc spaces. If we were using a
@@ -808,6 +1003,7 @@ export default function makeKernelKeeper(kernelStorage, kernelSlog) {
808
1003
  // used.
809
1004
 
810
1005
  // first, scan for exported objects, which must be orphaned
1006
+ remaining = budget.exports ?? budget.default;
811
1007
  for (const k of enumeratePrefixedKeys(kvStore, exportPrefix)) {
812
1008
  // The void for an object exported by a vat will always be of the form
813
1009
  // `o+NN`. The '+' means that the vat exported the object (rather than
@@ -816,28 +1012,67 @@ export default function makeKernelKeeper(kernelStorage, kernelSlog) {
816
1012
  // begin with `vMM.c.o+`. In addition to deleting the c-list entry, we
817
1013
  // must also delete the corresponding kernel owner entry for the object,
818
1014
  // since the object will no longer be accessible.
1015
+ const vref = stripPrefix(clistPrefix, k);
1016
+ assert(vref.startsWith('o+'), vref);
819
1017
  const kref = kvStore.get(k);
1018
+ // note: adds to maybeFreeKrefs, deletes c-list and .owner
820
1019
  orphanKernelObject(kref, vatID);
1020
+ work.exports += 1;
1021
+ remaining -= 1;
1022
+ if (remaining <= 0) {
1023
+ return { done: false, work };
1024
+ }
821
1025
  }
822
1026
 
823
1027
  // then scan for imported objects, which must be decrefed
1028
+ remaining = budget.imports ?? budget.default;
824
1029
  for (const k of enumeratePrefixedKeys(kvStore, importPrefix)) {
825
1030
  // abandoned imports: delete the clist entry as if the vat did a
826
1031
  // drop+retire
827
1032
  const kref = kvStore.get(k) || Fail`getNextKey ensures get`;
828
- const vref = k.slice(`${vatID}.c.`.length);
1033
+ const vref = stripPrefix(clistPrefix, k);
829
1034
  vatKeeper.deleteCListEntry(kref, vref);
830
1035
  // that will also delete both db keys
1036
+ work.imports += 1;
1037
+ remaining -= 1;
1038
+ if (remaining <= 0) {
1039
+ return { done: false, work };
1040
+ }
831
1041
  }
832
1042
 
833
1043
  // the caller used enumeratePromisesByDecider() before calling us,
834
1044
  // so they already know the orphaned promises to reject
835
1045
 
836
1046
  // now loop back through everything and delete it all
1047
+ remaining = budget.kv ?? budget.default;
837
1048
  for (const k of enumeratePrefixedKeys(kvStore, `${vatID}.`)) {
838
1049
  kvStore.delete(k);
1050
+ work.kv += 1;
1051
+ remaining -= 1;
1052
+ if (remaining <= 0) {
1053
+ return { done: false, work };
1054
+ }
839
1055
  }
840
1056
 
1057
+ // this will internally loop through 'budget' deletions
1058
+ remaining = budget.snapshots ?? budget.default;
1059
+ const dsc = vatKeeper.deleteSnapshots(remaining);
1060
+ work.snapshots += dsc.cleanups;
1061
+ remaining -= dsc.cleanups;
1062
+ if (remaining <= 0) {
1063
+ return { done: false, work };
1064
+ }
1065
+
1066
+ // same
1067
+ remaining = budget.transcripts ?? budget.default;
1068
+ const dts = vatKeeper.deleteTranscripts(remaining);
1069
+ work.transcripts += dts.cleanups;
1070
+ remaining -= dts.cleanups;
1071
+ // last task, so increment cleanups, but dc.done is authoritative
1072
+ return { done: dts.done, work };
1073
+ }
1074
+
1075
+ function deleteVatID(vatID) {
841
1076
  // TODO: deleting entries from the dynamic vat IDs list requires a linear
842
1077
  // scan of the list; arguably this collection ought to be represented in a
843
1078
  // different way that makes it efficient to remove an entry from it, though
@@ -1102,15 +1337,7 @@ export default function makeKernelKeeper(kernelStorage, kernelSlog) {
1102
1337
  kvStore.set(KEY, JSON.stringify(dynamicVatIDs));
1103
1338
  }
1104
1339
 
1105
- function getStaticVats() {
1106
- const result = [];
1107
- for (const k of enumeratePrefixedKeys(kvStore, 'vat.name.')) {
1108
- const name = k.slice(9);
1109
- const vatID = kvStore.get(k) || Fail`getNextKey ensures get`;
1110
- result.push([name, vatID]);
1111
- }
1112
- return result;
1113
- }
1340
+ const getStaticVats = () => getAllStaticVats(kvStore);
1114
1341
 
1115
1342
  function getDevices() {
1116
1343
  const result = [];
@@ -1122,9 +1349,7 @@ export default function makeKernelKeeper(kernelStorage, kernelSlog) {
1122
1349
  return result;
1123
1350
  }
1124
1351
 
1125
- function getDynamicVats() {
1126
- return JSON.parse(getRequired('vat.dynamicIDs'));
1127
- }
1352
+ const getDynamicVats = () => getAllDynamicVats(getRequired);
1128
1353
 
1129
1354
  function allocateUpgradeID() {
1130
1355
  const nextID = Nat(BigInt(getRequired(`vat.nextUpgradeID`)));
@@ -1132,6 +1357,44 @@ export default function makeKernelKeeper(kernelStorage, kernelSlog) {
1132
1357
  return makeUpgradeID(nextID);
1133
1358
  }
1134
1359
 
1360
+ function markVatAsTerminated(vatID) {
1361
+ if (!terminatedVats.includes(vatID)) {
1362
+ terminatedVats.push(vatID);
1363
+ kvStore.set(`vats.terminated`, JSON.stringify(terminatedVats));
1364
+ }
1365
+ }
1366
+
1367
+ function getFirstTerminatedVat() {
1368
+ if (terminatedVats.length) {
1369
+ return terminatedVats[0];
1370
+ }
1371
+ return undefined;
1372
+ }
1373
+
1374
+ function forgetTerminatedVat(vatID) {
1375
+ terminatedVats = terminatedVats.filter(id => id !== vatID);
1376
+ kvStore.set(`vats.terminated`, JSON.stringify(terminatedVats));
1377
+ }
1378
+
1379
+ /**
1380
+ * @param {PolicyOutputCleanupBudget} allowCleanup
1381
+ * @returns {RunQueueEventCleanupTerminatedVat | undefined}
1382
+ */
1383
+ function nextCleanupTerminatedVatAction(allowCleanup) {
1384
+ if (allowCleanup === false) {
1385
+ return undefined;
1386
+ } else {
1387
+ const unlimited = { default: Infinity };
1388
+ /** @type {CleanupBudget} */
1389
+ const budget = allowCleanup === true ? unlimited : allowCleanup;
1390
+ const vatID = getFirstTerminatedVat();
1391
+ if (vatID) {
1392
+ return { type: 'cleanup-terminated-vat', vatID, budget };
1393
+ }
1394
+ return undefined;
1395
+ }
1396
+ }
1397
+
1135
1398
  // As refcounts are decremented, we accumulate a set of krefs for which
1136
1399
  // action might need to be taken:
1137
1400
  // * promises which are now resolved and unreferenced can be deleted
@@ -1232,14 +1495,43 @@ export default function makeKernelKeeper(kernelStorage, kernelSlog) {
1232
1495
  return false;
1233
1496
  }
1234
1497
 
1498
+ // TODO (#9888): change the processRefcounts maybeFreeKrefs
1499
+ // iteration to handle krefs that get added multiple times (while
1500
+ // we're iterating), because we might do different work the second
1501
+ // time around. The concerning scenario is:
1502
+ //
1503
+ // * kp2 has resolution data which references ko1
1504
+ // * somehow both kp2 and ko1 end up on maybeFreeKrefs, but ko1.reachable=1
1505
+ // (via kp2)
1506
+ // * our iterator visits ko1 first, sees it is still reachable, ignores it
1507
+ // * then the iterator visits kp2, decrefs its kp.data.slots
1508
+ // * this pushes ko1 back onto maybeFreeKrefs
1509
+ // * we need to examine ko1 again, so it can be released
1510
+ //
1511
+ // It could also happen if/when we implement #2069 auxdata:
1512
+ //
1513
+ // * ko2 has auxdata that references ko1
1514
+ // * both ko1 and ko2 end up on maybeFreeKrefs, but ko1.reachable = 1
1515
+ // (via the ko2 auxdata)
1516
+ // * our iterator visits ko1 first, sees it is still reachable, ignores it
1517
+ // * then the iterator visits ko2, does deleteKernelObject
1518
+ // * this frees the auxdata, which pushes ko1 back onto maybeFreeKrefs
1519
+ // * we need to examine ko1 again, so it can be released
1520
+ //
1521
+ // We should use something like an ordered Set, and a loop that does:
1522
+ // * pop the first kref off
1523
+ // * processes it (maybe adding more krefs)
1524
+ // * repeats until the thing is empty
1525
+ // Or maybe make a copy of maybeFreeKrefs at the start, clear the
1526
+ // original, and wrap this in a loop that keeps going until
1527
+ // maybeFreeKrefs is still empty at the end. Be sure to imagine a
1528
+ // very deep linked list: don't just process it twice, keep
1529
+ // processing until there's nothing left to do, otherwise we'll be
1530
+ // leaving work for the next delivery.
1531
+
1235
1532
  function processRefcounts() {
1236
1533
  if (enableKernelGC) {
1237
- const actions = getGCActions(); // local cache
1238
- // TODO (else buggy): change the iteration to handle krefs that get
1239
- // added multiple times (while we're iterating), because we might do
1240
- // different work the second time around. Something like an ordered
1241
- // Set, and a loop that: pops the first kref off, processes it (maybe
1242
- // adding more krefs), repeats until the thing is empty.
1534
+ const actions = new Set();
1243
1535
  for (const kref of maybeFreeKrefs.values()) {
1244
1536
  const { type } = parseKernelSlot(kref);
1245
1537
  if (type === 'promise') {
@@ -1247,6 +1539,7 @@ export default function makeKernelKeeper(kernelStorage, kernelSlog) {
1247
1539
  const kp = getKernelPromise(kpid);
1248
1540
  if (kp.refCount === 0) {
1249
1541
  let idx = 0;
1542
+ // TODO (#9889) don't assume promise is settled
1250
1543
  for (const slot of kp.data.slots) {
1251
1544
  // Note: the following decrement can result in an addition to the
1252
1545
  // maybeFreeKrefs set, which we are in the midst of iterating.
@@ -1257,46 +1550,89 @@ export default function makeKernelKeeper(kernelStorage, kernelSlog) {
1257
1550
  deleteKernelPromise(kpid);
1258
1551
  }
1259
1552
  }
1553
+
1260
1554
  if (type === 'object') {
1261
1555
  const { reachable, recognizable } = getObjectRefCount(kref);
1262
1556
  if (reachable === 0) {
1263
- const ownerVatID = ownerOfKernelObject(kref);
1264
- if (ownerVatID) {
1265
- // eslint-disable-next-line no-use-before-define
1557
+ // We avoid ownerOfKernelObject(), which will report
1558
+ // 'undefined' if the owner is dead (and being slowly
1559
+ // deleted). Message delivery should use that, but not us.
1560
+ const ownerKey = `${kref}.owner`;
1561
+ let ownerVatID = kvStore.get(ownerKey);
1562
+ const terminated = terminatedVats.includes(ownerVatID);
1563
+
1564
+ // Some objects that are still owned, but the owning vat
1565
+ // might still alive, or might be terminated and in the
1566
+ // process of being deleted. These two clauses are
1567
+ // mutually exclusive.
1568
+
1569
+ if (ownerVatID && !terminated) {
1266
1570
  const vatKeeper = provideVatKeeper(ownerVatID);
1267
- const isReachable = vatKeeper.getReachableFlag(kref);
1268
- if (isReachable) {
1571
+ const vatConsidersReachable = vatKeeper.getReachableFlag(kref);
1572
+ if (vatConsidersReachable) {
1269
1573
  // the reachable count is zero, but the vat doesn't realize it
1270
1574
  actions.add(`${ownerVatID} dropExport ${kref}`);
1271
1575
  }
1272
1576
  if (recognizable === 0) {
1273
- // TODO: rethink this
1274
- // assert.equal(isReachable, false, `${kref} is reachable but not recognizable`);
1577
+ // TODO: rethink this assert
1578
+ // assert.equal(vatConsidersReachable, false, `${kref} is reachable but not recognizable`);
1275
1579
  actions.add(`${ownerVatID} retireExport ${kref}`);
1276
1580
  }
1277
- } else if (recognizable === 0) {
1278
- // unreachable, unrecognizable, orphaned: delete the
1279
- // empty refcount here, since we can't send a GC
1280
- // action without an ownerVatID
1281
- deleteKernelObject(kref);
1581
+ } else if (ownerVatID && terminated) {
1582
+ // When we're slowly deleting a vat, and one of its
1583
+ // exports becomes unreferenced, we obviously must not
1584
+ // send dropExports or retireExports into the dead vat.
1585
+ // We fast-forward the abandonment that slow-deletion
1586
+ // would have done, then treat the object as orphaned.
1587
+
1588
+ const { vatSlot } = getReachableAndVatSlot(ownerVatID, kref);
1589
+ // delete directly, not orphanKernelObject(), which
1590
+ // would re-submit to maybeFreeKrefs
1591
+ kvStore.delete(ownerKey);
1592
+ kvStore.delete(`${ownerVatID}.c.${kref}`);
1593
+ kvStore.delete(`${ownerVatID}.c.${vatSlot}`);
1594
+ // now fall through to the orphaned case
1595
+ ownerVatID = undefined;
1596
+ }
1597
+
1598
+ // Now handle objects which were orphaned. NOTE: this
1599
+ // includes objects which were owned by a terminated (but
1600
+ // not fully deleted) vat, where `ownerVatID` was cleared
1601
+ // in the last line of that previous clause (the
1602
+ // fall-through case). Don't try to change this `if
1603
+ // (!ownerVatID)` into an `else if`: the two clauses are
1604
+ // *not* mutually-exclusive.
1605
+
1606
+ if (!ownerVatID) {
1607
+ // orphaned and unreachable, so retire it. If the kref
1608
+ // is recognizable, then we need retireKernelObjects()
1609
+ // to scan for importers and send retireImports (and
1610
+ // delete), else we can call deleteKernelObject directly
1611
+ if (recognizable) {
1612
+ retireKernelObjects([kref]);
1613
+ } else {
1614
+ deleteKernelObject(kref);
1615
+ }
1282
1616
  }
1283
1617
  }
1284
1618
  }
1285
1619
  }
1286
- setGCActions(actions);
1620
+ addGCActions([...actions]);
1287
1621
  }
1288
1622
  maybeFreeKrefs.clear();
1289
1623
  }
1290
1624
 
1625
+ function createVatState(vatID, source, options) {
1626
+ initializeVatState(kvStore, transcriptStore, vatID, source, options);
1627
+ }
1628
+
1291
1629
  function provideVatKeeper(vatID) {
1292
1630
  insistVatID(vatID);
1293
1631
  const found = ephemeral.vatKeepers.get(vatID);
1294
1632
  if (found !== undefined) {
1295
1633
  return found;
1296
1634
  }
1297
- if (!kvStore.has(`${vatID}.o.nextID`)) {
1298
- initializeVatState(kvStore, transcriptStore, vatID);
1299
- }
1635
+ assert(kvStore.has(`${vatID}.o.nextID`), `${vatID} was not initialized`);
1300
1636
  const vk = makeVatKeeper(
1301
1637
  kvStore,
1302
1638
  transcriptStore,
@@ -1314,6 +1650,7 @@ export default function makeKernelKeeper(kernelStorage, kernelSlog) {
1314
1650
  incStat,
1315
1651
  decStat,
1316
1652
  getCrankNumber,
1653
+ scheduleReap,
1317
1654
  snapStore,
1318
1655
  );
1319
1656
  ephemeral.vatKeepers.set(vatID, vk);
@@ -1322,7 +1659,7 @@ export default function makeKernelKeeper(kernelStorage, kernelSlog) {
1322
1659
 
1323
1660
  function vatIsAlive(vatID) {
1324
1661
  insistVatID(vatID);
1325
- return kvStore.has(`${vatID}.o.nextID`);
1662
+ return kvStore.has(`${vatID}.o.nextID`) && !terminatedVats.includes(vatID);
1326
1663
  }
1327
1664
 
1328
1665
  /**
@@ -1530,13 +1867,13 @@ export default function makeKernelKeeper(kernelStorage, kernelSlog) {
1530
1867
  }
1531
1868
 
1532
1869
  return harden({
1533
- getInitialized,
1534
1870
  setInitialized,
1535
1871
  createStartingKernelState,
1536
1872
  getDefaultManagerType,
1537
- getDefaultReapInterval,
1538
1873
  getRelaxDurabilityRules,
1539
- setDefaultReapInterval,
1874
+ getDefaultReapDirtThreshold,
1875
+ setDefaultReapDirtThreshold,
1876
+
1540
1877
  getSnapshotInitial,
1541
1878
  getSnapshotInterval,
1542
1879
  setSnapshotInterval,
@@ -1570,6 +1907,7 @@ export default function makeKernelKeeper(kernelStorage, kernelSlog) {
1570
1907
  kernelObjectExists,
1571
1908
  getImporters,
1572
1909
  orphanKernelObject,
1910
+ retireKernelObjects,
1573
1911
  deleteKernelObject,
1574
1912
  pinObject,
1575
1913
 
@@ -1610,14 +1948,22 @@ export default function makeKernelKeeper(kernelStorage, kernelSlog) {
1610
1948
  getVatIDForName,
1611
1949
  allocateVatIDForNameIfNeeded,
1612
1950
  allocateUnusedVatID,
1951
+ createVatState,
1613
1952
  provideVatKeeper,
1614
1953
  vatIsAlive,
1615
1954
  evictVatKeeper,
1955
+ removeVatFromSwingStoreExports,
1616
1956
  cleanupAfterTerminatedVat,
1617
1957
  addDynamicVatID,
1618
1958
  getDynamicVats,
1619
1959
  getStaticVats,
1620
1960
  getDevices,
1961
+ deleteVatID,
1962
+
1963
+ markVatAsTerminated,
1964
+ getFirstTerminatedVat,
1965
+ forgetTerminatedVat,
1966
+ nextCleanupTerminatedVatAction,
1621
1967
 
1622
1968
  allocateUpgradeID,
1623
1969