Skip to content

Commit f84b136

Browse files
committed
feat: relocate snapshot metadata from kvStore to snapStore
Completes #6742
1 parent 41afbb0 commit f84b136

18 files changed

+183
-364
lines changed

packages/SwingSet/src/controller/controller.js

+6-4
Original file line numberDiff line numberDiff line change
@@ -115,21 +115,23 @@ export function makeStartXSnap(bundles, { snapStore, env, spawn }) {
115115
}
116116

117117
/**
118+
* @param {string} vatID
118119
* @param {string} name
119120
* @param {(request: Uint8Array) => Promise<Uint8Array>} handleCommand
120121
* @param {boolean} [metered]
121-
* @param {string} [snapshotHash]
122+
* @param {boolean} [reload]
122123
*/
123124
async function startXSnap(
125+
vatID,
124126
name,
125127
handleCommand,
126128
metered,
127-
snapshotHash = undefined,
129+
reload = false,
128130
) {
129131
const meterOpts = metered ? {} : { meteringLimit: 0 };
130-
if (snapStore && snapshotHash) {
132+
if (snapStore && reload) {
131133
// console.log('startXSnap from', { snapshotHash });
132-
return snapStore.load(snapshotHash, async snapshot => {
134+
return snapStore.load(vatID, async snapshot => {
133135
const xs = doXSnap({
134136
snapshot,
135137
name,

packages/SwingSet/src/kernel/state/kernelKeeper.js

+2-6
Original file line numberDiff line numberDiff line change
@@ -89,8 +89,7 @@ const enableKernelGC = true;
8989
// v$NN.reapInterval = $NN or 'never'
9090
// v$NN.reapCountdown = $NN or 'never'
9191
// exclude from consensus
92-
// local.v$NN.lastSnapshot = JSON({ snapshotID, startPos })
93-
// local.snapshot.$id = [vatID, ...]
92+
// local.*
9493

9594
// m$NN.remaining = $NN // remaining capacity (in computrons) or 'unlimited'
9695
// m$NN.threshold = $NN // notify when .remaining first drops below this
@@ -786,10 +785,7 @@ export default function makeKernelKeeper(kernelStorage, kernelSlog) {
786785
const promisePrefix = `${vatID}.c.p`;
787786
const kernelPromisesToReject = [];
788787

789-
const old = vatKeeper.getLastSnapshot();
790-
if (old) {
791-
vatKeeper.removeFromSnapshot(old.snapshotID);
792-
}
788+
vatKeeper.deleteSnapshots();
793789

794790
// Note: ASCII order is "+,-./", and we rely upon this to split the
795791
// keyspace into the various o+NN/o-NN/etc spaces. If we were using a

packages/SwingSet/src/kernel/state/vatKeeper.js

+26-95
Original file line numberDiff line numberDiff line change
@@ -45,14 +45,8 @@ export function initializeVatState(kvStore, streamStore, vatID) {
4545
kvStore.set(`${vatID}.d.nextID`, `${FIRST_DEVICE_ID}`);
4646
kvStore.set(`${vatID}.nextDeliveryNum`, `0`);
4747
kvStore.set(`${vatID}.incarnationNumber`, `1`);
48-
kvStore.set(
49-
`${vatID}.t.startPosition`,
50-
`${JSON.stringify(streamStore.STREAM_START)}`,
51-
);
52-
kvStore.set(
53-
`${vatID}.t.endPosition`,
54-
`${JSON.stringify(streamStore.STREAM_START)}`,
55-
);
48+
kvStore.set(`${vatID}.t.startPosition`, `${streamStore.STREAM_START}`);
49+
kvStore.set(`${vatID}.t.endPosition`, `${streamStore.STREAM_START}`);
5650
}
5751

5852
/**
@@ -486,9 +480,9 @@ export function makeVatKeeper(
486480
*/
487481
function* getTranscript(startPos) {
488482
if (startPos === undefined) {
489-
startPos = JSON.parse(getRequired(`${vatID}.t.startPosition`));
483+
startPos = Number(getRequired(`${vatID}.t.startPosition`));
490484
}
491-
const endPos = JSON.parse(getRequired(`${vatID}.t.endPosition`));
485+
const endPos = Number(getRequired(`${vatID}.t.endPosition`));
492486
for (const entry of streamStore.readStream(
493487
transcriptStream,
494488
/** @type { StreamPosition } */ (startPos),
@@ -504,88 +498,38 @@ export function makeVatKeeper(
504498
* @param {object} entry The transcript entry to append.
505499
*/
506500
function addToTranscript(entry) {
507-
const oldPos = JSON.parse(getRequired(`${vatID}.t.endPosition`));
501+
const oldPos = Number(getRequired(`${vatID}.t.endPosition`));
508502
const newPos = streamStore.writeStreamItem(
509503
transcriptStream,
510504
JSON.stringify(entry),
511505
oldPos,
512506
);
513-
kvStore.set(`${vatID}.t.endPosition`, `${JSON.stringify(newPos)}`);
507+
kvStore.set(`${vatID}.t.endPosition`, `${newPos}`);
514508
}
515509

516510
/** @returns {StreamPosition} */
517511
function getTranscriptEndPosition() {
518-
return JSON.parse(
512+
const endPosition =
519513
kvStore.get(`${vatID}.t.endPosition`) ||
520-
assert.fail('missing endPosition'),
521-
);
514+
assert.fail('missing endPosition');
515+
return Number(endPosition);
522516
}
523517

524-
/**
525-
* @returns {{ snapshotID: string, startPos: StreamPosition } | undefined}
526-
*/
527-
function getLastSnapshot() {
528-
const notation = kvStore.get(`local.${vatID}.lastSnapshot`);
529-
if (!notation) {
518+
function getSnapshotInfo() {
519+
if (snapStore) {
520+
return snapStore.getSnapshotInfo(vatID);
521+
} else {
530522
return undefined;
531523
}
532-
const { snapshotID, startPos } = JSON.parse(notation);
533-
assert.typeof(snapshotID, 'string');
534-
assert(startPos);
535-
return { snapshotID, startPos };
536524
}
537525

538526
function transcriptSnapshotStats() {
539527
const totalEntries = getTranscriptEndPosition();
540-
const lastSnapshot = getLastSnapshot();
541-
const snapshottedEntries = lastSnapshot ? lastSnapshot.startPos : 0;
528+
const snapshotInfo = getSnapshotInfo();
529+
const snapshottedEntries = snapshotInfo ? snapshotInfo.endPos : 0;
542530
return { totalEntries, snapshottedEntries };
543531
}
544532

545-
/**
546-
* Add vatID to consumers of a snapshot.
547-
*
548-
* @param {string} snapshotID
549-
*/
550-
function addToSnapshot(snapshotID) {
551-
const key = `local.snapshot.${snapshotID}`;
552-
const consumers = JSON.parse(kvStore.get(key) || '[]');
553-
assert(Array.isArray(consumers));
554-
555-
// We can't completely rule out the possibility that
556-
// a vat will use the same snapshot twice in a row.
557-
//
558-
// PERFORMANCE NOTE: we assume consumer lists are short;
559-
// usually length 1. So O(n) search here is better
560-
// than keeping the list sorted.
561-
if (!consumers.includes(vatID)) {
562-
consumers.push(vatID);
563-
kvStore.set(key, JSON.stringify(consumers));
564-
// console.log('addToSnapshot result:', { vatID, snapshotID, consumers });
565-
}
566-
}
567-
568-
/**
569-
* Remove vatID from consumers of a snapshot.
570-
*
571-
* @param {string} snapshotID
572-
*/
573-
function removeFromSnapshot(snapshotID) {
574-
const key = `local.snapshot.${snapshotID}`;
575-
const consumersJSON = kvStore.get(key);
576-
if (!consumersJSON) {
577-
throw Fail`cannot remove ${vatID}: ${key} key not defined`;
578-
}
579-
const consumers = JSON.parse(consumersJSON);
580-
assert(Array.isArray(consumers));
581-
const ix = consumers.indexOf(vatID);
582-
assert(ix >= 0);
583-
consumers.splice(ix, 1);
584-
// console.log('removeFromSnapshot done:', { vatID, snapshotID, consumers });
585-
kvStore.set(key, JSON.stringify(consumers));
586-
return consumers.length;
587-
}
588-
589533
/**
590534
* Store a snapshot, if given a snapStore.
591535
*
@@ -597,26 +541,15 @@ export function makeVatKeeper(
597541
return false;
598542
}
599543

600-
const info = await manager.makeSnapshot(snapStore);
544+
const endPosition = getTranscriptEndPosition();
545+
const info = await manager.makeSnapshot(endPosition, snapStore);
601546
const {
602547
hash: snapshotID,
603548
rawByteCount,
604549
rawSaveSeconds,
605550
compressedByteCount,
606551
compressSeconds,
607552
} = info;
608-
const old = getLastSnapshot();
609-
if (old && old.snapshotID !== snapshotID) {
610-
if (removeFromSnapshot(old.snapshotID) === 0) {
611-
snapStore.deleteSnapshot(old.snapshotID);
612-
}
613-
}
614-
const endPosition = getTranscriptEndPosition();
615-
kvStore.set(
616-
`local.${vatID}.lastSnapshot`,
617-
JSON.stringify({ snapshotID, startPos: endPosition }),
618-
);
619-
addToSnapshot(snapshotID);
620553
kernelSlog.write({
621554
type: 'heap-snapshot-save',
622555
vatID,
@@ -630,17 +563,15 @@ export function makeVatKeeper(
630563
return true;
631564
}
632565

566+
function deleteSnapshots() {
567+
if (snapStore) {
568+
snapStore.deleteSnapshots(vatID);
569+
}
570+
}
571+
633572
function removeSnapshotAndTranscript() {
634-
const skey = `local.${vatID}.lastSnapshot`;
635573
if (snapStore) {
636-
const notation = kvStore.get(skey);
637-
if (notation) {
638-
const { snapshotID } = JSON.parse(notation);
639-
if (removeFromSnapshot(snapshotID) === 0) {
640-
snapStore.deleteSnapshot(snapshotID);
641-
}
642-
kvStore.delete(skey);
643-
}
574+
snapStore.deleteSnapshots(vatID);
644575
}
645576

646577
const endPos = getRequired(`${vatID}.t.endPosition`);
@@ -719,8 +650,8 @@ export function makeVatKeeper(
719650
vatStats,
720651
dumpState,
721652
saveSnapshot,
722-
getLastSnapshot,
723-
removeFromSnapshot,
653+
deleteSnapshots,
654+
getSnapshotInfo,
724655
removeSnapshotAndTranscript,
725656
});
726657
}

packages/SwingSet/src/kernel/vat-loader/manager-helper.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ import { makeTranscriptManager } from './transcript.js';
4747
/**
4848
*
4949
* @typedef { { getManager: (shutdown: () => Promise<void>,
50-
* makeSnapshot?: (ss: SnapStore) => Promise<SnapshotInfo>) => VatManager,
50+
* makeSnapshot?: (endPos: number, ss: SnapStore) => Promise<SnapshotResult>) => VatManager,
5151
* syscallFromWorker: (vso: VatSyscallObject) => VatSyscallResult,
5252
* setDeliverToWorker: (dtw: unknown) => void,
5353
* } } ManagerKit
@@ -260,7 +260,7 @@ function makeManagerKit(
260260
/**
261261
*
262262
* @param { () => Promise<void>} shutdown
263-
* @param {(ss: SnapStore) => Promise<SnapshotInfo>} makeSnapshot
263+
* @param {(endPos: number, ss: SnapStore) => Promise<SnapshotResult>} makeSnapshot
264264
* @returns {VatManager}
265265
*/
266266
function getManager(shutdown, makeSnapshot) {

packages/SwingSet/src/kernel/vat-loader/manager-subprocess-xsnap.js

+12-10
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ const decoder = new TextDecoder();
2222
* allVatPowers: VatPowers,
2323
* kernelKeeper: KernelKeeper,
2424
* kernelSlog: KernelSlog,
25-
* startXSnap: (name: string, handleCommand: AsyncHandler, metered?: boolean, snapshotHash?: string) => Promise<XSnap>,
25+
* startXSnap: (vatID: string, name: string, handleCommand: AsyncHandler, metered?: boolean, reload?: boolean) => Promise<XSnap>,
2626
* testLog: (...args: unknown[]) => void,
2727
* }} tools
2828
* @returns {VatManagerFactory}
@@ -112,10 +112,10 @@ export function makeXsSubprocessFactory({
112112
}
113113

114114
const vatKeeper = kernelKeeper.provideVatKeeper(vatID);
115-
const lastSnapshot = vatKeeper.getLastSnapshot();
116-
if (lastSnapshot) {
117-
const { snapshotID } = lastSnapshot;
118-
kernelSlog.write({ type: 'heap-snapshot-load', vatID, snapshotID });
115+
const snapshotInfo = vatKeeper.getSnapshotInfo();
116+
if (snapshotInfo) {
117+
const { hash, endPos } = snapshotInfo;
118+
kernelSlog.write({ type: 'heap-snapshot-load', vatID, hash, endPos });
119119
}
120120

121121
// `startXSnap` adds `argName` as a dummy argument so that 'ps'
@@ -125,10 +125,11 @@ export function makeXsSubprocessFactory({
125125

126126
// start the worker and establish a connection
127127
const worker = await startXSnap(
128+
vatID,
128129
argName,
129130
handleCommand,
130131
metered,
131-
lastSnapshot ? lastSnapshot.snapshotID : undefined,
132+
!!snapshotInfo,
132133
);
133134

134135
/** @type { (item: Tagged) => Promise<CrankResults> } */
@@ -144,7 +145,7 @@ export function makeXsSubprocessFactory({
144145
return { ...result, reply: [tag, ...rest] };
145146
}
146147

147-
if (lastSnapshot) {
148+
if (snapshotInfo) {
148149
parentLog(vatID, `snapshot loaded. dispatch ready.`);
149150
} else {
150151
parentLog(vatID, `instructing worker to load bundle..`);
@@ -209,11 +210,12 @@ export function makeXsSubprocessFactory({
209210
return worker.close().then(_ => undefined);
210211
}
211212
/**
213+
* @param {number} endPos
212214
* @param {SnapStore} snapStore
213-
* @returns {Promise<SnapshotInfo>}
215+
* @returns {Promise<SnapshotResult>}
214216
*/
215-
function makeSnapshot(snapStore) {
216-
return snapStore.save(fn => worker.snapshot(fn));
217+
function makeSnapshot(endPos, snapStore) {
218+
return snapStore.save(vatID, endPos, fn => worker.snapshot(fn));
217219
}
218220

219221
return mk.getManager(shutdown, makeSnapshot);

packages/SwingSet/src/kernel/vat-warehouse.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -125,9 +125,9 @@ export function makeVatWarehouse(kernelKeeper, vatLoader, policyOptions) {
125125
// TODO(3218): persist this option; avoid spinning up a vat that isn't pipelined
126126
const { enablePipelining = false } = options;
127127

128-
const lastSnapshot = vatKeeper.getLastSnapshot();
128+
const snapshotInfo = vatKeeper.getSnapshotInfo();
129129
await manager.replayTranscript(
130-
lastSnapshot ? lastSnapshot.startPos : undefined,
130+
snapshotInfo ? snapshotInfo.endPos : undefined,
131131
);
132132

133133
const result = {

packages/SwingSet/src/types-ambient.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@
118118
* @typedef { import('./types-external.js').VatManagerFactory } VatManagerFactory
119119
* @typedef { import('./types-external.js').VatManager } VatManager
120120
* @typedef { import('./types-external.js').SnapStore } SnapStore
121-
* @typedef { import('./types-external.js').SnapshotInfo } SnapshotInfo
121+
* @typedef { import('./types-external.js').SnapshotResult } SnapshotResult
122122
* @typedef { import('./types-external.js').WaitUntilQuiescent } WaitUntilQuiescent
123123
*/
124124

packages/SwingSet/src/types-external.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,7 @@ export {};
221221
* } } VatManagerFactory
222222
* @typedef { { deliver: (delivery: VatDeliveryObject) => Promise<VatDeliveryResult>,
223223
* replayTranscript: (startPos: StreamPosition | undefined) => Promise<number?>,
224-
* makeSnapshot?: (ss: SnapStore) => Promise<SnapshotInfo>,
224+
* makeSnapshot?: (endPos: number, ss: SnapStore) => Promise<SnapshotResult>,
225225
* shutdown: () => Promise<void>,
226226
* } } VatManager
227227
*
@@ -291,7 +291,7 @@ export {};
291291
/**
292292
* @typedef { import('@agoric/swing-store').KVStore } KVStore
293293
* @typedef { import('@agoric/swing-store').SnapStore } SnapStore
294-
* @typedef { import('@agoric/swing-store').SnapshotInfo } SnapshotInfo
294+
* @typedef { import('@agoric/swing-store').SnapshotResult } SnapshotResult
295295
* @typedef { import('@agoric/swing-store').StreamStore } StreamStore
296296
* @typedef { import('@agoric/swing-store').StreamPosition } StreamPosition
297297
* @typedef { import('@agoric/swing-store').SwingStore } SwingStore

packages/SwingSet/test/test-state.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -697,9 +697,9 @@ test('crankhash - skip keys', t => {
697697
// certain local keys are excluded from consensus, and should not affect
698698
// the hash
699699
k.kvStore.set('one', '1');
700-
k.kvStore.set('local.snapshot.XYZ', '["vat1234"]');
700+
k.kvStore.set('local.xsnapshot.XYZ', '["vat1234"]');
701701
k.kvStore.set(
702-
'local.v1234.lastSnapshot',
702+
'local.v1234.xlastSnapshot',
703703
'{"snapshotID":"XYZ","startPos":4}',
704704
);
705705
t.throws(() => k.kvStore.set('host.foo', 'bar'));

packages/SwingSet/test/test-xsnap-errors.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ test('child termination distinguished from meter exhaustion', async t => {
3838
/** @type { any } */
3939
const kernelKeeper = {
4040
provideVatKeeper: () => ({
41-
getLastSnapshot: () => undefined,
41+
getSnapshotInfo: () => undefined,
4242
addToTranscript: () => undefined,
4343
}),
4444
getRelaxDurabilityRules: () => false,
@@ -60,6 +60,7 @@ test('child termination distinguished from meter exhaustion', async t => {
6060
// @ts-expect-error close enough for this test
6161
const managerOptions = { useTranscript: true };
6262
const schandler = _vso => ['ok', null];
63+
6364
const m = await xsWorkerFactory.createFromBundle(
6465
'v1',
6566
bundle,

0 commit comments

Comments
 (0)