From 5a4ddb34d4372afa9dbdcfd69053603071dcb1d8 Mon Sep 17 00:00:00 2001 From: Brian Warner Date: Wed, 5 Apr 2023 00:33:11 -0700 Subject: [PATCH 1/6] liveslots: flush all caches after BOYD, not just stateCache --- packages/swingset-liveslots/src/liveslots.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/swingset-liveslots/src/liveslots.js b/packages/swingset-liveslots/src/liveslots.js index cf8029bdf84..62aea84e43c 100644 --- a/packages/swingset-liveslots/src/liveslots.js +++ b/packages/swingset-liveslots/src/liveslots.js @@ -1537,7 +1537,8 @@ function build( async function bringOutYourDead() { await scanForDeadObjects(); // now flush all the vatstore changes (deletions) we made - vom.flushStateCache(); + // eslint-disable-next-line no-use-before-define + afterDispatchActions(); // XXX TODO: make this conditional on a config setting return getRetentionStats(); } From ee0128d3c0d8fb5645cc2e27b4bb4eada097610c Mon Sep 17 00:00:00 2001 From: Brian Warner Date: Wed, 5 Apr 2023 00:33:00 -0700 Subject: [PATCH 2/6] cache: assert that all keys are strings --- packages/swingset-liveslots/src/cache.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/swingset-liveslots/src/cache.js b/packages/swingset-liveslots/src/cache.js index 72a7ff0ff71..4186aa9a464 100644 --- a/packages/swingset-liveslots/src/cache.js +++ b/packages/swingset-liveslots/src/cache.js @@ -64,6 +64,7 @@ export function makeCache(readBacking, writeBacking, deleteBacking) { const dirtyKeys = new Set(); const cache = { get: key => { + assert.typeof(key, 'string'); if (stash.has(key)) { return stash.get(key); } else if (dirtyKeys.has(key)) { @@ -75,10 +76,12 @@ export function makeCache(readBacking, writeBacking, deleteBacking) { return value; }, set: (key, value) => { + assert.typeof(key, 'string'); stash.set(key, value); dirtyKeys.add(key); }, delete: key => { + assert.typeof(key, 'string'); stash.delete(key); dirtyKeys.add(key); }, From 39eb8d500431705a0649f4efa17b3835eabb7ed9 Mon Sep 17 00:00:00 2001 From: Brian Warner Date: Wed, 5 Apr 2023 00:35:22 -0700 Subject: [PATCH 3/6] be consistent about collectionID being a string --- .../src/collectionManager.js | 21 +++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/packages/swingset-liveslots/src/collectionManager.js b/packages/swingset-liveslots/src/collectionManager.js index 319388940a3..ef9ec78a6d3 100644 --- a/packages/swingset-liveslots/src/collectionManager.js +++ b/packages/swingset-liveslots/src/collectionManager.js @@ -645,7 +645,13 @@ export function makeCollectionManager( const { id, subid } = parseVatSlot(vobjID); const kindName = storeKindIDToName.get(`${id}`); kindName || Fail`unknown kind ID ${id}`; - const collection = summonCollectionInternal(false, 'test', subid, kindName); + const collectionID = `${subid}`; + const collection = summonCollectionInternal( + false, + 'test', + collectionID, + kindName, + ); return collection.sizeInternal(); } @@ -655,7 +661,13 @@ export function makeCollectionManager( } const { id, subid } = parseVatSlot(vobjID); const kindName = storeKindIDToName.get(`${id}`); - const collection = summonCollectionInternal(false, 'GC', subid, kindName); + const collectionID = `${subid}`; + const collection = summonCollectionInternal( + false, + 'GC', + collectionID, + kindName, + ); allCollectionObjIDs.delete(vobjID); const doMoreGC = collection.clearInternal(true); @@ -686,7 +698,7 @@ export function makeCollectionManager( assertPattern(valueShape); schemata.push(valueShape); } - const collectionID = allocateCollectionID(); + const collectionID = `${allocateCollectionID()}`; const kindID = obtainStoreKindID(kindName); const vobjID = makeBaseRef(kindID, collectionID, isDurable); @@ -908,6 +920,7 @@ export function makeCollectionManager( function reanimateCollection(vobjID) { const { id, subid } = parseVatSlot(vobjID); + const collectionID = `${subid}`; const kindName = storeKindIDToName.get(`${id}`); const rawSchemata = JSON.parse( syscall.vatstoreGet(prefixc(subid, '|schemata')), @@ -917,7 +930,7 @@ export function makeCollectionManager( return summonCollection( false, label, - subid, + collectionID, kindName, keyShape, valueShape, From c44f1e50f5a4a0594e840bed56958ad29546c7da Mon Sep 17 00:00:00 2001 From: Brian Warner Date: Tue, 4 Apr 2023 22:44:29 -0700 Subject: [PATCH 4/6] remove in-RAM list of all collections This removes `deleteAllVirtualCollections`, and the in-RAM `allCollectionObjIDs` Set of all collectionIDs which supported it. Once upon a time, `stopVat` called `deleteAllVirtualCollections` to delete the vatstore data for all non-durable virtual collections. We removed responsibility for deleting virtual data from the vat (and stopped calling `stopVat` altogether) in the interests of reducing upgrade risk, and improving upgrade performance, despite the fact that this will cause a DB storage leak during upgrade (we figure we can find a way to fix that later, and disk is cheap). Now that `deleteAllVirtualCollections` is gone, we don't need to spend the RAM on each collection, which violated our high-cardinality design rules anyways. refs #5058 --- .../src/collectionManager.js | 24 ------------------- 1 file changed, 24 deletions(-) diff --git a/packages/swingset-liveslots/src/collectionManager.js b/packages/swingset-liveslots/src/collectionManager.js index ef9ec78a6d3..23ecdd96176 100644 --- a/packages/swingset-liveslots/src/collectionManager.js +++ b/packages/swingset-liveslots/src/collectionManager.js @@ -58,12 +58,6 @@ export function makeCollectionManager( unserialize, assertAcceptableSyscallCapdataSize, ) { - // TODO(#5058): we hold a list of all collections (both virtual and - // durable) in RAM, so we can delete the virtual ones during - // stopVat(), and tolerate subsequent GC-triggered duplication - // deletion without crashing. This needs to move to the DB to avoid - // the RAM consumption of a large number of collections. - const allCollectionObjIDs = new Set(); const storeKindIDToName = new Map(); const storeKindInfo = { @@ -656,9 +650,6 @@ export function makeCollectionManager( } function deleteCollection(vobjID) { - if (!allCollectionObjIDs.has(vobjID)) { - return false; // already deleted - } const { id, subid } = parseVatSlot(vobjID); const kindName = storeKindIDToName.get(`${id}`); const collectionID = `${subid}`; @@ -668,7 +659,6 @@ export function makeCollectionManager( collectionID, kindName, ); - allCollectionObjIDs.delete(vobjID); const doMoreGC = collection.clearInternal(true); for (const dbKey of enumerateKeysWithPrefix(syscall, prefixc(subid, '|'))) { @@ -677,18 +667,6 @@ export function makeCollectionManager( return doMoreGC; } - function deleteAllVirtualCollections() { - const vobjIDs = Array.from(allCollectionObjIDs).sort(); - for (const vobjID of vobjIDs) { - const { id } = parseVatSlot(vobjID); - const kindName = storeKindIDToName.get(`${id}`); - const { durable } = storeKindInfo[kindName]; - if (!durable) { - deleteCollection(vobjID); - } - } - } - function makeCollection(label, kindName, isDurable, keyShape, valueShape) { assert.typeof(label, 'string'); assert(storeKindInfo[kindName]); @@ -712,7 +690,6 @@ export function makeCollectionManager( JSON.stringify(serialize(harden(schemata))), ); syscall.vatstoreSet(prefixc(collectionID, '|label'), label); - allCollectionObjIDs.add(vobjID); return [ vobjID, @@ -1031,7 +1008,6 @@ export function makeCollectionManager( return harden({ initializeStoreKindInfo, - deleteAllVirtualCollections, makeScalarBigMapStore, makeScalarBigWeakMapStore, makeScalarBigSetStore, From 0830fff90b571d7ac141305ca87a251adb602669 Mon Sep 17 00:00:00 2001 From: Brian Warner Date: Wed, 5 Apr 2023 00:47:19 -0700 Subject: [PATCH 5/6] cache the collection schema, only read from vatstore once per crank This changes the collectionManager to keep the per-collection metadata (keyShape, valueShape, label) in a cache. Like the VOM's dataCache, this `schemaCache` is populated at most once per crank, and flushed at the end of every crank. This removes the GC-sensitive syscalls that used to fetch the metadata when userspace caused a collection to be reanimated. Instead, the cache is read when userspace invokes a collection method that needs the data (most of them, to validate keys or values), regardless of whether the collection Representative remains in RAM or not. It also changes the serialized form of the schemata to be an object like `{ keyShape, valueShape }` instead of an array like `[keyShape, valueShape]`. If the constraints are missing, the object is empty, which is smaller to serialize. I'm also thinking this might make it more extensible. closes #6360 --- .../virtualObjects/test-representatives.js | 8 +- .../src/collectionManager.js | 189 ++++++++++-------- packages/swingset-liveslots/src/liveslots.js | 5 +- .../test/test-gc-sensitivity.js | 2 +- .../test/test-handled-promises.js | 8 +- .../test/test-initial-vrefs.js | 8 +- .../swingset-liveslots/test/test-liveslots.js | 18 ++ 7 files changed, 145 insertions(+), 93 deletions(-) diff --git a/packages/SwingSet/test/virtualObjects/test-representatives.js b/packages/SwingSet/test/virtualObjects/test-representatives.js index 0336c6d0d22..c39bdb4ebd7 100644 --- a/packages/SwingSet/test/virtualObjects/test-representatives.js +++ b/packages/SwingSet/test/virtualObjects/test-representatives.js @@ -454,19 +454,19 @@ test('virtual object gc', async t => { [`${v}.vs.vc.1.|entryCount`]: '0', [`${v}.vs.vc.1.|label`]: 'baggage', [`${v}.vs.vc.1.|nextOrdinal`]: '1', - [`${v}.vs.vc.1.|schemata`]: vstr([M.string()]), + [`${v}.vs.vc.1.|schemata`]: vstr({ keyShape: M.string() }), [`${v}.vs.vc.2.|entryCount`]: '0', [`${v}.vs.vc.2.|label`]: 'promiseRegistrations', [`${v}.vs.vc.2.|nextOrdinal`]: '1', - [`${v}.vs.vc.2.|schemata`]: vstr([M.scalar()]), + [`${v}.vs.vc.2.|schemata`]: vstr({ keyShape: M.scalar() }), [`${v}.vs.vc.3.|entryCount`]: '0', [`${v}.vs.vc.3.|label`]: 'promiseWatcherByKind', [`${v}.vs.vc.3.|nextOrdinal`]: '1', - [`${v}.vs.vc.3.|schemata`]: vstr([M.scalar()]), + [`${v}.vs.vc.3.|schemata`]: vstr({ keyShape: M.scalar() }), [`${v}.vs.vc.4.|entryCount`]: '0', [`${v}.vs.vc.4.|label`]: 'watchedPromises', [`${v}.vs.vc.4.|nextOrdinal`]: '1', - [`${v}.vs.vc.4.|schemata`]: vstr([M.and(M.scalar(), M.string())]), + [`${v}.vs.vc.4.|schemata`]: vstr({ keyShape: M.and(M.scalar(), M.string()) }), [`${v}.vs.vom.es.o+v10/3`]: 'r', [`${v}.vs.vom.o+v10/2`]: `{"label":${vstr('thing #2')}}`, [`${v}.vs.vom.o+v10/3`]: `{"label":${vstr('thing #3')}}`, diff --git a/packages/swingset-liveslots/src/collectionManager.js b/packages/swingset-liveslots/src/collectionManager.js index 23ecdd96176..15972f12733 100644 --- a/packages/swingset-liveslots/src/collectionManager.js +++ b/packages/swingset-liveslots/src/collectionManager.js @@ -21,6 +21,7 @@ import { enumerateKeysStartEnd, enumerateKeysWithPrefix, } from './vatstore-iterators.js'; +import { makeCache } from './cache.js'; // XXX TODO: The following key length limit was put in place due to limitations // in LMDB. With the move away from LMDB, it is no longer relevant, but I'm @@ -46,6 +47,58 @@ function throwNotDurable(value, slotIndex, serializedValue) { Fail`value is not durable: ${value} at slot ${q(slotIndex)} of ${serializedValue.body}`; } +function prefixc(collectionID, dbEntryKey) { + return `vc.${collectionID}.${dbEntryKey}`; +} + +/** + * @typedef {object} SchemaCacheValue + * @property {Pattern} keyShape + * @property {Pattern} valueShape + * @property {string} label + * @property {object} schemataCapData + */ + +/* + * Build a cache that holds the schema for each collection. + * + * The cache maps collectionID to { keyShape, valueShape, label, + * schemataCapData }. These are initialized when the collection is + * first constructed, and never modified afterwards. The values live + * in the vatstore, inside two keys, one for the [keyShape, + * valueShape] schemata, another for the label. + */ +function makeSchemaCache(syscall, unserialize) { + /** @type {(collectionID: string) => SchemaCacheValue} */ + const readBacking = collectionID => { + // this is only called once per crank + const schemataKey = prefixc(collectionID, '|schemata'); + const schemataValue = syscall.vatstoreGet(schemataKey); + const schemataCapData = JSON.parse(schemataValue); + const { keyShape, valueShape } = unserialize(schemataCapData); + const labelKey = prefixc(collectionID, '|label'); + const label = syscall.vatstoreGet(labelKey); + return harden({ keyShape, valueShape, label, schemataCapData }); + }; + /** @type {(collectionID: string, value: SchemaCacheValue) => void } */ + const writeBacking = (collectionID, value) => { + const { label, schemataCapData } = value; + const schemataKey = prefixc(collectionID, '|schemata'); + const schemataValue = JSON.stringify(schemataCapData); + syscall.vatstoreSet(schemataKey, schemataValue); + const labelKey = prefixc(collectionID, '|label'); + syscall.vatstoreSet(labelKey, label); + }; + /** @type {(collectionID: string) => void} */ + const deleteBacking = collectionID => { + const schemataKey = prefixc(collectionID, '|schemata'); + const labelKey = prefixc(collectionID, '|label'); + syscall.vatstoreDelete(schemataKey); + syscall.vatstoreDelete(labelKey); + }; + return makeCache(readBacking, writeBacking, deleteBacking); +} + export function makeCollectionManager( syscall, vrm, @@ -60,6 +113,9 @@ export function makeCollectionManager( ) { const storeKindIDToName = new Map(); + /** @type { import('./cache.js').Cache} */ + const schemaCache = makeSchemaCache(syscall, unserialize); + const storeKindInfo = { scalarMapStore: { hasWeakKeys: false, @@ -119,10 +175,6 @@ export function makeCollectionManager( }, }; - function prefixc(collectionID, dbEntryKey) { - return `vc.${collectionID}.${dbEntryKey}`; - } - function initializeStoreKindInfo() { let storeKindIDs = {}; const rawTable = syscall.vatstoreGet('storeKindIDTable'); @@ -181,35 +233,33 @@ export function makeCollectionManager( } vrm.setDeleteCollectionEntry(deleteCollectionEntry); - function summonCollectionInternal( - _initial, - label, - collectionID, - kindName, - keyShape = M.any(), - valueShape, - ) { + function summonCollectionInternal(_initial, collectionID, kindName) { assert.typeof(kindName, 'string'); const kindInfo = storeKindInfo[kindName]; kindInfo || Fail`unknown collection kind ${kindName}`; const { hasWeakKeys, durable } = kindInfo; + const getSchema = () => schemaCache.get(collectionID); const dbKeyPrefix = `vc.${collectionID}.`; let currentGenerationNumber = 0; - const invalidKeyTypeMsg = `invalid key type for collection ${q(label)}`; - const invalidValueTypeMsg = `invalid value type for collection ${q(label)}`; + const makeInvalidKeyTypeMsg = label => + `invalid key type for collection ${q(label)}`; + const makeInvalidValueTypeMsg = label => + `invalid value type for collection ${q(label)}`; const serializeValue = value => { + const { valueShape, label } = getSchema(); if (valueShape !== undefined) { - mustMatch(value, valueShape, invalidValueTypeMsg); + mustMatch(value, valueShape, makeInvalidValueTypeMsg(label)); } return serialize(value); }; const unserializeValue = data => { + const { valueShape, label } = getSchema(); const value = unserialize(data); if (valueShape !== undefined) { - mustMatch(value, valueShape, invalidValueTypeMsg); + mustMatch(value, valueShape, makeInvalidValueTypeMsg(label)); } return value; }; @@ -277,6 +327,7 @@ export function makeCollectionManager( } function has(key) { + const { keyShape } = getSchema(); if (!matches(key, keyShape)) { return false; } @@ -288,7 +339,8 @@ export function makeCollectionManager( } function get(key) { - mustMatch(key, keyShape, invalidKeyTypeMsg); + const { keyShape, label } = getSchema(); + mustMatch(key, keyShape, makeInvalidKeyTypeMsg(label)); if (passStyleOf(key) === 'remotable' && getOrdinal(key) === undefined) { throw Fail`key ${key} not found in collection ${q(label)}`; } @@ -310,7 +362,8 @@ export function makeCollectionManager( } const doInit = (key, value, precheckedHas) => { - mustMatch(key, keyShape, invalidKeyTypeMsg); + const { keyShape, label } = getSchema(); + mustMatch(key, keyShape, makeInvalidKeyTypeMsg(label)); precheckedHas || !has(key) || Fail`key ${key} already registered in collection ${q(label)}`; @@ -350,7 +403,8 @@ export function makeCollectionManager( }; function set(key, value) { - mustMatch(key, keyShape, invalidKeyTypeMsg); + const { keyShape, label } = getSchema(); + mustMatch(key, keyShape, makeInvalidKeyTypeMsg(label)); const after = serializeValue(harden(value)); assertAcceptableSyscallCapdataSize([after]); if (durable) { @@ -369,7 +423,8 @@ export function makeCollectionManager( } function deleteInternal(key) { - mustMatch(key, keyShape, invalidKeyTypeMsg); + const { keyShape, label } = getSchema(); + mustMatch(key, keyShape, makeInvalidKeyTypeMsg(label)); if (passStyleOf(key) === 'remotable' && getOrdinal(key) === undefined) { throw Fail`key ${key} not found in collection ${q(label)}`; } @@ -580,23 +635,9 @@ export function makeCollectionManager( }; } - function summonCollection( - initial, - label, - collectionID, - kindName, - keyShape, - valueShape, - ) { + function summonCollection(initial, collectionID, kindName) { const hasWeakKeys = storeKindInfo[kindName].hasWeakKeys; - const raw = summonCollectionInternal( - initial, - label, - collectionID, - kindName, - keyShape, - valueShape, - ); + const raw = summonCollectionInternal(initial, collectionID, kindName); const { has, get, init, addToSet, set, delete: del } = raw; const weakMethods = { @@ -640,12 +681,7 @@ export function makeCollectionManager( const kindName = storeKindIDToName.get(`${id}`); kindName || Fail`unknown kind ID ${id}`; const collectionID = `${subid}`; - const collection = summonCollectionInternal( - false, - 'test', - collectionID, - kindName, - ); + const collection = summonCollectionInternal(false, collectionID, kindName); return collection.sizeInternal(); } @@ -653,17 +689,23 @@ export function makeCollectionManager( const { id, subid } = parseVatSlot(vobjID); const kindName = storeKindIDToName.get(`${id}`); const collectionID = `${subid}`; - const collection = summonCollectionInternal( - false, - 'GC', - collectionID, - kindName, - ); + const collection = summonCollectionInternal(false, collectionID, kindName); const doMoreGC = collection.clearInternal(true); - for (const dbKey of enumerateKeysWithPrefix(syscall, prefixc(subid, '|'))) { + for (const dbKey of enumerateKeysWithPrefix( + syscall, + prefixc(collectionID, '|'), + )) { + // these two keys are owned by schemaCache, and will be deleted + // when schemaCache is flushed + if (dbKey.endsWith('|schemata') || dbKey.endsWith('|label')) { + continue; + } + // but we must still delete the other keys (|nextOrdinal and + // |entryCount) syscall.vatstoreDelete(dbKey); } + schemaCache.delete(collectionID); return doMoreGC; } @@ -671,10 +713,8 @@ export function makeCollectionManager( assert.typeof(label, 'string'); assert(storeKindInfo[kindName]); assertPattern(keyShape); - const schemata = [keyShape]; if (valueShape) { assertPattern(valueShape); - schemata.push(valueShape); } const collectionID = `${allocateCollectionID()}`; const kindID = obtainStoreKindID(kindName); @@ -685,23 +725,21 @@ export function makeCollectionManager( if (!hasWeakKeys) { syscall.vatstoreSet(prefixc(collectionID, '|entryCount'), '0'); } - syscall.vatstoreSet( - prefixc(collectionID, '|schemata'), - JSON.stringify(serialize(harden(schemata))), + + const schemata = {}; // don't populate 'undefined', keep it small + if (keyShape !== undefined) { + schemata.keyShape = keyShape; + } + if (valueShape !== undefined) { + schemata.valueShape = valueShape; + } + const schemataCapData = serialize(harden(schemata)); + schemaCache.set( + collectionID, + harden({ keyShape, valueShape, label, schemataCapData }), ); - syscall.vatstoreSet(prefixc(collectionID, '|label'), label); - - return [ - vobjID, - summonCollection( - true, - label, - collectionID, - kindName, - keyShape, - valueShape, - ), - ]; + + return [vobjID, summonCollection(true, collectionID, kindName)]; } function collectionToMapStore(collection) { @@ -899,19 +937,7 @@ export function makeCollectionManager( const { id, subid } = parseVatSlot(vobjID); const collectionID = `${subid}`; const kindName = storeKindIDToName.get(`${id}`); - const rawSchemata = JSON.parse( - syscall.vatstoreGet(prefixc(subid, '|schemata')), - ); - const [keyShape, valueShape] = unserialize(rawSchemata); - const label = syscall.vatstoreGet(prefixc(subid, '|label')); - return summonCollection( - false, - label, - collectionID, - kindName, - keyShape, - valueShape, - ); + return summonCollection(false, collectionID, kindName); } function reanimateMapStore(vobjID) { @@ -1002,6 +1028,8 @@ export function makeCollectionManager( const makeScalarBigWeakSetStore = (label = 'weakSet', options = {}) => makeBigWeakSetStore(label, narrowKeyShapeOption(M.scalar(), options)); + const flushSchemaCache = () => schemaCache.flush(); + function getRetentionStats() { return {}; } @@ -1013,6 +1041,7 @@ export function makeCollectionManager( makeScalarBigSetStore, makeScalarBigWeakSetStore, provideBaggage, + flushSchemaCache, getRetentionStats, testHooks, }); diff --git a/packages/swingset-liveslots/src/liveslots.js b/packages/swingset-liveslots/src/liveslots.js index 62aea84e43c..b85cc40c40e 100644 --- a/packages/swingset-liveslots/src/liveslots.js +++ b/packages/swingset-liveslots/src/liveslots.js @@ -1536,7 +1536,9 @@ function build( async function bringOutYourDead() { await scanForDeadObjects(); - // now flush all the vatstore changes (deletions) we made + // Now flush all the vatstore changes (deletions and refcounts) we + // made. dispatch() calls afterDispatchActions() automatically for + // most methods, but not bringOutYourDead(). // eslint-disable-next-line no-use-before-define afterDispatchActions(); // XXX TODO: make this conditional on a config setting @@ -1557,6 +1559,7 @@ function build( */ function afterDispatchActions() { flushIDCounters(); + collectionManager.flushSchemaCache(); vom.flushStateCache(); } diff --git a/packages/swingset-liveslots/test/test-gc-sensitivity.js b/packages/swingset-liveslots/test/test-gc-sensitivity.js index 4aaf486b63d..332602d73b8 100644 --- a/packages/swingset-liveslots/test/test-gc-sensitivity.js +++ b/packages/swingset-liveslots/test/test-gc-sensitivity.js @@ -146,7 +146,7 @@ test('representative reanimation', async t => { t.deepEqual(noGCLog, yesGCLog); }); -test.failing('collection reanimation', async t => { +test('collection reanimation', async t => { const { syscall, log } = buildSyscall(); const gcTools = makeMockGC(); diff --git a/packages/swingset-liveslots/test/test-handled-promises.js b/packages/swingset-liveslots/test/test-handled-promises.js index db1aafbaed8..272093ea9b2 100644 --- a/packages/swingset-liveslots/test/test-handled-promises.js +++ b/packages/swingset-liveslots/test/test-handled-promises.js @@ -141,18 +141,18 @@ const kvStoreDataV1 = Object.entries({ 'vc.1.|label': 'baggage', 'vc.1.|nextOrdinal': '1', 'vc.1.|schemata': - '{"body":"#[{\\"#tag\\":\\"match:string\\",\\"payload\\":[]}]","slots":[]}', + '{"body":"#{\\"keyShape\\":{\\"#tag\\":\\"match:string\\",\\"payload\\":[]}}","slots":[]}', // non-durable // 'vc.2.sp+6': '{"body":"#\\"&0\\"","slots":["p+6"]}', // 'vc.2.|entryCount': '1', // 'vc.2.|label': 'promiseRegistrations', // 'vc.2.|nextOrdinal': '1', - // 'vc.2.|schemata': '{"body":"#[{\\"#tag\\":\\"match:scalar\\",\\"payload\\":\\"#undefined\\"}]","slots":[]}', + // 'vc.2.|schemata': '{"body":"#{\\"keyShape\\":{\\"#tag\\":\\"match:scalar\\",\\"payload\\":\\"#undefined\\"}}","slots":[]}', 'vc.3.|entryCount': '0', 'vc.3.|label': 'promiseWatcherByKind', 'vc.3.|nextOrdinal': '1', 'vc.3.|schemata': - '{"body":"#[{\\"#tag\\":\\"match:scalar\\",\\"payload\\":\\"#undefined\\"}]","slots":[]}', + '{"body":"#{\\"keyShape\\":{\\"#tag\\":\\"match:scalar\\",\\"payload\\":\\"#undefined\\"}}","slots":[]}', 'vc.4.sp+6': '{"body":"#[[\\"$0.Alleged: DurablePromiseIgnorer\\",\\"orphaned\\"]]","slots":["o+d10/1"]}', 'vc.4.sp-8': @@ -163,7 +163,7 @@ const kvStoreDataV1 = Object.entries({ 'vc.4.|label': 'watchedPromises', 'vc.4.|nextOrdinal': '1', 'vc.4.|schemata': - '{"body":"#[{\\"#tag\\":\\"match:and\\",\\"payload\\":[{\\"#tag\\":\\"match:scalar\\",\\"payload\\":\\"#undefined\\"},{\\"#tag\\":\\"match:string\\",\\"payload\\":[]}]}]","slots":[]}', + '{"body":"#{\\"keyShape\\":{\\"#tag\\":\\"match:and\\",\\"payload\\":[{\\"#tag\\":\\"match:scalar\\",\\"payload\\":\\"#undefined\\"},{\\"#tag\\":\\"match:string\\",\\"payload\\":[]}]}}","slots":[]}', 'vom.dkind.10': '{"kindID":"10","tag":"DurablePromiseIgnorer","nextInstanceID":2,"unfaceted":true}', 'vom.o+d10/1': '{}', diff --git a/packages/swingset-liveslots/test/test-initial-vrefs.js b/packages/swingset-liveslots/test/test-initial-vrefs.js index 04ce20281a6..acf7f7a8181 100644 --- a/packages/swingset-liveslots/test/test-initial-vrefs.js +++ b/packages/swingset-liveslots/test/test-initial-vrefs.js @@ -67,7 +67,7 @@ test('initial vatstore contents', async t => { t.is(get(`vc.1.|entryCount`), '0'); // no entries yet t.is(get(`vc.1.|nextOrdinal`), '1'); // no ordinals yet t.is(get(`vc.1.|entryCount`), '0'); - const stringSchema = [M.string()]; + const stringSchema = { keyShape: M.string() }; t.deepEqual(kunser(JSON.parse(get(`vc.1.|schemata`))), stringSchema); // then three tables for the promise watcher (one virtual, two durable) @@ -89,11 +89,13 @@ test('initial vatstore contents', async t => { t.is(get(`vom.rc.${watchedPromiseTableVref}`), '1'); // promiseRegistrations and promiseWatcherByKind arbitrary scalars as keys - const scalarSchema = [M.scalar()]; + const scalarSchema = { keyShape: M.scalar() }; t.deepEqual(kunser(JSON.parse(get(`vc.2.|schemata`))), scalarSchema); t.deepEqual(kunser(JSON.parse(get(`vc.3.|schemata`))), scalarSchema); // watchedPromises uses vref (string) keys - const scalarStringSchema = [M.and(M.scalar(), M.string())]; + const scalarStringSchema = { + keyShape: M.and(M.scalar(), M.string()), + }; t.deepEqual(kunser(JSON.parse(get(`vc.4.|schemata`))), scalarStringSchema); }); diff --git a/packages/swingset-liveslots/test/test-liveslots.js b/packages/swingset-liveslots/test/test-liveslots.js index 6e9fc2710c5..ed6cb33bdd6 100644 --- a/packages/swingset-liveslots/test/test-liveslots.js +++ b/packages/swingset-liveslots/test/test-liveslots.js @@ -5,6 +5,7 @@ import { E } from '@endo/eventual-send'; import { Far } from '@endo/marshal'; import { makePromiseKit } from '@endo/promise-kit'; import { Fail } from '@agoric/assert'; +import { M } from '@agoric/store'; import { makeLiveSlots, makeMarshaller } from '../src/liveslots.js'; import { kslot, kser, kunser } from './kmarshal.js'; import { buildSyscall, makeDispatch } from './liveslots-helpers.js'; @@ -709,8 +710,22 @@ test('capdata size limit on syscalls', async t => { expectStore(15); t.deepEqual(log, []); + const gotSchema = () => { + t.deepEqual(log.shift(), { + type: 'vatstoreGet', + key: 'vc.5.|schemata', + result: JSON.stringify(kser({ keyShape: M.scalar() })), + }); + t.deepEqual(log.shift(), { + type: 'vatstoreGet', + key: 'vc.5.|label', + result: 'test', + }); + }; + rp = nextRP(); await send('storeInitTooManySlots'); + gotSchema(); t.deepEqual(log.shift(), { type: 'vatstoreGet', key: 'vc.5.skey', @@ -722,6 +737,7 @@ test('capdata size limit on syscalls', async t => { rp = nextRP(); await send('storeInitBodyTooBig'); + gotSchema(); t.deepEqual(log.shift(), { type: 'vatstoreGet', key: 'vc.5.skey', @@ -733,12 +749,14 @@ test('capdata size limit on syscalls', async t => { rp = nextRP(); await send('storeSetTooManySlots'); + gotSchema(); expectFail(); expectVoidReturn(); t.deepEqual(log, []); rp = nextRP(); await send('storeSetBodyTooBig'); + gotSchema(); expectFail(); expectVoidReturn(); t.deepEqual(log, []); From 2faa0a3c9297b7490a951ad3276c5760633c3fa1 Mon Sep 17 00:00:00 2001 From: Brian Warner Date: Thu, 6 Apr 2023 23:20:54 -0700 Subject: [PATCH 6/6] swingset-liveslots: merge '|label' into '|schemata' Virtual collections need to record a label and a keyShape/valueShape schema. Previously these were kept in two separate vatstore keys, `|label` and `|schemata`. With this change, they're both stored in the `|schemata` key. This should reduce the number of vatstore reads necessary to work with a collection. --- .../virtualObjects/test-representatives.js | 12 ++++------ .../src/collectionManager.js | 18 +++++---------- .../swingset-liveslots/src/vatstore-usage.md | 5 ++-- .../swingset-liveslots/test/gc-helpers.js | 1 - .../test/storeGC/test-lifecycle.js | 10 ++++---- .../swingset-liveslots/test/test-baggage.js | 5 +++- .../test/test-handled-promises.js | 12 ++++------ .../test/test-initial-vrefs.js | 23 +++++++++++-------- .../test/test-liveslots-mock-gc.js | 1 - .../swingset-liveslots/test/test-liveslots.js | 8 ++----- 10 files changed, 41 insertions(+), 54 deletions(-) diff --git a/packages/SwingSet/test/virtualObjects/test-representatives.js b/packages/SwingSet/test/virtualObjects/test-representatives.js index c39bdb4ebd7..b4214c889bb 100644 --- a/packages/SwingSet/test/virtualObjects/test-representatives.js +++ b/packages/SwingSet/test/virtualObjects/test-representatives.js @@ -452,21 +452,17 @@ test('virtual object gc', async t => { [`${v}.vs.storeKindIDTable`]: '{"scalarMapStore":2,"scalarWeakMapStore":3,"scalarSetStore":4,"scalarWeakSetStore":5,"scalarDurableMapStore":6,"scalarDurableWeakMapStore":7,"scalarDurableSetStore":8,"scalarDurableWeakSetStore":9}', [`${v}.vs.vc.1.|entryCount`]: '0', - [`${v}.vs.vc.1.|label`]: 'baggage', [`${v}.vs.vc.1.|nextOrdinal`]: '1', - [`${v}.vs.vc.1.|schemata`]: vstr({ keyShape: M.string() }), + [`${v}.vs.vc.1.|schemata`]: vstr({ label: 'baggage', keyShape: M.string() }), [`${v}.vs.vc.2.|entryCount`]: '0', - [`${v}.vs.vc.2.|label`]: 'promiseRegistrations', [`${v}.vs.vc.2.|nextOrdinal`]: '1', - [`${v}.vs.vc.2.|schemata`]: vstr({ keyShape: M.scalar() }), + [`${v}.vs.vc.2.|schemata`]: vstr({ label: 'promiseRegistrations', keyShape: M.scalar() }), [`${v}.vs.vc.3.|entryCount`]: '0', - [`${v}.vs.vc.3.|label`]: 'promiseWatcherByKind', [`${v}.vs.vc.3.|nextOrdinal`]: '1', - [`${v}.vs.vc.3.|schemata`]: vstr({ keyShape: M.scalar() }), + [`${v}.vs.vc.3.|schemata`]: vstr({ label: 'promiseWatcherByKind', keyShape: M.scalar() }), [`${v}.vs.vc.4.|entryCount`]: '0', - [`${v}.vs.vc.4.|label`]: 'watchedPromises', [`${v}.vs.vc.4.|nextOrdinal`]: '1', - [`${v}.vs.vc.4.|schemata`]: vstr({ keyShape: M.and(M.scalar(), M.string()) }), + [`${v}.vs.vc.4.|schemata`]: vstr({ label: 'watchedPromises', keyShape: M.and(M.scalar(), M.string()) }), [`${v}.vs.vom.es.o+v10/3`]: 'r', [`${v}.vs.vom.o+v10/2`]: `{"label":${vstr('thing #2')}}`, [`${v}.vs.vom.o+v10/3`]: `{"label":${vstr('thing #3')}}`, diff --git a/packages/swingset-liveslots/src/collectionManager.js b/packages/swingset-liveslots/src/collectionManager.js index 15972f12733..fb63f280ff4 100644 --- a/packages/swingset-liveslots/src/collectionManager.js +++ b/packages/swingset-liveslots/src/collectionManager.js @@ -75,26 +75,20 @@ function makeSchemaCache(syscall, unserialize) { const schemataKey = prefixc(collectionID, '|schemata'); const schemataValue = syscall.vatstoreGet(schemataKey); const schemataCapData = JSON.parse(schemataValue); - const { keyShape, valueShape } = unserialize(schemataCapData); - const labelKey = prefixc(collectionID, '|label'); - const label = syscall.vatstoreGet(labelKey); + const { label, keyShape, valueShape } = unserialize(schemataCapData); return harden({ keyShape, valueShape, label, schemataCapData }); }; /** @type {(collectionID: string, value: SchemaCacheValue) => void } */ const writeBacking = (collectionID, value) => { - const { label, schemataCapData } = value; + const { schemataCapData } = value; const schemataKey = prefixc(collectionID, '|schemata'); const schemataValue = JSON.stringify(schemataCapData); syscall.vatstoreSet(schemataKey, schemataValue); - const labelKey = prefixc(collectionID, '|label'); - syscall.vatstoreSet(labelKey, label); }; /** @type {(collectionID: string) => void} */ const deleteBacking = collectionID => { const schemataKey = prefixc(collectionID, '|schemata'); - const labelKey = prefixc(collectionID, '|label'); syscall.vatstoreDelete(schemataKey); - syscall.vatstoreDelete(labelKey); }; return makeCache(readBacking, writeBacking, deleteBacking); } @@ -696,9 +690,9 @@ export function makeCollectionManager( syscall, prefixc(collectionID, '|'), )) { - // these two keys are owned by schemaCache, and will be deleted - // when schemaCache is flushed - if (dbKey.endsWith('|schemata') || dbKey.endsWith('|label')) { + // this key is owned by schemaCache, and will be deleted when + // schemaCache is flushed + if (dbKey.endsWith('|schemata')) { continue; } // but we must still delete the other keys (|nextOrdinal and @@ -726,7 +720,7 @@ export function makeCollectionManager( syscall.vatstoreSet(prefixc(collectionID, '|entryCount'), '0'); } - const schemata = {}; // don't populate 'undefined', keep it small + const schemata = { label }; // don't populate 'undefined', keep it small if (keyShape !== undefined) { schemata.keyShape = keyShape; } diff --git a/packages/swingset-liveslots/src/vatstore-usage.md b/packages/swingset-liveslots/src/vatstore-usage.md index 4695193e5b1..5872800ac30 100644 --- a/packages/swingset-liveslots/src/vatstore-usage.md +++ b/packages/swingset-liveslots/src/vatstore-usage.md @@ -180,11 +180,10 @@ c1.init('key4', foo2); Each collection stores a number of metadata keys in the vatstore, all with a prefix of `vc.${collectionID}.|` (note that the collection *type* is not a part of the key, only the collection *index*). The currently defined metadata keys (copied from the record for the "mylabel" Kind stored in `c1`) are: * `v6.vs.vc.2.|entryCount`: `4` (the size of the collection as a numeric string, incremented with each call to `c1.init` and decremented with each call to `c1.delete`) -* `v6.vs.vc.2.|label`: `mylabel` (a debugging label provided in the `make*Store` call that creates the collection) * `v6.vs.vc.2.|nextOrdinal`: `1` (a numeric string counter used to allocate index values for Objects used as keys, see `generateOrdinal` in [collectionManager.js](./collectionManager.js)) -* `v6.vs.vc.2.|schemata`: `{"body":"#[{\\"#tag\\":\\"match:scalar\\",\\"payload\\":\\"#undefined\\"}]","slots":[]}` +* `v6.vs.vc.2.|schemata`: `{"label":"mylabel","body":"#[{\\"#tag\\":\\"match:scalar\\",\\"payload\\":\\"#undefined\\"}]","slots":[]}` -The `schemata` is a capdata serialization of the Matcher constraints recorded for the collection. These constraints can limit keys to be just strings, or numbers, etc. (see [Patterns](https://github.com/endojs/endo/tree/master/packages/patterns)). The schemata consists of an array in which the first element is a schema for the keys and the second is a separate schema for the values. +The `schemata` is a capdata serialization of the debugging label provided in the `make*Store` call that created the collection, plus the Matcher constraints recorded for the collection. These constraints can limit keys to be just strings, or numbers, etc. (see [Patterns](https://github.com/endojs/endo/tree/master/packages/patterns)). The schemata consists of an array in which the first element is a schema for the keys and the second is a separate schema for the values. Each entry in the collection gets put into a single vatstore entry with a capdata-serialized value: diff --git a/packages/swingset-liveslots/test/gc-helpers.js b/packages/swingset-liveslots/test/gc-helpers.js index b85012531ed..8736a587895 100644 --- a/packages/swingset-liveslots/test/gc-helpers.js +++ b/packages/swingset-liveslots/test/gc-helpers.js @@ -182,7 +182,6 @@ export function assertCollectionDeleted(v, baseref) { const { subid: cID } = parseVatSlot(baseref); const { t, fakestore } = v; t.is(fakestore.get(`vom.rc.${baseref}`), undefined); - t.is(fakestore.get(`vc.${cID}.|label`), undefined); t.is(fakestore.get(`vc.${cID}.|schemata`), undefined); t.is(fakestore.get(`vc.${cID}.|nextOrdinal`), undefined); // there should be no ordinal mappings: vc.${cid}.|${vref} diff --git a/packages/swingset-liveslots/test/storeGC/test-lifecycle.js b/packages/swingset-liveslots/test/storeGC/test-lifecycle.js index 72691cbf75d..58a78e02931 100644 --- a/packages/swingset-liveslots/test/storeGC/test-lifecycle.js +++ b/packages/swingset-liveslots/test/storeGC/test-lifecycle.js @@ -6,7 +6,7 @@ import { findSyscallsByType, } from '../liveslots-helpers.js'; import { buildRootObject, mainHeldIdx, mapRef } from '../gc-helpers.js'; -import { kslot } from '../kmarshal.js'; +import { kslot, kunser } from '../kmarshal.js'; import { parseVatSlot } from '../../src/parseVatSlots.js'; // These tests follow the model described in @@ -33,12 +33,13 @@ function assertState(v, vref, reachable, erv) { } const vdata = erv[2] === 'V'; const { t, fakestore } = v; + const get = key => fakestore.get(key); + const getLabel = key => kunser(JSON.parse(get(key))).label; const { subid: cID } = parseVatSlot(vref); if (reachable) { - t.is(fakestore.get(`vc.${cID}.|label`), 'store #6'); + t.is(getLabel(`vc.${cID}.|schemata`), 'store #6'); t.truthy(fakestore.get(`vc.${cID}.|entryCount`)); // exists even if 0 t.truthy(fakestore.get(`vc.${cID}.|nextOrdinal`)); - t.truthy(fakestore.get(`vc.${cID}.|schemata`)); if (vdata) { t.is(fakestore.get(`vom.rc.${vref}`), `1`); } else { @@ -52,10 +53,9 @@ function assertState(v, vref, reachable, erv) { t.is(fakestore.get(`vom.es.${vref}`), undefined); } } else { - t.is(fakestore.get(`vc.${cID}.|label`), undefined); + t.is(fakestore.get(`vc.${cID}.|schemata`), undefined); t.is(fakestore.get(`vc.${cID}.|entryCount`), undefined); t.is(fakestore.get(`vc.${cID}.|nextOrdinal`), undefined); - t.is(fakestore.get(`vc.${cID}.|schemata`), undefined); t.is(fakestore.get(`vom.rc.${vref}`), undefined); t.is(fakestore.get(`vom.es.${vref}`), undefined); } diff --git a/packages/swingset-liveslots/test/test-baggage.js b/packages/swingset-liveslots/test/test-baggage.js index e730b839748..e33d068a740 100644 --- a/packages/swingset-liveslots/test/test-baggage.js +++ b/packages/swingset-liveslots/test/test-baggage.js @@ -4,6 +4,7 @@ import '@endo/init/debug.js'; import { Far } from '@endo/marshal'; import { setupTestLiveslots } from './liveslots-helpers.js'; import { vstr } from './util.js'; +import { kunser } from './kmarshal.js'; import { parseVatSlot } from '../src/parseVatSlots.js'; function buildRootObject(vatPowers, vatParameters, baggage) { @@ -25,6 +26,8 @@ test.serial('exercise baggage', async t => { { forceGC: true }, ); const { fakestore } = v; + const get = key => fakestore.get(key); + const getLabel = key => kunser(JSON.parse(get(key))).label; const baggageVref = fakestore.get('baggageID'); const { subid } = parseVatSlot(baggageVref); @@ -32,7 +35,7 @@ test.serial('exercise baggage', async t => { const kindIDs = JSON.parse(fakestore.get('storeKindIDTable')); // baggage is the first collection created, a scalarDurableMapStore t.is(baggageVref, `o+d${kindIDs.scalarDurableMapStore}/1`); - t.is(fakestore.get(`vc.${baggageID}.|label`), 'baggage'); + t.is(getLabel(`vc.${baggageID}.|schemata`), 'baggage'); const outsideVal = fakestore.get(`vc.${baggageID}.soutside`); t.is(outsideVal, vstr('outer val')); t.is(fakestore.get(`vc.${baggageID}.|entryCount`), '1'); diff --git a/packages/swingset-liveslots/test/test-handled-promises.js b/packages/swingset-liveslots/test/test-handled-promises.js index 272093ea9b2..ac88375d675 100644 --- a/packages/swingset-liveslots/test/test-handled-promises.js +++ b/packages/swingset-liveslots/test/test-handled-promises.js @@ -138,21 +138,18 @@ const kvStoreDataV1 = Object.entries({ 'vc.1.sthe_DurablePromiseIgnorer': '{"body":"#\\"$0.Alleged: DurablePromiseIgnorer\\"","slots":["o+d10/1"]}', 'vc.1.|entryCount': '2', - 'vc.1.|label': 'baggage', 'vc.1.|nextOrdinal': '1', 'vc.1.|schemata': - '{"body":"#{\\"keyShape\\":{\\"#tag\\":\\"match:string\\",\\"payload\\":[]}}","slots":[]}', + '{"label":"baggage","body":"#{\\"keyShape\\":{\\"#tag\\":\\"match:string\\",\\"payload\\":[]}}","slots":[]}', // non-durable // 'vc.2.sp+6': '{"body":"#\\"&0\\"","slots":["p+6"]}', // 'vc.2.|entryCount': '1', - // 'vc.2.|label': 'promiseRegistrations', // 'vc.2.|nextOrdinal': '1', - // 'vc.2.|schemata': '{"body":"#{\\"keyShape\\":{\\"#tag\\":\\"match:scalar\\",\\"payload\\":\\"#undefined\\"}}","slots":[]}', + // 'vc.2.|schemata': '{"label":"promiseRegistrations","body":"#{\\"keyShape\\":{\\"#tag\\":\\"match:scalar\\",\\"payload\\":\\"#undefined\\"}}","slots":[]}', 'vc.3.|entryCount': '0', - 'vc.3.|label': 'promiseWatcherByKind', 'vc.3.|nextOrdinal': '1', 'vc.3.|schemata': - '{"body":"#{\\"keyShape\\":{\\"#tag\\":\\"match:scalar\\",\\"payload\\":\\"#undefined\\"}}","slots":[]}', + '{"label":"promiseWatcherByKind","body":"#{\\"keyShape\\":{\\"#tag\\":\\"match:scalar\\",\\"payload\\":\\"#undefined\\"}}","slots":[]}', 'vc.4.sp+6': '{"body":"#[[\\"$0.Alleged: DurablePromiseIgnorer\\",\\"orphaned\\"]]","slots":["o+d10/1"]}', 'vc.4.sp-8': @@ -160,10 +157,9 @@ const kvStoreDataV1 = Object.entries({ 'vc.4.sp-9': '{"body":"#[[\\"$0.Alleged: DurablePromiseIgnorer\\",\\"late-rejected\\"]]","slots":["o+d10/1"]}', 'vc.4.|entryCount': '3', - 'vc.4.|label': 'watchedPromises', 'vc.4.|nextOrdinal': '1', 'vc.4.|schemata': - '{"body":"#{\\"keyShape\\":{\\"#tag\\":\\"match:and\\",\\"payload\\":[{\\"#tag\\":\\"match:scalar\\",\\"payload\\":\\"#undefined\\"},{\\"#tag\\":\\"match:string\\",\\"payload\\":[]}]}}","slots":[]}', + '{"label":"watchedPromises","body":"#{\\"keyShape\\":{\\"#tag\\":\\"match:and\\",\\"payload\\":[{\\"#tag\\":\\"match:scalar\\",\\"payload\\":\\"#undefined\\"},{\\"#tag\\":\\"match:string\\",\\"payload\\":[]}]}}","slots":[]}', 'vom.dkind.10': '{"kindID":"10","tag":"DurablePromiseIgnorer","nextInstanceID":2,"unfaceted":true}', 'vom.o+d10/1': '{}', diff --git a/packages/swingset-liveslots/test/test-initial-vrefs.js b/packages/swingset-liveslots/test/test-initial-vrefs.js index acf7f7a8181..48ba28a7fd5 100644 --- a/packages/swingset-liveslots/test/test-initial-vrefs.js +++ b/packages/swingset-liveslots/test/test-initial-vrefs.js @@ -56,6 +56,7 @@ test('initial vatstore contents', async t => { }); const { fakestore } = v; const get = key => fakestore.get(key); + const getLabel = key => kunser(JSON.parse(get(key))).label; // an empty buildRootObject should create 4 collections, and some metadata const kindIDs = JSON.parse(get('storeKindIDTable')); @@ -63,20 +64,19 @@ test('initial vatstore contents', async t => { // the first collection is baggage: a durable mapstore const baggageVref = `${dmsBase}/1`; t.is(get('baggageID'), baggageVref); - t.is(get(`vc.1.|label`), 'baggage'); t.is(get(`vc.1.|entryCount`), '0'); // no entries yet t.is(get(`vc.1.|nextOrdinal`), '1'); // no ordinals yet t.is(get(`vc.1.|entryCount`), '0'); - const stringSchema = { keyShape: M.string() }; + const stringSchema = { label: 'baggage', keyShape: M.string() }; t.deepEqual(kunser(JSON.parse(get(`vc.1.|schemata`))), stringSchema); // then three tables for the promise watcher (one virtual, two durable) - t.is(get(`vc.3.|label`), `promiseWatcherByKind`); // durable - t.is(get(`vc.4.|label`), `watchedPromises`); // durable + t.is(getLabel(`vc.3.|schemata`), 'promiseWatcherByKind'); // durable + t.is(getLabel(`vc.4.|schemata`), 'watchedPromises'); // durable // the promiseRegistrations table is not durable, and only gets vc.2 // on the first incarnation: it will get a new ID on subsequent // incarnations - t.is(get(`vc.2.|label`), `promiseRegistrations`); // virtual + t.is(getLabel(`vc.2.|schemata`), `promiseRegistrations`); // virtual const watcherTableVref = get('watcherTableID'); const watchedPromiseTableVref = get('watchedPromiseTableID'); @@ -89,11 +89,13 @@ test('initial vatstore contents', async t => { t.is(get(`vom.rc.${watchedPromiseTableVref}`), '1'); // promiseRegistrations and promiseWatcherByKind arbitrary scalars as keys - const scalarSchema = { keyShape: M.scalar() }; - t.deepEqual(kunser(JSON.parse(get(`vc.2.|schemata`))), scalarSchema); - t.deepEqual(kunser(JSON.parse(get(`vc.3.|schemata`))), scalarSchema); + const scalarSchema2 = { label: 'promiseRegistrations', keyShape: M.scalar() }; + const scalarSchema3 = { label: 'promiseWatcherByKind', keyShape: M.scalar() }; + t.deepEqual(kunser(JSON.parse(get(`vc.2.|schemata`))), scalarSchema2); + t.deepEqual(kunser(JSON.parse(get(`vc.3.|schemata`))), scalarSchema3); // watchedPromises uses vref (string) keys const scalarStringSchema = { + label: 'watchedPromises', keyShape: M.and(M.scalar(), M.string()), }; t.deepEqual(kunser(JSON.parse(get(`vc.4.|schemata`))), scalarStringSchema); @@ -108,6 +110,9 @@ test('vrefs', async t => { ); // const { fakestore, dumpFakestore } = v; const { fakestore } = v; + const get = key => fakestore.get(key); + const getLabel = key => kunser(JSON.parse(get(key))).label; + const kindIDID = JSON.parse(fakestore.get('kindIDID')); const initialKindIDs = JSON.parse(fakestore.get('storeKindIDTable')); const initialCounters = JSON.parse(fakestore.get(`idCounters`)); @@ -139,7 +144,7 @@ test('vrefs', async t => { // the liveslots-created collections consume vc.1 through vc.4, // leaving vc.5 for the first user-created collection - t.is(fakestore.get('vc.5.|label'), 'store1'); + t.is(getLabel('vc.5.|schemata'), 'store1'); const expectedStore1Vref = `o+v${initialKindIDs.scalarMapStore}/5`; const store1Vref = (await run('getStore1')).slots[0]; t.is(store1Vref, expectedStore1Vref); diff --git a/packages/swingset-liveslots/test/test-liveslots-mock-gc.js b/packages/swingset-liveslots/test/test-liveslots-mock-gc.js index 1ddb03633ef..20e8a5274c1 100644 --- a/packages/swingset-liveslots/test/test-liveslots-mock-gc.js +++ b/packages/swingset-liveslots/test/test-liveslots-mock-gc.js @@ -448,7 +448,6 @@ const doublefreetest = test.macro(async (t, mode) => { // all collection metadata should be gone const collectionID = String(parseVatSlot(vref).subid); t.is(fakestore.get(`vc.${collectionID}.|schemata`), undefined); - t.is(fakestore.get(`vc.${collectionID}.|label`), undefined); t.is(fakestore.get(`vc.${collectionID}.|nextOrdinal`), undefined); t.is(fakestore.get(`vc.${collectionID}.|entryCount`), undefined); } diff --git a/packages/swingset-liveslots/test/test-liveslots.js b/packages/swingset-liveslots/test/test-liveslots.js index ed6cb33bdd6..4bbebcd4d36 100644 --- a/packages/swingset-liveslots/test/test-liveslots.js +++ b/packages/swingset-liveslots/test/test-liveslots.js @@ -711,15 +711,11 @@ test('capdata size limit on syscalls', async t => { t.deepEqual(log, []); const gotSchema = () => { + const label = 'test'; t.deepEqual(log.shift(), { type: 'vatstoreGet', key: 'vc.5.|schemata', - result: JSON.stringify(kser({ keyShape: M.scalar() })), - }); - t.deepEqual(log.shift(), { - type: 'vatstoreGet', - key: 'vc.5.|label', - result: 'test', + result: JSON.stringify(kser({ label, keyShape: M.scalar() })), }); };