-
Notifications
You must be signed in to change notification settings - Fork 212
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
collectionManager: refcount remotables in keyShape/valueShape
The collection schema (keyShape/valueShape) can refer to specific Remotable instances (e.g. Presence, or a durable object), rather than defining a constraint to be a whole category of objects. The collection schema metadata is thus a form of write-once state storage, like a tiny virtual object. Previously, defineKind failed to increment the refcount on the objects' vrefs, so when the collection falls out of memory, the object might be deleted, and then we would be unable to revive the constraint when loading the schema back in later. This changes the code to increment the refcounts of the vrefs in keyShape and valueShape when the collection is created, and decrement them when the collection is deleted. It handles ephemeral Remotables, as well as virtual/durable object Representatives. For durable collections, it also enforces the same `isDurable` check on the schema as it would on keys and values of the collection itself, so you cannot use a `Far()` or a merely-virtual object in the valueShape of a durable collection. closes #7321
- Loading branch information
Showing
2 changed files
with
121 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
106 changes: 106 additions & 0 deletions
106
packages/swingset-liveslots/test/test-collection-schema-refcount.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
import test from 'ava'; | ||
import '@endo/init/debug.js'; | ||
|
||
import { Far } from '@endo/marshal'; | ||
import { makeLiveSlots } from '../src/liveslots.js'; | ||
import { parseVatSlot } from '../src/parseVatSlots.js'; | ||
import { kser } from './kmarshal.js'; | ||
import { buildSyscall } from './liveslots-helpers.js'; | ||
import { makeStartVat, makeBringOutYourDead } from './util.js'; | ||
import { makeMockGC } from './mock-gc.js'; | ||
|
||
// virtual/durable collections serialize their keyShape/valueShape, | ||
// any Remotables therein must be compatible, and we should have | ||
// refcounts on them | ||
|
||
const shapetest = test.macro(async (t, collectionType, remotableType) => { | ||
const { syscall, fakestore } = buildSyscall(); | ||
const gcTools = makeMockGC(); | ||
let remotable; | ||
let map; | ||
|
||
function buildRootObject(vatPowers) { | ||
const { VatData } = vatPowers; | ||
const initData = () => ({}); | ||
const handle = VatData.makeKindHandle('thing'); | ||
const makeVirtualThing = VatData.defineKind('thing', initData, {}); | ||
const makeDurableThing = VatData.defineDurableKind(handle, initData, {}); | ||
switch (remotableType) { | ||
case 'ephemeral': | ||
remotable = Far('thing', {}); | ||
break; | ||
case 'virtual': | ||
remotable = makeVirtualThing(); | ||
break; | ||
case 'durable': | ||
remotable = makeDurableThing(); | ||
break; | ||
default: | ||
throw Error(`unhandled ${remotableType}`); | ||
} | ||
t.truthy(remotable); | ||
const valueShape = remotable; | ||
const durable = collectionType === 'durable'; | ||
map = VatData.makeScalarBigMapStore('map', { valueShape, durable }); | ||
return Far('root', {}); | ||
} | ||
|
||
const makeNS = () => ({ buildRootObject }); | ||
const ls = makeLiveSlots(syscall, 'vatA', {}, {}, gcTools, undefined, makeNS); | ||
const { dispatch, testHooks } = ls; | ||
const startVat = makeStartVat(kser()); | ||
if (collectionType === 'durable' && remotableType !== 'durable') { | ||
await t.throwsAsync(() => dispatch(startVat), { | ||
message: /value is not durable/, | ||
}); | ||
// TODO: unhandled rejection interferes with the test | ||
return; | ||
} | ||
await dispatch(startVat); | ||
|
||
// the object we used in the valueShape schema .. | ||
const vref = testHooks.valToSlot.get(remotable); | ||
t.truthy(vref); | ||
|
||
// .. should appear in the serialized schema | ||
const mapVref = testHooks.valToSlot.get(map); | ||
const collectionID = parseVatSlot(mapVref).subid; | ||
const schemaKey = `vc.${collectionID}.|schemata`; | ||
const schemataData = JSON.parse(fakestore.get(schemaKey)); | ||
t.deepEqual(schemataData.slots, [vref]); | ||
|
||
// and it should hold a refcount | ||
t.is(testHooks.getReachableRefCount(vref), 1); | ||
|
||
// now pretend the collection is deleted, and do a BOYD | ||
gcTools.kill(map); | ||
gcTools.flushAllFRs(); | ||
await dispatch(makeBringOutYourDead()); | ||
// the refcount should be gone | ||
t.falsy(testHooks.getReachableRefCount(vref), undefined); | ||
// so should the schema (and all other keys) | ||
t.falsy(fakestore.has(schemaKey)); | ||
}); | ||
|
||
test( | ||
'virtual collection shape holds ephmeral', | ||
shapetest, | ||
'virtual', | ||
'ephemeral', | ||
); | ||
test('virtual collection shape holds virtual', shapetest, 'virtual', 'virtual'); | ||
test('virtual collection shape holds durable', shapetest, 'virtual', 'durable'); | ||
|
||
test.skip( | ||
'durable collection shape holds ephmeral', | ||
shapetest, | ||
'durable', | ||
'ephemeral', | ||
); | ||
test.skip( | ||
'durable collection shape holds virtual', | ||
shapetest, | ||
'durable', | ||
'virtual', | ||
); | ||
test('durable collection shape holds durable', shapetest, 'durable', 'durable'); |