diff --git a/packages/SwingSet/src/kernel/liveSlots.js b/packages/SwingSet/src/kernel/liveSlots.js index c4c88bd6a59..9e4f50e4b09 100644 --- a/packages/SwingSet/src/kernel/liveSlots.js +++ b/packages/SwingSet/src/kernel/liveSlots.js @@ -215,7 +215,6 @@ function build(syscall, forVatID, cacheSize, vatPowers, vatParameters) { makeVirtualObjectRepresentative, makeWeakStore, makeKind, - flushCache, } = makeVirtualObjectManager( syscall, allocateExportID, @@ -569,7 +568,6 @@ function build(syscall, forVatID, cacheSize, vatPowers, vatParameters) { const vatGlobals = harden({ makeWeakStore, makeKind, - flushCache, }); function setBuildRootObject(buildRootObject) { diff --git a/packages/SwingSet/src/kernel/virtualObjectManager.js b/packages/SwingSet/src/kernel/virtualObjectManager.js index 3205c4f12bd..25fd38ec56a 100644 --- a/packages/SwingSet/src/kernel/virtualObjectManager.js +++ b/packages/SwingSet/src/kernel/virtualObjectManager.js @@ -19,12 +19,11 @@ import { parseVatSlot } from '../parseVatSlots'; export function makeCache(size, fetch, store) { let lruHead; let lruTail; - let count = 0; const liveTable = new Map(); const cache = { makeRoom() { - while (count > size && lruTail) { + while (liveTable.size > size && lruTail) { liveTable.delete(lruTail.instanceKey); store(lruTail.instanceKey, lruTail.rawData); lruTail.rawData = null; @@ -34,7 +33,6 @@ export function makeCache(size, fetch, store) { lruHead = undefined; } lruTail = lruTail.prev; - count -= 1; } }, flush() { @@ -47,9 +45,9 @@ export function makeCache(size, fetch, store) { if (liveTable.has(innerObj.instanceKey)) { return; } + cache.makeRoom(); liveTable.set(innerObj.instanceKey, innerObj); innerObj.prev = undefined; - cache.makeRoom(); innerObj.next = lruHead; if (lruHead) { lruHead.prev = innerObj; @@ -58,7 +56,6 @@ export function makeCache(size, fetch, store) { if (!lruTail) { lruTail = innerObj; } - count += 1; }, refresh(innerObj) { if (innerObj !== lruHead) { @@ -280,40 +277,49 @@ export function makeVirtualObjectManager( const kindID = `${allocateExportID()}`; let nextInstanceID = 1; - function makeRepresentative(innerSelf) { + function makeRepresentative(innerSelf, initializing) { function ensureState() { if (!innerSelf.rawData) { innerSelf = cache.lookup(innerSelf.instanceKey); } } - function wrapData() { - const activeData = {}; + function wrapData(target) { for (const prop of Object.getOwnPropertyNames(innerSelf.rawData)) { - Object.defineProperty(activeData, prop, { + Object.defineProperty(target, prop, { get: () => { - ensureState(); + ensureState(innerSelf); return m.unserialize(innerSelf.rawData[prop]); }, set: value => { - ensureState(); - innerSelf.rawData[prop] = m.serialize(value); + const serializedValue = m.serialize(value); + ensureState(innerSelf); + innerSelf.rawData[prop] = serializedValue; }, }); } - return harden(activeData); + innerSelf.wrapData = undefined; + harden(target); } - const representative = instanceMaker(wrapData()); - delete representative.initialize; - harden(representative); + let representative; + if (initializing) { + innerSelf.wrapData = wrapData; + representative = instanceMaker(innerSelf.rawData); + } else { + const activeData = {}; + wrapData(activeData); + representative = instanceMaker(activeData); + delete representative.initialize; + harden(representative); + } cache.remember(innerSelf); valToSlotTable.set(representative, innerSelf.instanceKey); return representative; } function reanimate(instanceKey) { - return makeRepresentative(cache.lookup(instanceKey)); + return makeRepresentative(cache.lookup(instanceKey), false); } kindTable.set(kindID, reanimate); @@ -321,19 +327,22 @@ export function makeVirtualObjectManager( const instanceKey = `o+${kindID}/${nextInstanceID}`; nextInstanceID += 1; - const initializationData = {}; - const tempInstance = instanceMaker(initializationData); - tempInstance.initialize(...args); + const initialData = {}; + const innerSelf = { instanceKey, rawData: initialData }; + const initialRepresentative = makeRepresentative(innerSelf, true); + initialRepresentative.initialize(...args); const rawData = {}; - for (const prop of Object.getOwnPropertyNames(initializationData)) { + for (const prop of Object.getOwnPropertyNames(initialData)) { try { - rawData[prop] = m.serialize(initializationData[prop]); + rawData[prop] = m.serialize(initialData[prop]); } catch (e) { console.error(`state property ${prop} is not serializable`); + throw e; } } - const innerSelf = { instanceKey, rawData }; - return makeRepresentative(innerSelf); + innerSelf.rawData = rawData; + innerSelf.wrapData(initialData); + return initialRepresentative; } return makeNewInstance; diff --git a/packages/SwingSet/test/test-virtualObjectManager.js b/packages/SwingSet/test/test-virtualObjectManager.js index 50968845b59..2e3fb4b1101 100644 --- a/packages/SwingSet/test/test-virtualObjectManager.js +++ b/packages/SwingSet/test/test-virtualObjectManager.js @@ -10,7 +10,7 @@ function capdata(body, slots = []) { } const s = JSON.stringify; -test('virtual objects', t => { +function makeAllTheStuff(cacheSize) { const fakeStore = new Map(); function dumpStore() { @@ -49,7 +49,7 @@ test('virtual objects', t => { fakeAllocateExportID, valToSlot, fakeMarshal, - 3, + cacheSize, ); function fakeConvertValToSlot(val) { @@ -66,60 +66,70 @@ test('virtual objects', t => { return makeVirtualObjectRepresentative(slot); } - function makeThingInstance(state) { - return { - initialize(label = 'thing', counter = 0) { - state.counter = counter; - state.label = label; - state.resetCounter = 0; - }, - inc() { - state.counter += 1; - return state.counter; - }, - reset(newStart) { - state.counter = newStart; - state.resetCounter += 1; - return state.resetCounter; - }, - relabel(newLabel) { - state.label = newLabel; - }, - get() { - return state.counter; - }, - describe() { - return `${state.label} counter has been reset ${state.resetCounter} times and is now ${state.counter}`; - }, - }; - } + return { + makeWeakStore, + makeKind, + flushCache, + dumpStore, + }; +} - const thingMaker = makeKind(makeThingInstance); +function makeThingInstance(state) { + return { + initialize(label = 'thing', counter = 0) { + state.counter = counter; + state.label = label; + state.resetCounter = 0; + }, + inc() { + state.counter += 1; + return state.counter; + }, + reset(newStart) { + state.counter = newStart; + state.resetCounter += 1; + return state.resetCounter; + }, + relabel(newLabel) { + state.label = newLabel; + }, + get() { + return state.counter; + }, + describe() { + return `${state.label} counter has been reset ${state.resetCounter} times and is now ${state.counter}`; + }, + }; +} - function makeZotInstance(state) { - return { - initialize(arbitrary = 47, name = 'Bob', tag = 'say what?') { - state.arbitrary = arbitrary; - state.name = name; - state.tag = tag; - state.count = 0; - }, - sayHello(msg) { - state.count += 1; - return `${msg} ${state.name}`; - }, - rename(newName) { - state.name = newName; - state.count += 1; - return state.name; - }, - getInfo() { - state.count += 1; - return `zot ${state.name} tag=${state.tag} count=${state.count} arbitrary=${state.arbitrary}`; - }, - }; - } +function makeZotInstance(state) { + return { + initialize(arbitrary = 47, name = 'Bob', tag = 'say what?') { + state.arbitrary = arbitrary; + state.name = name; + state.tag = tag; + state.count = 0; + }, + sayHello(msg) { + state.count += 1; + return `${msg} ${state.name}`; + }, + rename(newName) { + state.name = newName; + state.count += 1; + return state.name; + }, + getInfo() { + state.count += 1; + return `zot ${state.name} tag=${state.tag} count=${state.count} arbitrary=${state.arbitrary}`; + }, + }; +} + +test('virtual object operations', t => { + const { makeKind, flushCache, dumpStore } = makeAllTheStuff(3); + const thingMaker = makeKind(makeThingInstance); const zotMaker = makeKind(makeZotInstance); // phase 0: start @@ -135,6 +145,7 @@ test('virtual objects', t => { const zot2 = zotMaker(29, 'Bob', 'what are you saying?'); const zot3 = zotMaker(47, 'Carol', 'as if...'); const zot4 = zotMaker(66, 'Dave', 'you and what army?'); + // prettier-ignore t.deepEqual(dumpStore(), [ ['o+1/1', s({counter:capdata('0'),label:capdata('"thing-1"'),resetCounter:capdata('0')})], @@ -212,8 +223,22 @@ test('virtual objects', t => { ['o+2/3', s({arbitrary:capdata('47'),name:capdata('"Chester"'),tag:capdata('"as if..."'),count:capdata('3')})], ['o+2/4', s({arbitrary:capdata('66'),name:capdata('"Dave"'),tag:capdata('"you and what army?"'),count:capdata('2')})], ]); +}); + +test('weak store operations', t => { + const { makeWeakStore, makeKind } = makeAllTheStuff(3); + + const thingMaker = makeKind(makeThingInstance); + const zotMaker = makeKind(makeZotInstance); + + const thing1 = thingMaker('t1'); + const thing2 = thingMaker('t2'); + + const zot1 = zotMaker(1, 'z1'); + const zot2 = zotMaker(2, 'z2'); + const zot3 = zotMaker(3, 'z3'); + const zot4 = zotMaker(4, 'z4'); - // phase 5: test weakStore const ws1 = makeWeakStore(); const ws2 = makeWeakStore(); const nv1 = {}; diff --git a/packages/swingset-runner/demo/vatStore1/vat-bob.js b/packages/swingset-runner/demo/vatStore1/vat-bob.js index d0e6be51ac1..51f43b7d7ae 100644 --- a/packages/swingset-runner/demo/vatStore1/vat-bob.js +++ b/packages/swingset-runner/demo/vatStore1/vat-bob.js @@ -1,4 +1,4 @@ -/* global makeKind flushCache */ +/* global makeKind */ const p = console.log; function makeThingInstance(state) { @@ -119,11 +119,6 @@ export function buildRootObject(_vatPowers) { thing3.inc(); p(`${thing4.describe()}`); break; - case 4: - p('phase 4: flush test'); - thing1.inc(); - flushCache(); - break; default: // because otherwise eslint complains break; diff --git a/packages/xs-vat-worker/src/vatWorker.js b/packages/xs-vat-worker/src/vatWorker.js index 6aa128dd0bc..8e367973b34 100644 --- a/packages/xs-vat-worker/src/vatWorker.js +++ b/packages/xs-vat-worker/src/vatWorker.js @@ -99,7 +99,7 @@ function makeWorker(io, setImmediate) { workerLog(`got start`); sendUplink(['gotStart']); } else if (type === 'setBundle') { - const [bundle, vatParameters] = margs; + const [bundle, vatParameters, virtualObjectCacheSize] = margs; const endowments = { console: makeConsole(`SwingSet:vatWorker`), HandledPromise, @@ -149,6 +149,7 @@ function makeWorker(io, setImmediate) { vatID, vatPowers, vatParameters, + virtualObjectCacheSize, ); workerLog(`got dispatch:`, Object.keys(dispatch).join(',')); sendUplink(['dispatchReady']);