Skip to content

Commit

Permalink
VOM: split nextInstanceID into a separate vatstore key
Browse files Browse the repository at this point in the history
Previously, we stored both the static parts of a
DurableKindDescriptor (tag, stateShape) and the dynamic
part (nextInstanceID) in the same vatStore
key (`vom.dkind.${kindID}`). This meant we had to re-write the whole
thing each time we make a new instance, which seems like a waste.

This change splits the next instance ID out to a separate key:
* `vom.dkind.${kindID}.descriptor`: holds the static descriptor
* `vom.dkind.${kindID}.nextID`: holds the next instance ID

Note that KindIDs are always integers, and we always append the
.descriptor/.nextID suffix, so I'm not worried about vatstore key
confusion/collision.

It also changes the merely-virtual Kind descriptor key to match:
`vom.vkind.${kindID}.descriptor`. Note that we don't bother recording
a `nextID` for virtual Kinds, since these never outlive an
incarnation, so we can hold the nextInstanceID in RAM. We only write
out the descriptor for external tooling and debugging, anyways.

Internally, `nextInstanceID` is now a BigInt, because we use `Nat()`
on the value we get back from the vatstore. This is not exposed to
userspace, and vrefs are strings (which incorporate the ID).

closes #7364
  • Loading branch information
warner committed Apr 15, 2023
1 parent a295333 commit 4393c69
Show file tree
Hide file tree
Showing 6 changed files with 122 additions and 88 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -471,7 +471,7 @@ test('virtual object gc', async t => {
[`${v}.vs.vom.rc.o+d6/1`]: '1',
[`${v}.vs.vom.rc.o+d6/3`]: '1',
[`${v}.vs.vom.rc.o+d6/4`]: '1',
[`${v}.vs.vom.vkind.10`]: '{"kindID":"10","tag":"thing"}',
[`${v}.vs.vom.vkind.10.descriptor`]: '{"kindID":"10","tag":"thing"}',
[`${v}.vs.watchedPromiseTableID`]: 'o+d6/4',
[`${v}.vs.watcherTableID`]: 'o+d6/3',
});
Expand Down
86 changes: 57 additions & 29 deletions packages/swingset-liveslots/src/virtualObjectManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { assert, Fail } from '@agoric/assert';
import { assertPattern, mustMatch } from '@agoric/store';
import { defendPrototype, defendPrototypeKit } from '@endo/exo/tools.js';
import { Far, hasOwnPropertyOf, passStyleOf } from '@endo/marshal';
import { Nat } from '@endo/nat';
import { parseVatSlot, makeBaseRef } from './parseVatSlots.js';
import { enumerateKeysWithPrefix } from './vatstore-iterators.js';
import { makeCache } from './cache.js';
Expand Down Expand Up @@ -586,7 +587,6 @@ export function makeVirtualObjectManager(
/**
* @typedef {{
* kindID: string,
* nextInstanceID: number,
* tag: string,
* unfaceted?: boolean,
* facets?: string[],
Expand All @@ -598,10 +598,38 @@ export function makeVirtualObjectManager(
* @param {DurableKindDescriptor} durableKindDescriptor
*/
function saveDurableKindDescriptor(durableKindDescriptor) {
syscall.vatstoreSet(
`vom.dkind.${durableKindDescriptor.kindID}`,
JSON.stringify(durableKindDescriptor),
);
const { kindID } = durableKindDescriptor;
const key = `vom.dkind.${kindID}.descriptor`;
syscall.vatstoreSet(key, JSON.stringify(durableKindDescriptor));
}

/**
* @param {string} kindID
* @returns {DurableKindDescriptor} durableKindDescriptor
*/
function loadDurableKindDescriptor(kindID) {
const key = `vom.dkind.${kindID}.descriptor`;
const raw = syscall.vatstoreGet(key);
raw || Fail`unknown kind ID ${kindID}`;
return JSON.parse(raw);
}

function saveNextInstanceID(kindID) {
const key = `vom.dkind.${kindID}.nextID`;
syscall.vatstoreSet(key, `${nextInstanceIDs.get(kindID)}`);
}

function loadNextInstanceID(kindID) {
const key = `vom.dkind.${kindID}.nextID`;
return Nat(Number(syscall.vatstoreGet(key)));
}

function saveVirtualKindDescriptor(kindID, descriptor) {
// we never read these back: they're stored in the DB for the sake
// of diagnostics, debugging, and potential external DB
// cleanup/upgrade tools
const key = `vom.vkind.${kindID}.descriptor`;
syscall.vatstoreSet(key, JSON.stringify(descriptor));
}

/**
Expand Down Expand Up @@ -1009,10 +1037,10 @@ export function makeVirtualObjectManager(

function reanimateDurableKindID(vobjID) {
const kindID = `${parseVatSlot(vobjID).subid}`;
const raw = syscall.vatstoreGet(`vom.dkind.${kindID}`);
raw || Fail`unknown kind ID ${kindID}`;
const durableKindDescriptor = JSON.parse(raw);
const durableKindDescriptor = loadDurableKindDescriptor(kindID);
const nextInstanceID = loadNextInstanceID(kindID);
kindIDToDescriptor.set(kindID, durableKindDescriptor);
nextInstanceIDs.set(kindID, nextInstanceID);
const kindHandle = Far('kind', {});
kindHandleToID.set(kindHandle, kindID);
// KindHandles are held strongly for the remainder of the incarnation, so
Expand All @@ -1037,21 +1065,18 @@ export function makeVirtualObjectManager(
// kindDescriptors[kindID]
const id = nextInstanceIDs.get(kindID);
assert(id !== undefined);
const next = id + 1;
const next = id + 1n;
nextInstanceIDs.set(kindID, next);
if (isDurable) {
const durableKindDescriptor = kindIDToDescriptor.get(kindID);
assert(durableKindDescriptor);
durableKindDescriptor.nextInstanceID = next;
saveDurableKindDescriptor(durableKindDescriptor);
saveNextInstanceID(kindID);
}
return id;
}

function defineKind(tag, init, behavior, options) {
const kindID = `${allocateExportID()}`;
syscall.vatstoreSet(`vom.vkind.${kindID}`, JSON.stringify({ kindID, tag }));
nextInstanceIDs.set(kindID, 1);
saveVirtualKindDescriptor(kindID, { kindID, tag });
nextInstanceIDs.set(kindID, 1n);
return defineKindInternal(
kindID,
tag,
Expand All @@ -1065,8 +1090,8 @@ export function makeVirtualObjectManager(

function defineKindMulti(tag, init, behavior, options) {
const kindID = `${allocateExportID()}`;
syscall.vatstoreSet(`vom.vkind.${kindID}`, JSON.stringify({ kindID, tag }));
nextInstanceIDs.set(kindID, 1);
saveVirtualKindDescriptor(kindID, { kindID, tag });
nextInstanceIDs.set(kindID, 1n);
return defineKindInternal(
kindID,
tag,
Expand All @@ -1086,16 +1111,19 @@ export function makeVirtualObjectManager(
const makeKindHandle = tag => {
assert(kindIDID, 'initializeKindHandleKind not called yet');
const kindID = `${allocateExportID()}`;
const kindIDvref = makeBaseRef(kindIDID, kindID, true);
const durableKindDescriptor = { kindID, tag, nextInstanceID: 1 };
const durableKindDescriptor = { kindID, tag };
const nextInstanceID = 1n;
kindIDToDescriptor.set(kindID, durableKindDescriptor);
nextInstanceIDs.set(kindID, nextInstanceID);
saveDurableKindDescriptor(durableKindDescriptor);
saveNextInstanceID(kindID);
/** @type {import('@agoric/vat-data').DurableKindHandle} */
// eslint-disable-next-line @typescript-eslint/prefer-ts-expect-error -- https://github.com/Agoric/agoric-sdk/issues/4620
// @ts-ignore cast
const kindHandle = Far('kind', {});
kindHandleToID.set(kindHandle, kindID);
kindIDToDescriptor.set(kindID, durableKindDescriptor);
const kindIDvref = makeBaseRef(kindIDID, kindID, true);
registerValue(kindIDvref, kindHandle, false);
saveDurableKindDescriptor(durableKindDescriptor);
return kindHandle;
};

Expand All @@ -1104,10 +1132,9 @@ export function makeVirtualObjectManager(
const kindID = kindHandleToID.get(kindHandle);
const durableKindDescriptor = kindIDToDescriptor.get(kindID);
assert(durableKindDescriptor);
const { tag, nextInstanceID } = durableKindDescriptor;
const { tag } = durableKindDescriptor;
!definedDurableKinds.has(kindID) ||
Fail`redefinition of durable kind ${tag}`;
nextInstanceIDs.set(kindID, nextInstanceID);
const maker = defineKindInternal(
kindID,
tag,
Expand All @@ -1127,10 +1154,9 @@ export function makeVirtualObjectManager(
const kindID = kindHandleToID.get(kindHandle);
const durableKindDescriptor = kindIDToDescriptor.get(kindID);
assert(durableKindDescriptor);
const { tag, nextInstanceID } = durableKindDescriptor;
const { tag } = durableKindDescriptor;
!definedDurableKinds.has(kindID) ||
Fail`redefinition of durable kind "${tag}"`;
nextInstanceIDs.set(kindID, nextInstanceID);
const maker = defineKindInternal(
kindID,
tag,
Expand All @@ -1150,10 +1176,12 @@ export function makeVirtualObjectManager(
const missing = [];
const prefix = 'vom.dkind.';
for (const key of enumerateKeysWithPrefix(syscall, prefix)) {
const value = syscall.vatstoreGet(key);
const durableKindDescriptor = JSON.parse(value);
if (!definedDurableKinds.has(durableKindDescriptor.kindID)) {
missing.push(durableKindDescriptor.tag);
if (key.endsWith('.descriptor')) {
const value = syscall.vatstoreGet(key);
const durableKindDescriptor = JSON.parse(value);
if (!definedDurableKinds.has(durableKindDescriptor.kindID)) {
missing.push(durableKindDescriptor.tag);
}
}
}
if (missing.length) {
Expand Down
5 changes: 3 additions & 2 deletions packages/swingset-liveslots/test/test-handled-promises.js
Original file line number Diff line number Diff line change
Expand Up @@ -160,8 +160,9 @@ const kvStoreDataV1 = Object.entries({
'vc.4.|nextOrdinal': '1',
'vc.4.|schemata':
'{"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.dkind.10.descriptor':
'{"kindID":"10","tag":"DurablePromiseIgnorer","unfaceted":true}',
'vom.dkind.10.nextID': '2',
'vom.o+d10/1': '{}',
'vom.rc.o+d1/10': '1',
'vom.rc.o+d10/1': '3',
Expand Down
2 changes: 1 addition & 1 deletion packages/swingset-liveslots/test/test-liveslots.js
Original file line number Diff line number Diff line change
Expand Up @@ -666,7 +666,7 @@ test('capdata size limit on syscalls', async t => {
const expectKindDef = kid =>
t.deepEqual(log.shift(), {
type: 'vatstoreSet',
key: `vom.vkind.${kid}`,
key: `vom.vkind.${kid}.descriptor`,
value: `{"kindID":"${kid}","tag":"test"}`,
});
const expectStore = kid =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -972,7 +972,7 @@ async function voRefcountManagementTest1(t, isf) {

// holder Kind is the next-to-last created kind, which gets idCounters.exportID-2
const holderKindID = JSON.parse(fakestore.get(`idCounters`)).exportID - 2;
t.is(JSON.parse(fakestore.get(`vom.vkind.${holderKindID}`)).tag, 'holder');
t.is(JSON.parse(fakestore.get(`vom.vkind.${holderKindID}.descriptor`)).tag, 'holder');

await dispatchMessageSuccessfully('prepareStore3');
// create three VOs (tag "holder") which hold our vref in their vdata
Expand Down Expand Up @@ -1013,7 +1013,7 @@ async function voRefcountManagementTest2(t, isf) {

// holder Kind is the next-to-last created kind
const holderKindID = JSON.parse(fakestore.get(`idCounters`)).exportID - 2;
t.is(JSON.parse(fakestore.get(`vom.vkind.${holderKindID}`)).tag, 'holder');
t.is(JSON.parse(fakestore.get(`vom.vkind.${holderKindID}.descriptor`)).tag, 'holder');

await dispatchMessageSuccessfully('prepareStore3');
// create three VOs (tag "holder") which hold our vref in their vdata
Expand Down Expand Up @@ -1054,7 +1054,7 @@ async function voRefcountManagementTest3(t, isf) {

// holder Kind is the next-to-last created kind
const holderKindID = JSON.parse(fakestore.get(`idCounters`)).exportID - 2;
t.is(JSON.parse(fakestore.get(`vom.vkind.${holderKindID}`)).tag, 'holder');
t.is(JSON.parse(fakestore.get(`vom.vkind.${holderKindID}.descriptor`)).tag, 'holder');

// make a linked list with virtual "holder" objects
await dispatchMessageSuccessfully('prepareStoreLinked');
Expand Down Expand Up @@ -1104,7 +1104,7 @@ test.serial('presence refcount management 1', async t => {

// holder Kind is the next-to-last created kind, which gets idCounters.exportID-2
const holderKindID = JSON.parse(fakestore.get(`idCounters`)).exportID - 2;
t.is(JSON.parse(fakestore.get(`vom.vkind.${holderKindID}`)).tag, 'holder');
t.is(JSON.parse(fakestore.get(`vom.vkind.${holderKindID}.descriptor`)).tag, 'holder');

// create three VOs (tag "holder") which hold our vref in their vdata
await dispatchMessageSuccessfully('prepareStore3');
Expand Down Expand Up @@ -1143,7 +1143,7 @@ test.serial('presence refcount management 2', async t => {

// holder Kind is the next-to-last created kind, which gets idCounters.exportID-2
const holderKindID = JSON.parse(fakestore.get(`idCounters`)).exportID - 2;
t.is(JSON.parse(fakestore.get(`vom.vkind.${holderKindID}`)).tag, 'holder');
t.is(JSON.parse(fakestore.get(`vom.vkind.${holderKindID}.descriptor`)).tag, 'holder');

await dispatchMessageSuccessfully('prepareStore3');

Expand Down Expand Up @@ -1175,7 +1175,7 @@ test.serial('remotable refcount management 1', async t => {

// holder Kind is the next-to-last created kind, which gets idCounters.exportID-2
const holderKindID = JSON.parse(fakestore.get(`idCounters`)).exportID - 2;
t.is(JSON.parse(fakestore.get(`vom.vkind.${holderKindID}`)).tag, 'holder');
t.is(JSON.parse(fakestore.get(`vom.vkind.${holderKindID}.descriptor`)).tag, 'holder');

await dispatchMessageSuccessfully('makeAndHoldRemotable');
// the Remotable is currently held by RAM, and doesn't get a vref
Expand Down Expand Up @@ -1217,7 +1217,7 @@ test.serial('remotable refcount management 2', async t => {
const { fakestore } = v;

const holderKindID = JSON.parse(fakestore.get(`idCounters`)).exportID - 2;
t.is(JSON.parse(fakestore.get(`vom.vkind.${holderKindID}`)).tag, 'holder');
t.is(JSON.parse(fakestore.get(`vom.vkind.${holderKindID}.descriptor`)).tag, 'holder');

await dispatchMessageSuccessfully('makeAndHoldRemotable');
await dispatchMessageSuccessfully('prepareStore3');
Expand Down Expand Up @@ -1323,7 +1323,7 @@ test.serial('VO holding non-VO', async t => {
await dispatchMessageSuccessfully('makeAndHoldRemotable');
// still held in RAM, no vref allocated yet
const holderKindID = JSON.parse(fakestore.get(`idCounters`)).exportID - 2;
t.is(JSON.parse(fakestore.get(`vom.vkind.${holderKindID}`)).tag, 'holder');
t.is(JSON.parse(fakestore.get(`vom.vkind.${holderKindID}.descriptor`)).tag, 'holder');
// holder is first instance created of that kind
const holderVref = `o+v${holderKindID}/1`;

Expand Down
Loading

0 comments on commit 4393c69

Please sign in to comment.