@agoric/swingset-vat 0.33.0-u17.1 → 0.33.0-u18.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,3 @@
1
- /* eslint-disable no-use-before-define */
2
1
  import { Nat, isNat } from '@endo/nat';
3
2
  import { assert, Fail } from '@endo/errors';
4
3
  import {
@@ -43,16 +42,19 @@ const enableKernelGC = true;
43
42
  * @typedef { import('../../types-external.js').SnapStore } SnapStore
44
43
  * @typedef { import('../../types-external.js').TranscriptStore } TranscriptStore
45
44
  * @typedef { import('../../types-external.js').VatKeeper } VatKeeper
45
+ * @typedef { Pick<VatKeeper, 'deleteCListEntry' | 'deleteSnapshots' | 'deleteTranscripts'> } VatUndertaker
46
46
  * @typedef { import('../../types-internal.js').InternalKernelOptions } InternalKernelOptions
47
47
  * @typedef { import('../../types-internal.js').ReapDirtThreshold } ReapDirtThreshold
48
+ * @import {PromiseRecord} from '../../types-internal.js';
48
49
  * @import {CleanupBudget, CleanupWork, PolicyOutputCleanupBudget} from '../../types-external.js';
49
50
  * @import {RunQueueEventCleanupTerminatedVat} from '../../types-internal.js';
51
+ * @import {SwingStoreKernelStorage} from '@agoric/swing-store';
50
52
  */
51
53
 
52
54
  export { DEFAULT_REAP_DIRT_THRESHOLD_KEY };
53
55
 
54
56
  // most recent DB schema version
55
- export const CURRENT_SCHEMA_VERSION = 2;
57
+ export const CURRENT_SCHEMA_VERSION = 3;
56
58
 
57
59
  // Kernel state lives in a key-value store supporting key retrieval by
58
60
  // lexicographic range. All keys and values are strings.
@@ -71,9 +73,9 @@ export const CURRENT_SCHEMA_VERSION = 2;
71
73
  // only modified by a call to upgradeSwingset(). See below for
72
74
  // deltas/upgrades from one version to the next.
73
75
  //
74
- // The current ("v2") schema keys/values are:
76
+ // The current ("v3") schema keys/values are:
75
77
  //
76
- // version = '2'
78
+ // version = '3'
77
79
  // vat.names = JSON([names..])
78
80
  // vat.dynamicIDs = JSON([vatIDs..])
79
81
  // vat.name.$NAME = $vatID = v$NN
@@ -117,6 +119,8 @@ export const CURRENT_SCHEMA_VERSION = 2;
117
119
  // old (v0): v$NN.reapCountdown = $NN or 'never'
118
120
  // v$NN.reapDirt = JSON({ deliveries, gcKrefs, computrons }) // missing keys treated as zero
119
121
  // (leave room for v$NN.snapshotDirt and options.snapshotDirtThreshold for #6786)
122
+ // v$NN.vatParameters = JSON(capdata) // missing for vats created/upgraded before #8947
123
+ //
120
124
  // exclude from consensus
121
125
  // local.*
122
126
 
@@ -141,6 +145,7 @@ export const CURRENT_SCHEMA_VERSION = 2;
141
145
  // gcActions = JSON(gcActions)
142
146
  // reapQueue = JSON([vatIDs...])
143
147
  // pinnedObjects = ko$NN[,ko$NN..]
148
+ // upgradeEvents = JSON([events..])
144
149
 
145
150
  // ko.nextID = $NN
146
151
  // ko$NN.owner = $vatID
@@ -177,7 +182,13 @@ export const CURRENT_SCHEMA_VERSION = 2;
177
182
  // v2:
178
183
  // * change `version` to `'2'`
179
184
  // * add `vats.terminated` with `[]` as initial value
185
+ // v3:
186
+ // * change `version` to `'3'`
187
+ // * perform remediation for bug #9039
188
+ // (after v3, does not get its own version)
189
+ // * `upgradeEvents` recognized, but omitted if empty
180
190
 
191
+ /** @type {(s: string) => string[]} s */
181
192
  export function commaSplit(s) {
182
193
  if (s === '') {
183
194
  return [];
@@ -211,6 +222,77 @@ export const getAllDynamicVats = getRequired => {
211
222
  return JSON.parse(getRequired('vat.dynamicIDs'));
212
223
  };
213
224
 
225
+ const getObjectReferenceCount = (kvStore, kref) => {
226
+ const data = kvStore.get(`${kref}.refCount`);
227
+ if (!data) {
228
+ return { reachable: 0, recognizable: 0 };
229
+ }
230
+ const [reachable, recognizable] = commaSplit(data).map(Number);
231
+ reachable <= recognizable ||
232
+ Fail`refmismatch(get) ${kref} ${reachable},${recognizable}`;
233
+ return { reachable, recognizable };
234
+ };
235
+
236
+ const setObjectReferenceCount = (kvStore, kref, counts) => {
237
+ const { reachable, recognizable } = counts;
238
+ assert.typeof(reachable, 'number');
239
+ assert.typeof(recognizable, 'number');
240
+ (reachable >= 0 && recognizable >= 0) ||
241
+ Fail`${kref} underflow ${reachable},${recognizable}`;
242
+ reachable <= recognizable ||
243
+ Fail`refmismatch(set) ${kref} ${reachable},${recognizable}`;
244
+ kvStore.set(`${kref}.refCount`, `${reachable},${recognizable}`);
245
+ };
246
+
247
+ /**
248
+ * Increment the reference count associated with some kernel object.
249
+ *
250
+ * We track references to promises and objects, but not devices. Promises
251
+ * have only a "reachable" count, whereas objects track both "reachable"
252
+ * and "recognizable" counts.
253
+ *
254
+ * @param { (key: string) => string} getRequired
255
+ * @param { import('@agoric/swing-store').KVStore } kvStore
256
+ * @param {string} kref The kernel slot whose refcount is to be incremented.
257
+ * @param {string?} tag Debugging note with rough source of the reference.
258
+ * @param {{ isExport?: boolean, onlyRecognizable?: boolean }} options
259
+ * 'isExport' means the reference comes from a clist export, which counts
260
+ * for promises but not objects. 'onlyRecognizable' means the reference
261
+ * provides only recognition, not reachability
262
+ */
263
+ export const incrementReferenceCount = (
264
+ getRequired,
265
+ kvStore,
266
+ kref,
267
+ tag,
268
+ options = {},
269
+ ) => {
270
+ const { isExport = false, onlyRecognizable = false } = options;
271
+ kref || Fail`incrementRefCount called with empty kref, tag=${tag}`;
272
+ const { type } = parseKernelSlot(kref);
273
+ if (type === 'promise') {
274
+ const refCount = Number(getRequired(`${kref}.refCount`)) + 1;
275
+ // kdebug(`++ ${kref} ${tag} ${refCount}`);
276
+ kvStore.set(`${kref}.refCount`, `${refCount}`);
277
+ }
278
+ if (type === 'object' && !isExport) {
279
+ let { reachable, recognizable } = getObjectReferenceCount(kvStore, kref);
280
+ if (!onlyRecognizable) {
281
+ reachable += 1;
282
+ }
283
+ recognizable += 1;
284
+ // kdebug(`++ ${kref} ${tag} ${reachable},${recognizable}`);
285
+ setObjectReferenceCount(kvStore, kref, { reachable, recognizable });
286
+ }
287
+ };
288
+
289
+ export function* readQueue(queue, getRequired) {
290
+ const [head, tail] = JSON.parse(getRequired(`${queue}`));
291
+ for (let i = head; i < tail; i += 1) {
292
+ yield JSON.parse(getRequired(`${queue}.${i}`));
293
+ }
294
+ }
295
+
214
296
  // we use different starting index values for the various vNN/koNN/kdNN/kpNN
215
297
  // slots, to reduce confusing overlap when looking at debug messages (e.g.
216
298
  // seeing both kp1 and ko1, which are completely unrelated despite having the
@@ -340,6 +422,8 @@ export default function makeKernelKeeper(
340
422
  const ephemeral = harden({
341
423
  /** @type { Map<string, VatKeeper> } */
342
424
  vatKeepers: new Map(),
425
+ /** @type { Map<string, VatUndertaker> } */
426
+ vatUndertakers: new Map(),
343
427
  deviceKeepers: new Map(), // deviceID -> deviceKeeper
344
428
  });
345
429
 
@@ -388,14 +472,7 @@ export default function makeKernelKeeper(
388
472
  return tail - head;
389
473
  }
390
474
 
391
- function dumpQueue(queue) {
392
- const [head, tail] = JSON.parse(getRequired(`${queue}`));
393
- const result = [];
394
- for (let i = head; i < tail; i += 1) {
395
- result.push(JSON.parse(getRequired(`${queue}.${i}`)));
396
- }
397
- return result;
398
- }
475
+ const dumpQueue = queue => [...readQueue(queue, getRequired)];
399
476
 
400
477
  /**
401
478
  * @param {InternalKernelOptions} kernelOptions
@@ -619,26 +696,9 @@ export default function makeKernelKeeper(
619
696
  return parseReachableAndVatSlot(kvStore.get(kernelKey));
620
697
  }
621
698
 
622
- function getObjectRefCount(kernelSlot) {
623
- const data = kvStore.get(`${kernelSlot}.refCount`);
624
- if (!data) {
625
- return { reachable: 0, recognizable: 0 };
626
- }
627
- const [reachable, recognizable] = commaSplit(data).map(Number);
628
- reachable <= recognizable ||
629
- Fail`refmismatch(get) ${kernelSlot} ${reachable},${recognizable}`;
630
- return { reachable, recognizable };
631
- }
632
-
633
- function setObjectRefCount(kernelSlot, { reachable, recognizable }) {
634
- assert.typeof(reachable, 'number');
635
- assert.typeof(recognizable, 'number');
636
- (reachable >= 0 && recognizable >= 0) ||
637
- Fail`${kernelSlot} underflow ${reachable},${recognizable}`;
638
- reachable <= recognizable ||
639
- Fail`refmismatch(set) ${kernelSlot} ${reachable},${recognizable}`;
640
- kvStore.set(`${kernelSlot}.refCount`, `${reachable},${recognizable}`);
641
- }
699
+ const getObjectRefCount = kref => getObjectReferenceCount(kvStore, kref);
700
+ const setObjectRefCount = (kref, counts) =>
701
+ setObjectReferenceCount(kvStore, kref, counts);
642
702
 
643
703
  /**
644
704
  * Iterate over non-durable objects exported by a vat.
@@ -790,43 +850,41 @@ export default function makeKernelKeeper(
790
850
  return kpid;
791
851
  }
792
852
 
853
+ /**
854
+ * @param {string} kernelSlot
855
+ * @returns {PromiseRecord}
856
+ */
793
857
  function getKernelPromise(kernelSlot) {
794
858
  insistKernelType('promise', kernelSlot);
795
- const p = { state: getRequired(`${kernelSlot}.state`) };
796
- switch (p.state) {
797
- case undefined: {
798
- throw Fail`unknown kernelPromise '${kernelSlot}'`;
799
- }
859
+ const state = getRequired(`${kernelSlot}.state`);
860
+ const refCount = Number(kvStore.get(`${kernelSlot}.refCount`));
861
+ switch (state) {
800
862
  case 'unresolved': {
801
- p.refCount = Number(kvStore.get(`${kernelSlot}.refCount`));
802
- p.decider = kvStore.get(`${kernelSlot}.decider`);
803
- if (p.decider === '') {
804
- p.decider = undefined;
805
- }
806
- p.policy = kvStore.get(`${kernelSlot}.policy`) || 'ignore';
807
- p.subscribers = commaSplit(kvStore.get(`${kernelSlot}.subscribers`));
808
- p.queue = Array.from(
863
+ const decider = kvStore.get(`${kernelSlot}.decider`) || undefined;
864
+ const policy = kvStore.get(`${kernelSlot}.policy`) || 'ignore';
865
+ const subscribers = commaSplit(
866
+ kvStore.get(`${kernelSlot}.subscribers`) || '',
867
+ );
868
+ const queue = Array.from(
809
869
  getPrefixedValues(kvStore, `${kernelSlot}.queue.`),
810
870
  ).map(s => JSON.parse(s));
811
- break;
871
+ return harden({ state, refCount, decider, policy, subscribers, queue });
812
872
  }
813
873
  case 'fulfilled':
814
874
  case 'rejected': {
815
- p.refCount = Number(kvStore.get(`${kernelSlot}.refCount`));
816
- p.data = {
817
- body: kvStore.get(`${kernelSlot}.data.body`),
818
- slots: commaSplit(kvStore.get(`${kernelSlot}.data.slots`)),
875
+ const data = {
876
+ body: getRequired(`${kernelSlot}.data.body`),
877
+ slots: commaSplit(getRequired(`${kernelSlot}.data.slots`)),
819
878
  };
820
- for (const s of p.data.slots) {
879
+ for (const s of data.slots) {
821
880
  parseKernelSlot(s);
822
881
  }
823
- break;
882
+ return harden({ state, refCount, data });
824
883
  }
825
884
  default: {
826
- throw Fail`unknown state for ${kernelSlot}: ${p.state}`;
885
+ throw Fail`unknown state for ${kernelSlot}: ${state}`;
827
886
  }
828
887
  }
829
- return harden(p);
830
888
  }
831
889
 
832
890
  function getResolveablePromise(kpid, expectedDecider) {
@@ -835,7 +893,9 @@ export default function makeKernelKeeper(
835
893
  insistVatID(expectedDecider);
836
894
  }
837
895
  const p = getKernelPromise(kpid);
838
- p.state === 'unresolved' || Fail`${kpid} was already resolved`;
896
+ if (p.state !== 'unresolved') {
897
+ throw Fail`${kpid} was already resolved`;
898
+ }
839
899
  if (expectedDecider) {
840
900
  p.decider === expectedDecider ||
841
901
  Fail`${kpid} is decided by ${p.decider}, not ${expectedDecider}`;
@@ -894,6 +954,7 @@ export default function makeKernelKeeper(
894
954
  // up the resolution *now* and set the correct target early. Doing that
895
955
  // might make it easier to remove the Promise Table entry earlier.
896
956
  const p = getKernelPromise(kernelSlot);
957
+ assert.equal(p.state, 'unresolved');
897
958
  for (const msg of p.queue) {
898
959
  const entry = harden({ type: 'send', target: kernelSlot, msg });
899
960
  enqueue('acceptanceQueue', entry);
@@ -964,6 +1025,7 @@ export default function makeKernelKeeper(
964
1025
  const work = {
965
1026
  exports: 0,
966
1027
  imports: 0,
1028
+ promises: 0,
967
1029
  kv: 0,
968
1030
  snapshots: 0,
969
1031
  transcripts: 0,
@@ -984,10 +1046,11 @@ export default function makeKernelKeeper(
984
1046
  // first or vref first), and delete the other one in the same
985
1047
  // call, so we don't wind up with half an entry.
986
1048
 
987
- const vatKeeper = provideVatKeeper(vatID);
1049
+ const undertaker = provideVatUndertaker(vatID);
988
1050
  const clistPrefix = `${vatID}.c.`;
989
1051
  const exportPrefix = `${clistPrefix}o+`;
990
1052
  const importPrefix = `${clistPrefix}o-`;
1053
+ const promisePrefix = `${clistPrefix}p`;
991
1054
 
992
1055
  // Note: ASCII order is "+,-./", and we rely upon this to split the
993
1056
  // keyspace into the various o+NN/o-NN/etc spaces. If we were using a
@@ -1031,7 +1094,7 @@ export default function makeKernelKeeper(
1031
1094
  // drop+retire
1032
1095
  const kref = kvStore.get(k) || Fail`getNextKey ensures get`;
1033
1096
  const vref = stripPrefix(clistPrefix, k);
1034
- vatKeeper.deleteCListEntry(kref, vref);
1097
+ undertaker.deleteCListEntry(kref, vref);
1035
1098
  // that will also delete both db keys
1036
1099
  work.imports += 1;
1037
1100
  remaining -= 1;
@@ -1040,8 +1103,22 @@ export default function makeKernelKeeper(
1040
1103
  }
1041
1104
  }
1042
1105
 
1043
- // the caller used enumeratePromisesByDecider() before calling us,
1044
- // so they already know the orphaned promises to reject
1106
+ // The caller used enumeratePromisesByDecider() before calling us,
1107
+ // so they have already rejected the orphan promises, but those
1108
+ // kpids are still present in the dead vat's c-list. Clean those
1109
+ // up now.
1110
+ remaining = budget.promises ?? budget.default;
1111
+ for (const k of enumeratePrefixedKeys(kvStore, promisePrefix)) {
1112
+ const kref = kvStore.get(k) || Fail`getNextKey ensures get`;
1113
+ const vref = stripPrefix(clistPrefix, k);
1114
+ undertaker.deleteCListEntry(kref, vref);
1115
+ // that will also delete both db keys
1116
+ work.promises += 1;
1117
+ remaining -= 1;
1118
+ if (remaining <= 0) {
1119
+ return { done: false, work };
1120
+ }
1121
+ }
1045
1122
 
1046
1123
  // now loop back through everything and delete it all
1047
1124
  remaining = budget.kv ?? budget.default;
@@ -1056,7 +1133,7 @@ export default function makeKernelKeeper(
1056
1133
 
1057
1134
  // this will internally loop through 'budget' deletions
1058
1135
  remaining = budget.snapshots ?? budget.default;
1059
- const dsc = vatKeeper.deleteSnapshots(remaining);
1136
+ const dsc = undertaker.deleteSnapshots(remaining);
1060
1137
  work.snapshots += dsc.cleanups;
1061
1138
  remaining -= dsc.cleanups;
1062
1139
  if (remaining <= 0) {
@@ -1065,7 +1142,7 @@ export default function makeKernelKeeper(
1065
1142
 
1066
1143
  // same
1067
1144
  remaining = budget.transcripts ?? budget.default;
1068
- const dts = vatKeeper.deleteTranscripts(remaining);
1145
+ const dts = undertaker.deleteTranscripts(remaining);
1069
1146
  work.transcripts += dts.cleanups;
1070
1147
  remaining -= dts.cleanups;
1071
1148
  // last task, so increment cleanups, but dc.done is authoritative
@@ -1139,18 +1216,22 @@ export default function makeKernelKeeper(
1139
1216
  function setDecider(kpid, decider) {
1140
1217
  insistVatID(decider);
1141
1218
  const p = getKernelPromise(kpid);
1142
- p.state === 'unresolved' || Fail`${kpid} was already resolved`;
1143
- !p.decider || Fail`${kpid} has decider ${p.decider}, not empty`;
1219
+ assert.equal(p.state, 'unresolved', `${kpid} was already resolved`);
1220
+ assert(!p.decider, `${kpid} has decider ${p.decider}, not empty`);
1144
1221
  kvStore.set(`${kpid}.decider`, decider);
1145
1222
  }
1146
1223
 
1147
1224
  function clearDecider(kpid) {
1148
1225
  const p = getKernelPromise(kpid);
1149
- p.state === 'unresolved' || Fail`${kpid} was already resolved`;
1150
- p.decider || Fail`${kpid} does not have a decider`;
1226
+ assert.equal(p.state, 'unresolved', `${kpid} was already resolved`);
1227
+ assert(p.decider, `${kpid} does not have a decider`);
1151
1228
  kvStore.set(`${kpid}.decider`, '');
1152
1229
  }
1153
1230
 
1231
+ /**
1232
+ * @param {string} vatID
1233
+ * @returns {IterableIterator<[kpid: string, p: PromiseRecord]>}
1234
+ */
1154
1235
  function* enumeratePromisesByDecider(vatID) {
1155
1236
  insistVatID(vatID);
1156
1237
  const promisePrefix = `${vatID}.c.p`;
@@ -1164,10 +1245,10 @@ export default function makeKernelKeeper(
1164
1245
  // whether the vat is the decider or not. If it is, we add the promise
1165
1246
  // to the list of promises that must be rejected because the dead vat
1166
1247
  // will never be able to act upon them.
1167
- const kpid = kvStore.get(k);
1248
+ const kpid = getRequired(k);
1168
1249
  const p = getKernelPromise(kpid);
1169
1250
  if (p.state === 'unresolved' && p.decider === vatID) {
1170
- yield kpid;
1251
+ yield [kpid, p];
1171
1252
  }
1172
1253
  }
1173
1254
  }
@@ -1177,6 +1258,7 @@ export default function makeKernelKeeper(
1177
1258
  insistKernelType('promise', kernelSlot);
1178
1259
  insistVatID(vatID);
1179
1260
  const p = getKernelPromise(kernelSlot);
1261
+ assert.equal(p.state, 'unresolved');
1180
1262
  const s = new Set(p.subscribers);
1181
1263
  s.add(vatID);
1182
1264
  const v = Array.from(s).sort().join(',');
@@ -1207,6 +1289,27 @@ export default function makeKernelKeeper(
1207
1289
  return dequeue('acceptanceQueue');
1208
1290
  }
1209
1291
 
1292
+ function injectQueuedUpgradeEvents() {
1293
+ // refcounts: Any krefs in `upgradeEvents` must have a refcount to
1294
+ // represent the list's hold on those objects. When
1295
+ // upgradeSwingset() creates these events, it must also
1296
+ // incref(kref), otherwise we run the risk of dropping the kref by
1297
+ // the time injectQueuedUpgradeEvents() is called. We're nominally
1298
+ // removing each event from upgradeEvents (decref), then pushing
1299
+ // it onto the run-queue (incref), but since those two cancel each
1300
+ // other out, we don't actually need to modify any reference
1301
+ // counts from within this function. Note that
1302
+ // addToAcceptanceQueue does not increment refcounts, just kernel
1303
+ // queue-length stats.
1304
+
1305
+ const events = JSON.parse(kvStore.get('upgradeEvents') || '[]');
1306
+ kvStore.delete('upgradeEvents');
1307
+ for (const e of events) {
1308
+ assert(e.type, `not an event`);
1309
+ addToAcceptanceQueue(e);
1310
+ }
1311
+ }
1312
+
1210
1313
  function allocateMeter(remaining, threshold) {
1211
1314
  if (remaining !== 'unlimited') {
1212
1315
  assert.typeof(remaining, 'bigint');
@@ -1414,40 +1517,8 @@ export default function makeKernelKeeper(
1414
1517
  maybeFreeKrefs.add(kref);
1415
1518
  }
1416
1519
 
1417
- /**
1418
- * Increment the reference count associated with some kernel object.
1419
- *
1420
- * We track references to promises and objects, but not devices. Promises
1421
- * have only a "reachable" count, whereas objects track both "reachable"
1422
- * and "recognizable" counts.
1423
- *
1424
- * @param {unknown} kernelSlot The kernel slot whose refcount is to be incremented.
1425
- * @param {string?} tag Debugging note with rough source of the reference.
1426
- * @param {{ isExport?: boolean, onlyRecognizable?: boolean }} options
1427
- * 'isExport' means the reference comes from a clist export, which counts
1428
- * for promises but not objects. 'onlyRecognizable' means the reference
1429
- * provides only recognition, not reachability
1430
- */
1431
- function incrementRefCount(kernelSlot, tag, options = {}) {
1432
- const { isExport = false, onlyRecognizable = false } = options;
1433
- kernelSlot ||
1434
- Fail`incrementRefCount called with empty kernelSlot, tag=${tag}`;
1435
- const { type } = parseKernelSlot(kernelSlot);
1436
- if (type === 'promise') {
1437
- const refCount = Nat(BigInt(getRequired(`${kernelSlot}.refCount`))) + 1n;
1438
- // kdebug(`++ ${kernelSlot} ${tag} ${refCount}`);
1439
- kvStore.set(`${kernelSlot}.refCount`, `${refCount}`);
1440
- }
1441
- if (type === 'object' && !isExport) {
1442
- let { reachable, recognizable } = getObjectRefCount(kernelSlot);
1443
- if (!onlyRecognizable) {
1444
- reachable += 1;
1445
- }
1446
- recognizable += 1;
1447
- // kdebug(`++ ${kernelSlot} ${tag} ${reachable},${recognizable}`);
1448
- setObjectRefCount(kernelSlot, { reachable, recognizable });
1449
- }
1450
- }
1520
+ const incrementRefCount = (kref, tag, options = {}) =>
1521
+ incrementReferenceCount(getRequired, kvStore, kref, tag, options);
1451
1522
 
1452
1523
  /**
1453
1524
  * Decrement the reference count associated with some kernel object.
@@ -1539,13 +1610,15 @@ export default function makeKernelKeeper(
1539
1610
  const kp = getKernelPromise(kpid);
1540
1611
  if (kp.refCount === 0) {
1541
1612
  let idx = 0;
1542
- // TODO (#9889) don't assume promise is settled
1543
- for (const slot of kp.data.slots) {
1544
- // Note: the following decrement can result in an addition to the
1545
- // maybeFreeKrefs set, which we are in the midst of iterating.
1546
- // TC39 went to a lot of trouble to ensure that this is kosher.
1547
- decrementRefCount(slot, `gc|${kpid}|s${idx}`);
1548
- idx += 1;
1613
+ if (kp.state === 'fulfilled' || kp.state === 'rejected') {
1614
+ // #9889 don't assume promise is settled
1615
+ for (const slot of kp.data.slots) {
1616
+ // Note: the following decrement can result in an addition to the
1617
+ // maybeFreeKrefs set, which we are in the midst of iterating.
1618
+ // TC39 went to a lot of trouble to ensure that this is kosher.
1619
+ decrementRefCount(slot, `gc|${kpid}|s${idx}`);
1620
+ idx += 1;
1621
+ }
1549
1622
  }
1550
1623
  deleteKernelPromise(kpid);
1551
1624
  }
@@ -1626,6 +1699,26 @@ export default function makeKernelKeeper(
1626
1699
  initializeVatState(kvStore, transcriptStore, vatID, source, options);
1627
1700
  }
1628
1701
 
1702
+ /** @type {import('./vatKeeper.js').VatKeeperPowers} */
1703
+ const vatKeeperPowers = {
1704
+ transcriptStore,
1705
+ kernelSlog,
1706
+ addKernelObject,
1707
+ addKernelPromiseForVat,
1708
+ kernelObjectExists,
1709
+ incrementRefCount,
1710
+ decrementRefCount,
1711
+ getObjectRefCount,
1712
+ setObjectRefCount,
1713
+ getReachableAndVatSlot,
1714
+ addMaybeFreeKref,
1715
+ incStat,
1716
+ decStat,
1717
+ getCrankNumber,
1718
+ scheduleReap,
1719
+ snapStore,
1720
+ };
1721
+
1629
1722
  function provideVatKeeper(vatID) {
1630
1723
  insistVatID(vatID);
1631
1724
  const found = ephemeral.vatKeepers.get(vatID);
@@ -1633,30 +1726,36 @@ export default function makeKernelKeeper(
1633
1726
  return found;
1634
1727
  }
1635
1728
  assert(kvStore.has(`${vatID}.o.nextID`), `${vatID} was not initialized`);
1636
- const vk = makeVatKeeper(
1637
- kvStore,
1638
- transcriptStore,
1639
- kernelSlog,
1640
- vatID,
1641
- addKernelObject,
1642
- addKernelPromiseForVat,
1643
- kernelObjectExists,
1644
- incrementRefCount,
1645
- decrementRefCount,
1646
- getObjectRefCount,
1647
- setObjectRefCount,
1648
- getReachableAndVatSlot,
1649
- addMaybeFreeKref,
1650
- incStat,
1651
- decStat,
1652
- getCrankNumber,
1653
- scheduleReap,
1654
- snapStore,
1655
- );
1729
+ const vk = makeVatKeeper(vatID, kvStore, vatKeeperPowers);
1656
1730
  ephemeral.vatKeepers.set(vatID, vk);
1657
1731
  return vk;
1658
1732
  }
1659
1733
 
1734
+ /**
1735
+ * Produce an attenuated vatKeeper for slow vat termination (and that
1736
+ * therefore does not insist on liveness, unlike provideVatKeeper).
1737
+ *
1738
+ * @param {string} vatID
1739
+ */
1740
+ function provideVatUndertaker(vatID) {
1741
+ insistVatID(vatID);
1742
+ const found = ephemeral.vatUndertakers.get(vatID);
1743
+ if (found !== undefined) {
1744
+ return found;
1745
+ }
1746
+ const { deleteCListEntry, deleteSnapshots, deleteTranscripts } =
1747
+ ephemeral.vatKeepers.get(vatID) ||
1748
+ makeVatKeeper(vatID, kvStore, vatKeeperPowers);
1749
+ /** @type {VatUndertaker} */
1750
+ const undertaker = harden({
1751
+ deleteCListEntry,
1752
+ deleteSnapshots,
1753
+ deleteTranscripts,
1754
+ });
1755
+ ephemeral.vatUndertakers.set(vatID, undertaker);
1756
+ return undertaker;
1757
+ }
1758
+
1660
1759
  function vatIsAlive(vatID) {
1661
1760
  insistVatID(vatID);
1662
1761
  return kvStore.has(`${vatID}.o.nextID`) && !terminatedVats.includes(vatID);
@@ -1936,6 +2035,8 @@ export default function makeKernelKeeper(
1936
2035
  getAcceptanceQueueLength,
1937
2036
  getNextAcceptanceQueueMsg,
1938
2037
 
2038
+ injectQueuedUpgradeEvents,
2039
+
1939
2040
  allocateMeter,
1940
2041
  addMeterRemaining,
1941
2042
  setMeterThreshold,
@@ -1981,3 +2082,4 @@ export default function makeKernelKeeper(
1981
2082
  dump,
1982
2083
  });
1983
2084
  }
2085
+ /** @typedef {ReturnType<typeof makeKernelKeeper>} KernelKeeper */