@agoric/swingset-vat 0.32.3-dev-b4480d8.0 → 0.32.3-dev-ad7ac3c.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agoric/swingset-vat",
3
- "version": "0.32.3-dev-b4480d8.0+b4480d8",
3
+ "version": "0.32.3-dev-ad7ac3c.0+ad7ac3c",
4
4
  "description": "Vat/Container Launcher",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
@@ -27,16 +27,16 @@
27
27
  "@types/yargs-parser": "^21.0.0"
28
28
  },
29
29
  "dependencies": {
30
- "@agoric/internal": "0.3.3-dev-b4480d8.0+b4480d8",
31
- "@agoric/kmarshal": "0.1.1-dev-b4480d8.0+b4480d8",
32
- "@agoric/store": "0.9.3-dev-b4480d8.0+b4480d8",
33
- "@agoric/swing-store": "0.9.2-dev-b4480d8.0+b4480d8",
34
- "@agoric/swingset-liveslots": "0.10.3-dev-b4480d8.0+b4480d8",
35
- "@agoric/swingset-xsnap-supervisor": "0.10.3-dev-b4480d8.0+b4480d8",
36
- "@agoric/time": "0.3.3-dev-b4480d8.0+b4480d8",
37
- "@agoric/vat-data": "0.5.3-dev-b4480d8.0+b4480d8",
38
- "@agoric/xsnap": "0.14.3-dev-b4480d8.0+b4480d8",
39
- "@agoric/xsnap-lockdown": "0.14.1-dev-b4480d8.0+b4480d8",
30
+ "@agoric/internal": "0.3.3-dev-ad7ac3c.0+ad7ac3c",
31
+ "@agoric/kmarshal": "0.1.1-dev-ad7ac3c.0+ad7ac3c",
32
+ "@agoric/store": "0.9.3-dev-ad7ac3c.0+ad7ac3c",
33
+ "@agoric/swing-store": "0.9.2-dev-ad7ac3c.0+ad7ac3c",
34
+ "@agoric/swingset-liveslots": "0.10.3-dev-ad7ac3c.0+ad7ac3c",
35
+ "@agoric/swingset-xsnap-supervisor": "0.10.3-dev-ad7ac3c.0+ad7ac3c",
36
+ "@agoric/time": "0.3.3-dev-ad7ac3c.0+ad7ac3c",
37
+ "@agoric/vat-data": "0.5.3-dev-ad7ac3c.0+ad7ac3c",
38
+ "@agoric/xsnap": "0.14.3-dev-ad7ac3c.0+ad7ac3c",
39
+ "@agoric/xsnap-lockdown": "0.14.1-dev-ad7ac3c.0+ad7ac3c",
40
40
  "@endo/base64": "^1.0.8",
41
41
  "@endo/bundle-source": "^3.4.2",
42
42
  "@endo/captp": "^4.4.2",
@@ -103,5 +103,5 @@
103
103
  "typeCoverage": {
104
104
  "atLeast": 75.82
105
105
  },
106
- "gitHead": "b4480d8443ec4864a2deda7f67e186a7ff8e73ea"
106
+ "gitHead": "ad7ac3c0b5722cecd8bed5c3c554499a39a1d519"
107
107
  }
@@ -366,6 +366,10 @@ export async function makeSwingsetController(
366
366
  return kref;
367
367
  },
368
368
 
369
+ kpRegisterInterest(kpid) {
370
+ return kernel.kpRegisterInterest(kpid);
371
+ },
372
+
369
373
  kpStatus(kpid) {
370
374
  return kernel.kpStatus(kpid);
371
375
  },
@@ -384,6 +388,8 @@ export async function makeSwingsetController(
384
388
  return kernel.deviceNameToID(deviceName);
385
389
  },
386
390
 
391
+ injectQueuedUpgradeEvents: () => kernel.injectQueuedUpgradeEvents(),
392
+
387
393
  /**
388
394
  * Queue a method call into the named vat
389
395
  *
@@ -1,9 +1,17 @@
1
+ import { Fail } from '@endo/errors';
1
2
  import {
2
3
  DEFAULT_REAP_DIRT_THRESHOLD_KEY,
3
4
  DEFAULT_GC_KREFS_PER_BOYD,
4
5
  getAllDynamicVats,
5
6
  getAllStaticVats,
7
+ incrementReferenceCount,
8
+ readQueue,
6
9
  } from '../kernel/state/kernelKeeper.js';
10
+ import { enumeratePrefixedKeys } from '../kernel/state/storageHelper.js';
11
+
12
+ /**
13
+ * @import {RunQueueEvent} from '../types-internal.js';
14
+ */
7
15
 
8
16
  const upgradeVatV0toV1 = (kvStore, defaultReapDirtThreshold, vatID) => {
9
17
  // This is called, once per vat, when upgradeSwingset migrates from
@@ -92,11 +100,13 @@ const upgradeVatV0toV1 = (kvStore, defaultReapDirtThreshold, vatID) => {
92
100
  * `hostStorage.commit()` afterwards.
93
101
  *
94
102
  * @param {SwingStoreKernelStorage} kernelStorage
95
- * @returns {boolean} true if any changes were made
103
+ * @returns {{ modified: boolean }}
96
104
  */
97
105
  export const upgradeSwingset = kernelStorage => {
98
106
  const { kvStore } = kernelStorage;
99
107
  let modified = false;
108
+ /** @type {RunQueueEvent[]} */
109
+ const upgradeEvents = [];
100
110
  let vstring = kvStore.get('version');
101
111
  if (vstring === undefined) {
102
112
  vstring = '0';
@@ -204,9 +214,129 @@ export const upgradeSwingset = kernelStorage => {
204
214
  version = 2;
205
215
  }
206
216
 
217
+ if (version < 3) {
218
+ // v3 means that we've completed remediation for bug #9039
219
+ console.log(`Starting remediation of bug #9039`);
220
+
221
+ // find all terminated vats
222
+ const terminated = new Set(JSON.parse(getRequired('vats.terminated')));
223
+
224
+ // find all live vats
225
+ const allVatIDs = [];
226
+ for (const [_name, vatID] of getAllStaticVats(kvStore)) {
227
+ if (!terminated.has(vatID)) {
228
+ allVatIDs.push(vatID);
229
+ }
230
+ }
231
+ for (const vatID of getAllDynamicVats(getRequired)) {
232
+ if (!terminated.has(vatID)) {
233
+ allVatIDs.push(vatID);
234
+ }
235
+ }
236
+
237
+ // find all pending notifies
238
+ const notifies = new Map(); // .get(kpid) = [vatIDs..];
239
+ for (const name of ['runQueue', 'acceptanceQueue']) {
240
+ for (const rq of readQueue(name, getRequired)) {
241
+ if (rq.type === 'notify') {
242
+ const { vatID, kpid } = rq;
243
+ assert(vatID);
244
+ assert(kpid);
245
+ let vats = notifies.get(kpid);
246
+ if (!vats) {
247
+ vats = [];
248
+ notifies.set(kpid, vats);
249
+ }
250
+ vats.push(vatID);
251
+ }
252
+ }
253
+ }
254
+ console.log(` - pending notifies:`, notifies);
255
+
256
+ // cache of known-settled kpids: will grow to num(kpids)
257
+ const KPIDStatus = new Map();
258
+ const isSettled = kpid => {
259
+ if (KPIDStatus.has(kpid)) {
260
+ return KPIDStatus.get(kpid);
261
+ }
262
+ const state = kvStore.get(`${kpid}.state`);
263
+ // missing state means the kpid is deleted somehow, shouldn't happen
264
+ state || Fail`${kpid}.state is missing`;
265
+ const settled = state !== 'unresolved';
266
+ KPIDStatus.set(kpid, settled);
267
+ return settled;
268
+ };
269
+
270
+ // walk vNN.c.kpNN for all vats, for each one check the
271
+ // kpNN.state, for the settled ones check for a pending notify,
272
+ // record the ones without a pending notify
273
+
274
+ const buggyKPIDs = []; // [kpid, vatID]
275
+ for (const vatID of allVatIDs) {
276
+ const prefix = `${vatID}.c.`;
277
+ const len = prefix.length;
278
+ const ckpPrefix = `${vatID}.c.kp`;
279
+ for (const key of enumeratePrefixedKeys(kvStore, ckpPrefix)) {
280
+ const kpid = key.slice(len);
281
+ if (isSettled(kpid)) {
282
+ const n = notifies.get(kpid);
283
+ if (!n || !n.includes(vatID)) {
284
+ // there is no pending notify
285
+ buggyKPIDs.push([kpid, vatID]);
286
+ }
287
+ }
288
+ }
289
+ }
290
+ console.log(` - found ${buggyKPIDs.length} buggy kpids, enqueueing fixes`);
291
+
292
+ // now fix it. The bug means we failed to delete the c-list entry
293
+ // and decref it back when the promise was rejected. That decref
294
+ // would have pushed the kpid onto maybeFreeKrefs, which would
295
+ // have triggered a refcount check at end-of-crank, which might
296
+ // have deleted the promise records (if nothing else was
297
+ // referencing the promise, like arguments in messages enqueued to
298
+ // unresolved promises, or something transient on the
299
+ // run-queue). Deleting those promise records might have decreffed
300
+ // krefs in the rejection data (although in general 9039 rejects
301
+ // those promises with non-slot-bearing DisconnectionObjects).
302
+ //
303
+ // To avoid duplicating a lot of kernel code inside this upgrade
304
+ // handler, we do the simplest possible thing: enqueue a notify to
305
+ // the upgraded vat for all these leftover promises. The new vat
306
+ // incarnation will ignore it (they don't recognize the vpid), but
307
+ // the dispatch.notify() delivery will clear the c-list and decref
308
+ // the kpid, and will trigger all the usual GC work. Note that
309
+ // these notifies will be delivered before any activity the host
310
+ // app might trigger for e.g. a chain upgrade, but they should not
311
+ // cause userspace-visible behavior (non-slot-bearing rejection
312
+ // data means no other vat will even get a gc-action delivery:
313
+ // only the upgraded vat will see anything, and those deliveries
314
+ // won't make it past liveslots).
315
+
316
+ let count = 0;
317
+ for (const [kpid, vatID] of buggyKPIDs) {
318
+ // account for the reference to this kpid in upgradeEvents
319
+ incrementReferenceCount(getRequired, kvStore, kpid, `enq|notify`);
320
+ upgradeEvents.push({ type: 'notify', vatID, kpid });
321
+ count += 1;
322
+ }
323
+
324
+ console.log(` - #9039 remediation complete, ${count} notifies to inject`);
325
+ modified = true;
326
+ version = 3;
327
+ }
328
+
329
+ if (upgradeEvents.length) {
330
+ assert(modified);
331
+ // stash until host calls controller.injectQueuedUpgradeEvents()
332
+ const oldEvents = JSON.parse(kvStore.get('upgradeEvents') || '[]');
333
+ const events = [...oldEvents, ...upgradeEvents];
334
+ kvStore.set('upgradeEvents', JSON.stringify(events));
335
+ }
336
+
207
337
  if (modified) {
208
338
  kvStore.set('version', `${version}`);
209
339
  }
210
- return modified;
340
+ return harden({ modified });
211
341
  };
212
342
  harden(upgradeSwingset);
@@ -215,6 +215,10 @@ export default function buildKernel(
215
215
  return deviceID;
216
216
  }
217
217
 
218
+ function injectQueuedUpgradeEvents() {
219
+ kernelKeeper.injectQueuedUpgradeEvents();
220
+ }
221
+
218
222
  function addImport(forVatID, what) {
219
223
  if (!started) {
220
224
  throw Error('must do kernel.start() before addImport()');
@@ -287,15 +291,16 @@ export default function buildKernel(
287
291
  const vatKeeper = kernelKeeper.provideVatKeeper(vatID);
288
292
  critical = vatKeeper.getOptions().critical;
289
293
 
290
- // Reject all promises decided by the vat, making sure to capture the list
291
- // of kpids before that data is deleted.
292
- const deadPromises = [...kernelKeeper.enumeratePromisesByDecider(vatID)];
293
- // remove vatID from the list of live vats, and mark for deletion
294
+ // remove vatID from the list of live vats, and mark for
295
+ // deletion (which will happen later, in vat-cleanup events)
294
296
  kernelKeeper.deleteVatID(vatID);
295
297
  kernelKeeper.markVatAsTerminated(vatID);
296
298
  deferred.push(kernelKeeper.removeVatFromSwingStoreExports(vatID));
297
- for (const kpid of deadPromises) {
298
- resolveToError(kpid, makeError('vat terminated'), vatID);
299
+
300
+ // Reject all promises decided by the vat
301
+ const errdata = makeError('vat terminated');
302
+ for (const [kpid, _p] of kernelKeeper.enumeratePromisesByDecider(vatID)) {
303
+ resolveToError(kpid, errdata, vatID);
299
304
  }
300
305
  }
301
306
  if (critical) {
@@ -597,7 +602,9 @@ export default function buildKernel(
597
602
  const p = kernelKeeper.getKernelPromise(kpid);
598
603
  kernelKeeper.incStat('dispatchNotify');
599
604
  const vatKeeper = kernelKeeper.provideVatKeeper(vatID);
600
- p.state !== 'unresolved' || Fail`spurious notification ${kpid}`;
605
+ if (p.state === 'unresolved') {
606
+ throw Fail`spurious notification ${kpid}`;
607
+ }
601
608
  /** @type { KernelDeliveryOneNotify[] } */
602
609
  const resolutions = [];
603
610
  if (!vatKeeper.hasCListEntry(kpid)) {
@@ -612,7 +619,9 @@ export default function buildKernel(
612
619
  return NO_DELIVERY_CRANK_RESULTS;
613
620
  }
614
621
  for (const toResolve of targets) {
615
- const { state, data } = kernelKeeper.getKernelPromise(toResolve);
622
+ const tp = kernelKeeper.getKernelPromise(toResolve);
623
+ assert(tp.state !== 'unresolved');
624
+ const { state, data } = tp;
616
625
  resolutions.push([toResolve, { state, data }]);
617
626
  }
618
627
  /** @type { KernelDeliveryNotify } */
@@ -995,9 +1004,30 @@ export default function buildKernel(
995
1004
  return results;
996
1005
  }
997
1006
 
998
- // reject all promises for which the vat was decider
999
- for (const kpid of kernelKeeper.enumeratePromisesByDecider(vatID)) {
1000
- resolveToError(kpid, disconnectionCapData, vatID);
1007
+ // We are homesick for a future in which most promises are
1008
+ // durable, and vats do not need to subscribe to their own
1009
+ // promises to make promise-watchers work. In that world, vats
1010
+ // somehow mark the non-durable promises, which we must
1011
+ // reject/disconnect on their behalf during upgrade.
1012
+ //
1013
+ // To handle the present reality, without durable promises, we
1014
+ // pretend that all promises are so marked.
1015
+
1016
+ // reject all ephemeral promises for which the vat was decider
1017
+ for (const [kpid, p] of kernelKeeper.enumeratePromisesByDecider(vatID)) {
1018
+ const isEphemeral = true; // future vats will mark these explicitly
1019
+ const selfSubscribed =
1020
+ p.state === 'unresolved' && p.subscribers.includes(vatID);
1021
+ if (isEphemeral) {
1022
+ resolveToError(kpid, disconnectionCapData, vatID);
1023
+ if (!selfSubscribed) {
1024
+ // If the vat was subscribed to its own promise, the
1025
+ // resolveToError will enqueue a dispatch.notify, whose delivery
1026
+ // will delete the c-list entry. If it is *not* subscribed, we
1027
+ // should delete the c-list entry now, because nobody else will.
1028
+ vatKeeper.deleteCListEntriesForKernelSlots([kpid]);
1029
+ }
1030
+ }
1001
1031
  }
1002
1032
 
1003
1033
  // simulate an abandonExports syscall from the vat,
@@ -1186,6 +1216,7 @@ export default function buildKernel(
1186
1216
  }
1187
1217
  }
1188
1218
  default:
1219
+ // @ts-expect-error
1189
1220
  throw Fail`unknown promise resolution '${kp.state}'`;
1190
1221
  }
1191
1222
  }
@@ -2081,6 +2112,7 @@ export default function buildKernel(
2081
2112
  return p.data;
2082
2113
  }
2083
2114
  default: {
2115
+ // @ts-expect-error
2084
2116
  throw Fail`invalid state for ${kpid}: ${p.state}`;
2085
2117
  }
2086
2118
  }
@@ -2179,6 +2211,7 @@ export default function buildKernel(
2179
2211
  pinObject,
2180
2212
  vatNameToID,
2181
2213
  deviceNameToID,
2214
+ injectQueuedUpgradeEvents,
2182
2215
  queueToKref,
2183
2216
  kpRegisterInterest,
2184
2217
  kpStatus,
@@ -45,14 +45,16 @@ const enableKernelGC = true;
45
45
  * @typedef { import('../../types-external.js').VatKeeper } VatKeeper
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
@@ -141,6 +143,7 @@ export const CURRENT_SCHEMA_VERSION = 2;
141
143
  // gcActions = JSON(gcActions)
142
144
  // reapQueue = JSON([vatIDs...])
143
145
  // pinnedObjects = ko$NN[,ko$NN..]
146
+ // upgradeEvents = JSON([events..])
144
147
 
145
148
  // ko.nextID = $NN
146
149
  // ko$NN.owner = $vatID
@@ -177,6 +180,11 @@ export const CURRENT_SCHEMA_VERSION = 2;
177
180
  // v2:
178
181
  // * change `version` to `'2'`
179
182
  // * add `vats.terminated` with `[]` as initial value
183
+ // v3:
184
+ // * change `version` to `'3'`
185
+ // * perform remediation for bug #9039
186
+ // (after v3, does not get its own version)
187
+ // * `upgradeEvents` recognized, but omitted if empty
180
188
 
181
189
  /** @type {(s: string) => string[]} s */
182
190
  export function commaSplit(s) {
@@ -212,6 +220,77 @@ export const getAllDynamicVats = getRequired => {
212
220
  return JSON.parse(getRequired('vat.dynamicIDs'));
213
221
  };
214
222
 
223
+ const getObjectReferenceCount = (kvStore, kref) => {
224
+ const data = kvStore.get(`${kref}.refCount`);
225
+ if (!data) {
226
+ return { reachable: 0, recognizable: 0 };
227
+ }
228
+ const [reachable, recognizable] = commaSplit(data).map(Number);
229
+ reachable <= recognizable ||
230
+ Fail`refmismatch(get) ${kref} ${reachable},${recognizable}`;
231
+ return { reachable, recognizable };
232
+ };
233
+
234
+ const setObjectReferenceCount = (kvStore, kref, counts) => {
235
+ const { reachable, recognizable } = counts;
236
+ assert.typeof(reachable, 'number');
237
+ assert.typeof(recognizable, 'number');
238
+ (reachable >= 0 && recognizable >= 0) ||
239
+ Fail`${kref} underflow ${reachable},${recognizable}`;
240
+ reachable <= recognizable ||
241
+ Fail`refmismatch(set) ${kref} ${reachable},${recognizable}`;
242
+ kvStore.set(`${kref}.refCount`, `${reachable},${recognizable}`);
243
+ };
244
+
245
+ /**
246
+ * Increment the reference count associated with some kernel object.
247
+ *
248
+ * We track references to promises and objects, but not devices. Promises
249
+ * have only a "reachable" count, whereas objects track both "reachable"
250
+ * and "recognizable" counts.
251
+ *
252
+ * @param { (key: string) => string} getRequired
253
+ * @param { import('@agoric/swing-store').KVStore } kvStore
254
+ * @param {string} kref The kernel slot whose refcount is to be incremented.
255
+ * @param {string?} tag Debugging note with rough source of the reference.
256
+ * @param {{ isExport?: boolean, onlyRecognizable?: boolean }} options
257
+ * 'isExport' means the reference comes from a clist export, which counts
258
+ * for promises but not objects. 'onlyRecognizable' means the reference
259
+ * provides only recognition, not reachability
260
+ */
261
+ export const incrementReferenceCount = (
262
+ getRequired,
263
+ kvStore,
264
+ kref,
265
+ tag,
266
+ options = {},
267
+ ) => {
268
+ const { isExport = false, onlyRecognizable = false } = options;
269
+ kref || Fail`incrementRefCount called with empty kref, tag=${tag}`;
270
+ const { type } = parseKernelSlot(kref);
271
+ if (type === 'promise') {
272
+ const refCount = Number(getRequired(`${kref}.refCount`)) + 1;
273
+ // kdebug(`++ ${kref} ${tag} ${refCount}`);
274
+ kvStore.set(`${kref}.refCount`, `${refCount}`);
275
+ }
276
+ if (type === 'object' && !isExport) {
277
+ let { reachable, recognizable } = getObjectReferenceCount(kvStore, kref);
278
+ if (!onlyRecognizable) {
279
+ reachable += 1;
280
+ }
281
+ recognizable += 1;
282
+ // kdebug(`++ ${kref} ${tag} ${reachable},${recognizable}`);
283
+ setObjectReferenceCount(kvStore, kref, { reachable, recognizable });
284
+ }
285
+ };
286
+
287
+ export function* readQueue(queue, getRequired) {
288
+ const [head, tail] = JSON.parse(getRequired(`${queue}`));
289
+ for (let i = head; i < tail; i += 1) {
290
+ yield JSON.parse(getRequired(`${queue}.${i}`));
291
+ }
292
+ }
293
+
215
294
  // we use different starting index values for the various vNN/koNN/kdNN/kpNN
216
295
  // slots, to reduce confusing overlap when looking at debug messages (e.g.
217
296
  // seeing both kp1 and ko1, which are completely unrelated despite having the
@@ -389,14 +468,7 @@ export default function makeKernelKeeper(
389
468
  return tail - head;
390
469
  }
391
470
 
392
- function dumpQueue(queue) {
393
- const [head, tail] = JSON.parse(getRequired(`${queue}`));
394
- const result = [];
395
- for (let i = head; i < tail; i += 1) {
396
- result.push(JSON.parse(getRequired(`${queue}.${i}`)));
397
- }
398
- return result;
399
- }
471
+ const dumpQueue = queue => [...readQueue(queue, getRequired)];
400
472
 
401
473
  /**
402
474
  * @param {InternalKernelOptions} kernelOptions
@@ -620,26 +692,9 @@ export default function makeKernelKeeper(
620
692
  return parseReachableAndVatSlot(kvStore.get(kernelKey));
621
693
  }
622
694
 
623
- function getObjectRefCount(kernelSlot) {
624
- const data = kvStore.get(`${kernelSlot}.refCount`);
625
- if (!data) {
626
- return { reachable: 0, recognizable: 0 };
627
- }
628
- const [reachable, recognizable] = commaSplit(data).map(Number);
629
- reachable <= recognizable ||
630
- Fail`refmismatch(get) ${kernelSlot} ${reachable},${recognizable}`;
631
- return { reachable, recognizable };
632
- }
633
-
634
- function setObjectRefCount(kernelSlot, { reachable, recognizable }) {
635
- assert.typeof(reachable, 'number');
636
- assert.typeof(recognizable, 'number');
637
- (reachable >= 0 && recognizable >= 0) ||
638
- Fail`${kernelSlot} underflow ${reachable},${recognizable}`;
639
- reachable <= recognizable ||
640
- Fail`refmismatch(set) ${kernelSlot} ${reachable},${recognizable}`;
641
- kvStore.set(`${kernelSlot}.refCount`, `${reachable},${recognizable}`);
642
- }
695
+ const getObjectRefCount = kref => getObjectReferenceCount(kvStore, kref);
696
+ const setObjectRefCount = (kref, counts) =>
697
+ setObjectReferenceCount(kvStore, kref, counts);
643
698
 
644
699
  /**
645
700
  * Iterate over non-durable objects exported by a vat.
@@ -791,45 +846,41 @@ export default function makeKernelKeeper(
791
846
  return kpid;
792
847
  }
793
848
 
849
+ /**
850
+ * @param {string} kernelSlot
851
+ * @returns {PromiseRecord}
852
+ */
794
853
  function getKernelPromise(kernelSlot) {
795
854
  insistKernelType('promise', kernelSlot);
796
- const p = { state: getRequired(`${kernelSlot}.state`) };
797
- switch (p.state) {
798
- case undefined: {
799
- throw Fail`unknown kernelPromise '${kernelSlot}'`;
800
- }
855
+ const state = getRequired(`${kernelSlot}.state`);
856
+ const refCount = Number(kvStore.get(`${kernelSlot}.refCount`));
857
+ switch (state) {
801
858
  case 'unresolved': {
802
- p.refCount = Number(kvStore.get(`${kernelSlot}.refCount`));
803
- p.decider = kvStore.get(`${kernelSlot}.decider`);
804
- if (p.decider === '') {
805
- p.decider = undefined;
806
- }
807
- p.policy = kvStore.get(`${kernelSlot}.policy`) || 'ignore';
808
- // @ts-expect-error get() may fail
809
- p.subscribers = commaSplit(kvStore.get(`${kernelSlot}.subscribers`));
810
- p.queue = Array.from(
859
+ const decider = kvStore.get(`${kernelSlot}.decider`) || undefined;
860
+ const policy = kvStore.get(`${kernelSlot}.policy`) || 'ignore';
861
+ const subscribers = commaSplit(
862
+ kvStore.get(`${kernelSlot}.subscribers`) || '',
863
+ );
864
+ const queue = Array.from(
811
865
  getPrefixedValues(kvStore, `${kernelSlot}.queue.`),
812
866
  ).map(s => JSON.parse(s));
813
- break;
867
+ return harden({ state, refCount, decider, policy, subscribers, queue });
814
868
  }
815
869
  case 'fulfilled':
816
870
  case 'rejected': {
817
- p.refCount = Number(kvStore.get(`${kernelSlot}.refCount`));
818
- p.data = {
819
- body: kvStore.get(`${kernelSlot}.data.body`),
820
- // @ts-expect-error get() may fail
821
- slots: commaSplit(kvStore.get(`${kernelSlot}.data.slots`)),
871
+ const data = {
872
+ body: getRequired(`${kernelSlot}.data.body`),
873
+ slots: commaSplit(getRequired(`${kernelSlot}.data.slots`)),
822
874
  };
823
- for (const s of p.data.slots) {
875
+ for (const s of data.slots) {
824
876
  parseKernelSlot(s);
825
877
  }
826
- break;
878
+ return harden({ state, refCount, data });
827
879
  }
828
880
  default: {
829
- throw Fail`unknown state for ${kernelSlot}: ${p.state}`;
881
+ throw Fail`unknown state for ${kernelSlot}: ${state}`;
830
882
  }
831
883
  }
832
- return harden(p);
833
884
  }
834
885
 
835
886
  function getResolveablePromise(kpid, expectedDecider) {
@@ -838,7 +889,9 @@ export default function makeKernelKeeper(
838
889
  insistVatID(expectedDecider);
839
890
  }
840
891
  const p = getKernelPromise(kpid);
841
- p.state === 'unresolved' || Fail`${kpid} was already resolved`;
892
+ if (p.state !== 'unresolved') {
893
+ throw Fail`${kpid} was already resolved`;
894
+ }
842
895
  if (expectedDecider) {
843
896
  p.decider === expectedDecider ||
844
897
  Fail`${kpid} is decided by ${p.decider}, not ${expectedDecider}`;
@@ -897,6 +950,7 @@ export default function makeKernelKeeper(
897
950
  // up the resolution *now* and set the correct target early. Doing that
898
951
  // might make it easier to remove the Promise Table entry earlier.
899
952
  const p = getKernelPromise(kernelSlot);
953
+ assert.equal(p.state, 'unresolved');
900
954
  for (const msg of p.queue) {
901
955
  const entry = harden({ type: 'send', target: kernelSlot, msg });
902
956
  enqueue('acceptanceQueue', entry);
@@ -1142,18 +1196,22 @@ export default function makeKernelKeeper(
1142
1196
  function setDecider(kpid, decider) {
1143
1197
  insistVatID(decider);
1144
1198
  const p = getKernelPromise(kpid);
1145
- p.state === 'unresolved' || Fail`${kpid} was already resolved`;
1146
- !p.decider || Fail`${kpid} has decider ${p.decider}, not empty`;
1199
+ assert.equal(p.state, 'unresolved', `${kpid} was already resolved`);
1200
+ assert(!p.decider, `${kpid} has decider ${p.decider}, not empty`);
1147
1201
  kvStore.set(`${kpid}.decider`, decider);
1148
1202
  }
1149
1203
 
1150
1204
  function clearDecider(kpid) {
1151
1205
  const p = getKernelPromise(kpid);
1152
- p.state === 'unresolved' || Fail`${kpid} was already resolved`;
1153
- p.decider || Fail`${kpid} does not have a decider`;
1206
+ assert.equal(p.state, 'unresolved', `${kpid} was already resolved`);
1207
+ assert(p.decider, `${kpid} does not have a decider`);
1154
1208
  kvStore.set(`${kpid}.decider`, '');
1155
1209
  }
1156
1210
 
1211
+ /**
1212
+ * @param {string} vatID
1213
+ * @returns {IterableIterator<[kpid: string, p: PromiseRecord]>}
1214
+ */
1157
1215
  function* enumeratePromisesByDecider(vatID) {
1158
1216
  insistVatID(vatID);
1159
1217
  const promisePrefix = `${vatID}.c.p`;
@@ -1167,10 +1225,10 @@ export default function makeKernelKeeper(
1167
1225
  // whether the vat is the decider or not. If it is, we add the promise
1168
1226
  // to the list of promises that must be rejected because the dead vat
1169
1227
  // will never be able to act upon them.
1170
- const kpid = kvStore.get(k);
1228
+ const kpid = getRequired(k);
1171
1229
  const p = getKernelPromise(kpid);
1172
1230
  if (p.state === 'unresolved' && p.decider === vatID) {
1173
- yield kpid;
1231
+ yield [kpid, p];
1174
1232
  }
1175
1233
  }
1176
1234
  }
@@ -1180,6 +1238,7 @@ export default function makeKernelKeeper(
1180
1238
  insistKernelType('promise', kernelSlot);
1181
1239
  insistVatID(vatID);
1182
1240
  const p = getKernelPromise(kernelSlot);
1241
+ assert.equal(p.state, 'unresolved');
1183
1242
  const s = new Set(p.subscribers);
1184
1243
  s.add(vatID);
1185
1244
  const v = Array.from(s).sort().join(',');
@@ -1210,6 +1269,27 @@ export default function makeKernelKeeper(
1210
1269
  return dequeue('acceptanceQueue');
1211
1270
  }
1212
1271
 
1272
+ function injectQueuedUpgradeEvents() {
1273
+ // refcounts: Any krefs in `upgradeEvents` must have a refcount to
1274
+ // represent the list's hold on those objects. When
1275
+ // upgradeSwingset() creates these events, it must also
1276
+ // incref(kref), otherwise we run the risk of dropping the kref by
1277
+ // the time injectQueuedUpgradeEvents() is called. We're nominally
1278
+ // removing each event from upgradeEvents (decref), then pushing
1279
+ // it onto the run-queue (incref), but since those two cancel each
1280
+ // other out, we don't actually need to modify any reference
1281
+ // counts from within this function. Note that
1282
+ // addToAcceptanceQueue does not increment refcounts, just kernel
1283
+ // queue-length stats.
1284
+
1285
+ const events = JSON.parse(kvStore.get('upgradeEvents') || '[]');
1286
+ kvStore.delete('upgradeEvents');
1287
+ for (const e of events) {
1288
+ assert(e.type, `not an event`);
1289
+ addToAcceptanceQueue(e);
1290
+ }
1291
+ }
1292
+
1213
1293
  function allocateMeter(remaining, threshold) {
1214
1294
  if (remaining !== 'unlimited') {
1215
1295
  assert.typeof(remaining, 'bigint');
@@ -1417,40 +1497,8 @@ export default function makeKernelKeeper(
1417
1497
  maybeFreeKrefs.add(kref);
1418
1498
  }
1419
1499
 
1420
- /**
1421
- * Increment the reference count associated with some kernel object.
1422
- *
1423
- * We track references to promises and objects, but not devices. Promises
1424
- * have only a "reachable" count, whereas objects track both "reachable"
1425
- * and "recognizable" counts.
1426
- *
1427
- * @param {unknown} kernelSlot The kernel slot whose refcount is to be incremented.
1428
- * @param {string?} tag Debugging note with rough source of the reference.
1429
- * @param {{ isExport?: boolean, onlyRecognizable?: boolean }} options
1430
- * 'isExport' means the reference comes from a clist export, which counts
1431
- * for promises but not objects. 'onlyRecognizable' means the reference
1432
- * provides only recognition, not reachability
1433
- */
1434
- function incrementRefCount(kernelSlot, tag, options = {}) {
1435
- const { isExport = false, onlyRecognizable = false } = options;
1436
- kernelSlot ||
1437
- Fail`incrementRefCount called with empty kernelSlot, tag=${tag}`;
1438
- const { type } = parseKernelSlot(kernelSlot);
1439
- if (type === 'promise') {
1440
- const refCount = Nat(BigInt(getRequired(`${kernelSlot}.refCount`))) + 1n;
1441
- // kdebug(`++ ${kernelSlot} ${tag} ${refCount}`);
1442
- kvStore.set(`${kernelSlot}.refCount`, `${refCount}`);
1443
- }
1444
- if (type === 'object' && !isExport) {
1445
- let { reachable, recognizable } = getObjectRefCount(kernelSlot);
1446
- if (!onlyRecognizable) {
1447
- reachable += 1;
1448
- }
1449
- recognizable += 1;
1450
- // kdebug(`++ ${kernelSlot} ${tag} ${reachable},${recognizable}`);
1451
- setObjectRefCount(kernelSlot, { reachable, recognizable });
1452
- }
1453
- }
1500
+ const incrementRefCount = (kref, tag, options = {}) =>
1501
+ incrementReferenceCount(getRequired, kvStore, kref, tag, options);
1454
1502
 
1455
1503
  /**
1456
1504
  * Decrement the reference count associated with some kernel object.
@@ -1542,13 +1590,15 @@ export default function makeKernelKeeper(
1542
1590
  const kp = getKernelPromise(kpid);
1543
1591
  if (kp.refCount === 0) {
1544
1592
  let idx = 0;
1545
- // TODO (#9889) don't assume promise is settled
1546
- for (const slot of kp.data.slots) {
1547
- // Note: the following decrement can result in an addition to the
1548
- // maybeFreeKrefs set, which we are in the midst of iterating.
1549
- // TC39 went to a lot of trouble to ensure that this is kosher.
1550
- decrementRefCount(slot, `gc|${kpid}|s${idx}`);
1551
- idx += 1;
1593
+ if (kp.state === 'fulfilled' || kp.state === 'rejected') {
1594
+ // #9889 don't assume promise is settled
1595
+ for (const slot of kp.data.slots) {
1596
+ // Note: the following decrement can result in an addition to the
1597
+ // maybeFreeKrefs set, which we are in the midst of iterating.
1598
+ // TC39 went to a lot of trouble to ensure that this is kosher.
1599
+ decrementRefCount(slot, `gc|${kpid}|s${idx}`);
1600
+ idx += 1;
1601
+ }
1552
1602
  }
1553
1603
  deleteKernelPromise(kpid);
1554
1604
  }
@@ -1939,6 +1989,8 @@ export default function makeKernelKeeper(
1939
1989
  getAcceptanceQueueLength,
1940
1990
  getNextAcceptanceQueueMsg,
1941
1991
 
1992
+ injectQueuedUpgradeEvents,
1993
+
1942
1994
  allocateMeter,
1943
1995
  addMeterRemaining,
1944
1996
  setMeterThreshold,
@@ -50,6 +50,7 @@ function makeTranslateKernelDeliveryToVatDelivery(vatID, kernelKeeper) {
50
50
  parseVatSlot(targetSlot).allocatedByVat || Fail`deliver() to wrong vat`;
51
51
  } else if (type === 'promise') {
52
52
  const p = kernelKeeper.getKernelPromise(target);
53
+ assert(p.state === 'unresolved');
53
54
  p.decider === vatID || Fail`wrong decider`;
54
55
  }
55
56
  const inputSlots = msg.methargs.slots.map(slot =>
@@ -59,7 +60,9 @@ function makeTranslateKernelDeliveryToVatDelivery(vatID, kernelKeeper) {
59
60
  if (msg.result) {
60
61
  insistKernelType('promise', msg.result);
61
62
  const p = kernelKeeper.getKernelPromise(msg.result);
62
- p.state === 'unresolved' || Fail`result ${msg.result} already resolved`;
63
+ if (p.state !== 'unresolved') {
64
+ throw Fail`result ${msg.result} already resolved`;
65
+ }
63
66
  !p.decider || Fail`result ${msg.result} already has decider ${p.decider}`;
64
67
  resultSlot = vatKeeper.mapKernelSlotToVatSlot(msg.result);
65
68
  insistVatType('promise', resultSlot);
@@ -318,8 +321,9 @@ function makeTranslateVatSyscallToKernelSyscall(vatID, kernelKeeper) {
318
321
  // In the case of non-pipelining vats these checks are redundant since
319
322
  // we're guaranteed to have a promise newly allocated by the vat.
320
323
  const p = kernelKeeper.getKernelPromise(result);
321
- p.state === 'unresolved' ||
322
- Fail`send() result ${result} is already resolved`;
324
+ if (p.state !== 'unresolved') {
325
+ throw Fail`send() result ${result} is already resolved`;
326
+ }
323
327
  p.decider === vatID ||
324
328
  Fail`send() result ${result} is decided by ${p.decider} not ${vatID}`;
325
329
  kernelKeeper.clearDecider(result);
@@ -92,6 +92,17 @@ export {};
92
92
  * @property {number} [computrons]
93
93
  */
94
94
 
95
+ /**
96
+ * @typedef {{ state: 'unresolved', refCount: number,
97
+ * decider: string | undefined, policy: string,
98
+ * subscribers: string[], queue: string[],
99
+ * }} UnresolvedPromiseRecord
100
+ * @typedef {{ state: 'fulfilled' | 'rejected', refCount: number,
101
+ * data: SwingSetCapData,
102
+ * }} SettledPromiseRecord
103
+ * @typedef {UnresolvedPromiseRecord | SettledPromiseRecord} PromiseRecord
104
+ */
105
+
95
106
  /**
96
107
  * @typedef {{
97
108
  * enablePipelining: boolean,