diff --git a/packages/SwingSet/src/kernel/virtualObjectManager.js b/packages/SwingSet/src/kernel/virtualObjectManager.js index b5a8782dfa0..0ed1dddcd91 100644 --- a/packages/SwingSet/src/kernel/virtualObjectManager.js +++ b/packages/SwingSet/src/kernel/virtualObjectManager.js @@ -1,7 +1,7 @@ import { assert, details as d, quote as q } from '@agoric/assert'; import { parseVatSlot } from '../parseVatSlots'; -const initializationInProgress = Symbol('initializing'); +const initializationsInProgress = new WeakSet(); /** * Make a simple LRU cache of virtual object inner selves. @@ -26,9 +26,9 @@ export function makeCache(size, fetch, store) { const cache = { makeRoom() { while (liveTable.size > size && lruTail) { - if (lruTail.rawData[initializationInProgress]) { + if (initializationsInProgress.has(lruTail.rawData)) { let refreshCount = 1; - while (lruTail.rawData[initializationInProgress]) { + while (initializationsInProgress.has(lruTail.rawData)) { if (refreshCount > size) { throw Error(`cache overflowed with objects being initialized`); } @@ -37,7 +37,10 @@ export function makeCache(size, fetch, store) { } } liveTable.delete(lruTail.vobjID); - store(lruTail.vobjID, lruTail.rawData); + if (lruTail.dirty) { + store(lruTail.vobjID, lruTail.rawData); + lruTail.dirty = false; + } lruTail.rawData = null; if (lruTail.prev) { lruTail.prev.next = undefined; @@ -115,7 +118,7 @@ export function makeCache(size, fetch, store) { * @param {*} m The vat's marshaler. * @param {number} cacheSize How many virtual objects this manager should cache * in memory. - * @returns {*} a new virtual object manager. + * @returns {Object} a new virtual object manager. * * The virtual object manager allows the creation of persistent objects that do * not need to occupy memory when they are not in use. It provides four @@ -382,7 +385,7 @@ export function makeVirtualObjectManager( function wrapData(target) { assert( - !target[initializationInProgress], + !initializationsInProgress.has(target), `object is still being initialized`, ); for (const prop of Object.getOwnPropertyNames(innerSelf.rawData)) { @@ -395,6 +398,7 @@ export function makeVirtualObjectManager( const serializedValue = m.serialize(value); ensureState(innerSelf); innerSelf.rawData[prop] = serializedValue; + innerSelf.dirty = true; }, }); } @@ -402,24 +406,22 @@ export function makeVirtualObjectManager( harden(target); } - let representative; + let instanceKit; if (initializing) { innerSelf.wrapData = wrapData; - representative = instanceMaker(innerSelf.rawData); + instanceKit = harden(instanceMaker(innerSelf.rawData)); } else { const activeData = {}; wrapData(activeData); - representative = instanceMaker(activeData); - delete representative.initialize; - harden(representative); + instanceKit = harden(instanceMaker(activeData)); } cache.remember(innerSelf); - valToSlotTable.set(representative, innerSelf.vobjID); - return representative; + valToSlotTable.set(instanceKit.self, innerSelf.vobjID); + return instanceKit; } function reanimate(vobjID) { - return makeRepresentative(cache.lookup(vobjID), false); + return makeRepresentative(cache.lookup(vobjID), false).self; } kindTable.set(kindID, reanimate); @@ -428,20 +430,15 @@ export function makeVirtualObjectManager( nextInstanceID += 1; const initialData = {}; - Object.defineProperty(initialData, initializationInProgress, { - configurable: true, - enumerable: false, - writeable: false, - value: true, - }); + initializationsInProgress.add(initialData); const innerSelf = { vobjID, rawData: initialData }; - const initialRepresentative = makeRepresentative(innerSelf, true); - const initialize = initialRepresentative.initialize; - if (initialize) { - delete initialRepresentative.initialize; - initialize(...args); + // prettier-ignore + const { self: initialRepresentative, init } = + makeRepresentative(innerSelf, true); + if (init) { + init(...args); } - delete initialData[initializationInProgress]; + initializationsInProgress.delete(initialData); const rawData = {}; for (const prop of Object.getOwnPropertyNames(initialData)) { try { @@ -453,6 +450,7 @@ export function makeVirtualObjectManager( } innerSelf.rawData = rawData; innerSelf.wrapData(initialData); + innerSelf.dirty = true; return initialRepresentative; } diff --git a/packages/SwingSet/test/virtualObjects/test-virtualObjectCache.js b/packages/SwingSet/test/virtualObjects/test-virtualObjectCache.js index b6758c5953b..ed6c96195e2 100644 --- a/packages/SwingSet/test/virtualObjects/test-virtualObjectCache.js +++ b/packages/SwingSet/test/virtualObjects/test-virtualObjectCache.js @@ -37,6 +37,7 @@ function makeThing(n) { return { vobjID: `t${n}`, rawData: `thing #${n}`, + dirty: true, }; } @@ -71,6 +72,7 @@ test('cache overflow and refresh', t => { // lookup that has no effect things[0] = cache.lookup('t0'); // cache: t0, t2, t5, t4 + things[0].dirty = true; // pretend we changed it t.is(things[0].rawData, 'thing #0'); t.is(things[3].rawData, null); t.deepEqual(store.getLog(), [ @@ -100,7 +102,6 @@ test('cache overflow and refresh', t => { ['store', 't2', 'thing #2'], ['store', 't0', 'thing #0'], ['store', 't4', 'thing #4'], - ['store', 't1', 'thing #1'], ]); t.deepEqual(store.dump(), [ ['t0', 'thing #0'], @@ -114,16 +115,22 @@ test('cache overflow and refresh', t => { // verify that changes get written things[0] = cache.lookup('t0'); // cache: t0 things[0].rawData = 'new thing #0'; + things[0].dirty = true; things[1] = cache.lookup('t1'); // cache: t1, t0 things[1].rawData = 'new thing #1'; + things[1].dirty = true; things[2] = cache.lookup('t2'); // cache: t2, t1, t0 things[2].rawData = 'new thing #2'; + things[2].dirty = true; things[3] = cache.lookup('t3'); // cache: t3, t2, t1, t0 things[3].rawData = 'new thing #3'; + things[3].dirty = true; things[4] = cache.lookup('t4'); // cache: t4, t3, t2, t1 things[4].rawData = 'new thing #4'; + things[4].dirty = true; things[5] = cache.lookup('t5'); // cache: t5, t4, t3, t2 things[5].rawData = 'new thing #5'; + things[5].dirty = true; t.is(things[0].rawData, null); t.is(things[5].rawData, 'new thing #5'); t.deepEqual(store.getLog(), [ diff --git a/packages/SwingSet/test/virtualObjects/test-virtualObjectManager.js b/packages/SwingSet/test/virtualObjects/test-virtualObjectManager.js index 099df14cecf..62d1585a9e7 100644 --- a/packages/SwingSet/test/virtualObjects/test-virtualObjectManager.js +++ b/packages/SwingSet/test/virtualObjects/test-virtualObjectManager.js @@ -76,52 +76,56 @@ function makeAllTheStuff(cacheSize) { function makeThingInstance(state) { return { - initialize(label = 'thing', counter = 0) { + init(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}`; + self: { + 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?') { + init(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}`; + self: { + 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}`; + }, }, }; } diff --git a/packages/SwingSet/test/virtualObjects/vat-representative-bootstrap.js b/packages/SwingSet/test/virtualObjects/vat-representative-bootstrap.js index c0694b0d203..65ee43de063 100644 --- a/packages/SwingSet/test/virtualObjects/vat-representative-bootstrap.js +++ b/packages/SwingSet/test/virtualObjects/vat-representative-bootstrap.js @@ -4,11 +4,12 @@ const stuff = makeWeakStore(); let initialSelf; function makeThingInstance(state) { + function init(name) { + state.name = name; + // eslint-disable-next-line no-use-before-define + initialSelf = self; + } const self = { - initialize(name) { - state.name = name; - initialSelf = self; - }, getName() { return state.name; }, @@ -19,24 +20,24 @@ function makeThingInstance(state) { return self; }, }; - return self; + return { init, self }; } const thingMaker = makeKind(makeThingInstance); function makeZotInstance(state) { + function init(name, forceOverflow) { + state.name = name; + // enough instances to push me out of the cache + for (let i = 0; i < 5; i += 1) { + stuff.init(thingMaker(`${name}-subthing${i}`, 29)); + } + if (forceOverflow) { + // eslint-disable-next-line no-use-before-define + zotMaker('recur', true); + } + } const self = { - initialize(name, forceOverflow) { - state.name = name; - // enough instances to push me out of the cache - for (let i = 0; i < 5; i += 1) { - stuff.init(thingMaker(`${name}-subthing${i}`, 29)); - } - if (forceOverflow) { - // eslint-disable-next-line no-use-before-define - zotMaker('recur', true); - } - }, getName() { return state.name; }, @@ -44,7 +45,7 @@ function makeZotInstance(state) { state.name = newName; }, }; - return self; + return { init, self }; } const zotMaker = makeKind(makeZotInstance); diff --git a/packages/swingset-runner/demo/vatStore1/vat-bob.js b/packages/swingset-runner/demo/vatStore1/vat-bob.js index 51f43b7d7ae..00624f61e3e 100644 --- a/packages/swingset-runner/demo/vatStore1/vat-bob.js +++ b/packages/swingset-runner/demo/vatStore1/vat-bob.js @@ -3,32 +3,34 @@ const p = console.log; function makeThingInstance(state) { return { - initialize(label = 'thing', counter = 0) { + init(label = 'thing', counter = 0) { p(`@@@ thing.initialize(${label}, ${counter})`); state.counter = counter; state.label = label; state.resetCounter = 0; }, - inc() { - state.counter += 1; - p(`#thing# ${state.label} inc() counter now ${state.counter}`); - }, - reset(newStart) { - p(`#thing# ${state.label} reset(${newStart})`); - state.counter = newStart; - state.resetCounter += 1; - }, - relabel(newLabel) { - p(`#thing# ${state.label} relabel(${newLabel})`); - state.label = newLabel; - }, - get() { - p(`#thing# ${state.label} get()=>${state.counter}`); - return state.counter; - }, - describe() { - p(`#thing# ${state.label} describe()`); - return `${state.label} counter has been reset ${state.resetCounter} times and is now ${state.counter}`; + self: { + inc() { + state.counter += 1; + p(`#thing# ${state.label} inc() counter now ${state.counter}`); + }, + reset(newStart) { + p(`#thing# ${state.label} reset(${newStart})`); + state.counter = newStart; + state.resetCounter += 1; + }, + relabel(newLabel) { + p(`#thing# ${state.label} relabel(${newLabel})`); + state.label = newLabel; + }, + get() { + p(`#thing# ${state.label} get()=>${state.counter}`); + return state.counter; + }, + describe() { + p(`#thing# ${state.label} describe()`); + return `${state.label} counter has been reset ${state.resetCounter} times and is now ${state.counter}`; + }, }, }; } @@ -37,26 +39,28 @@ const thingMaker = makeKind(makeThingInstance); function makeZotInstance(state) { return { - initialize(arbitrary = 47, name = 'Bob', tag = 'say what?') { + init(arbitrary = 47, name = 'Bob', tag = 'say what?') { p(`@@@ zot.initialize(${arbitrary}, ${name}, ${tag})`); state.arbitrary = arbitrary; state.name = name; state.tag = tag; state.count = 0; }, - sayHello(msg) { - p(`#zot# ${msg} ${state.name}`); - state.count += 1; - }, - rename(newName) { - p(`#zot# ${state.name} rename(${newName})`); - state.name = newName; - state.count += 1; - }, - printInfo() { - // prettier-ignore - p(`#zot# ${state.name} tag=${state.tag} count=${state.count} arbitrary=${state.arbitrary}`); - state.count += 1; + self: { + sayHello(msg) { + p(`#zot# ${msg} ${state.name}`); + state.count += 1; + }, + rename(newName) { + p(`#zot# ${state.name} rename(${newName})`); + state.name = newName; + state.count += 1; + }, + printInfo() { + // prettier-ignore + p(`#zot# ${state.name} tag=${state.tag} count=${state.count} arbitrary=${state.arbitrary}`); + state.count += 1; + }, }, }; } diff --git a/packages/swingset-runner/demo/vatStore2/thingHolder.js b/packages/swingset-runner/demo/vatStore2/thingHolder.js index f16af5e8733..7e718dc73bd 100644 --- a/packages/swingset-runner/demo/vatStore2/thingHolder.js +++ b/packages/swingset-runner/demo/vatStore2/thingHolder.js @@ -6,32 +6,34 @@ const p = console.log; function build(name) { function makeThingInstance(state) { return { - initialize(label, companion, companionName) { + init(label, companion, companionName) { p(`${name}'s thing ${label}: initialize ${companionName}`); state.label = label; state.companion = companion; state.companionName = companionName; state.count = 0; }, - echo(message) { - state.count += 1; - E(state.companion).say(message); - }, - async changePartner(newCompanion) { - state.count += 1; - state.companion = newCompanion; - const companionName = await E(newCompanion).getName(); - state.companionName = companionName; - p(`${name}'s thing ${state.label}: changePartner ${companionName}`); - }, - getLabel() { - const label = state.label; - p(`${name}'s thing ${label}: getLabel`); - state.count += 1; - return label; - }, - report() { - p(`${name}'s thing ${state.label} invoked ${state.count} times`); + self: { + echo(message) { + state.count += 1; + E(state.companion).say(message); + }, + async changePartner(newCompanion) { + state.count += 1; + state.companion = newCompanion; + const companionName = await E(newCompanion).getName(); + state.companionName = companionName; + p(`${name}'s thing ${state.label}: changePartner ${companionName}`); + }, + getLabel() { + const label = state.label; + p(`${name}'s thing ${label}: getLabel`); + state.count += 1; + return label; + }, + report() { + p(`${name}'s thing ${state.label} invoked ${state.count} times`); + }, }, }; }