Skip to content

Commit 4e0f679

Browse files
committed
feat: relocate snapshot metadata from kvStore to snapStore
Closes #6742
1 parent f06a171 commit 4e0f679

19 files changed

+292
-384
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.loadSnapshot(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

+30-103
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,34 @@ 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) {
530-
return undefined;
531-
}
532-
const { snapshotID, startPos } = JSON.parse(notation);
533-
assert.typeof(snapshotID, 'string');
534-
assert(startPos);
535-
return { snapshotID, startPos };
518+
function getSnapshotInfo() {
519+
return snapStore?.getSnapshotInfo(vatID);
536520
}
537521

538522
function transcriptSnapshotStats() {
539523
const totalEntries = getTranscriptEndPosition();
540-
const lastSnapshot = getLastSnapshot();
541-
const snapshottedEntries = lastSnapshot ? lastSnapshot.startPos : 0;
524+
const snapshotInfo = getSnapshotInfo();
525+
const snapshottedEntries = snapshotInfo ? snapshotInfo.endPos : 0;
542526
return { totalEntries, snapshottedEntries };
543527
}
544528

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-
589529
/**
590530
* Store a snapshot, if given a snapStore.
591531
*
@@ -597,50 +537,37 @@ export function makeVatKeeper(
597537
return false;
598538
}
599539

600-
const info = await manager.makeSnapshot(snapStore);
540+
const endPosition = getTranscriptEndPosition();
541+
const info = await manager.makeSnapshot(endPosition, snapStore);
601542
const {
602-
hash: snapshotID,
603-
rawByteCount,
543+
hash,
544+
uncompressedSize,
604545
rawSaveSeconds,
605-
compressedByteCount,
546+
compressedSize,
606547
compressSeconds,
607548
} = 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);
620549
kernelSlog.write({
621550
type: 'heap-snapshot-save',
622551
vatID,
623-
snapshotID,
624-
rawByteCount,
552+
hash,
553+
uncompressedSize,
625554
rawSaveSeconds,
626-
compressedByteCount,
555+
compressedSize,
627556
compressSeconds,
628557
endPosition,
629558
});
630559
return true;
631560
}
632561

562+
function deleteSnapshots() {
563+
if (snapStore) {
564+
snapStore.deleteVatSnapshots(vatID);
565+
}
566+
}
567+
633568
function removeSnapshotAndTranscript() {
634-
const skey = `local.${vatID}.lastSnapshot`;
635569
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-
}
570+
snapStore.deleteVatSnapshots(vatID);
644571
}
645572

646573
const endPos = getRequired(`${vatID}.t.endPosition`);
@@ -719,8 +646,8 @@ export function makeVatKeeper(
719646
vatStats,
720647
dumpState,
721648
saveSnapshot,
722-
getLastSnapshot,
723-
removeFromSnapshot,
649+
deleteSnapshots,
650+
getSnapshotInfo,
724651
removeSnapshotAndTranscript,
725652
});
726653
}

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
@@ -259,7 +259,7 @@ function makeManagerKit(
259259
/**
260260
*
261261
* @param { () => Promise<void>} shutdown
262-
* @param {(ss: SnapStore) => Promise<SnapshotInfo>} makeSnapshot
262+
* @param {(endPos: number, ss: SnapStore) => Promise<SnapshotResult>} makeSnapshot
263263
* @returns {VatManager}
264264
*/
265265
function getManager(shutdown, makeSnapshot) {

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

+11-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,9 @@ 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+
kernelSlog.write({ type: 'heap-snapshot-load', vatID, ...snapshotInfo });
119118
}
120119

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

126125
// start the worker and establish a connection
127126
const worker = await startXSnap(
127+
vatID,
128128
argName,
129129
handleCommand,
130130
metered,
131-
lastSnapshot ? lastSnapshot.snapshotID : undefined,
131+
!!snapshotInfo,
132132
);
133133

134134
/** @type { (item: Tagged) => Promise<CrankResults> } */
@@ -144,7 +144,7 @@ export function makeXsSubprocessFactory({
144144
return { ...result, reply: [tag, ...rest] };
145145
}
146146

147-
if (lastSnapshot) {
147+
if (snapshotInfo) {
148148
parentLog(vatID, `snapshot loaded. dispatch ready.`);
149149
} else {
150150
parentLog(vatID, `instructing worker to load bundle..`);
@@ -209,11 +209,12 @@ export function makeXsSubprocessFactory({
209209
return worker.close().then(_ => undefined);
210210
}
211211
/**
212+
* @param {number} endPos
212213
* @param {SnapStore} snapStore
213-
* @returns {Promise<SnapshotInfo>}
214+
* @returns {Promise<SnapshotResult>}
214215
*/
215-
function makeSnapshot(snapStore) {
216-
return snapStore.save(fn => worker.snapshot(fn));
216+
function makeSnapshot(endPos, snapStore) {
217+
return snapStore.saveSnapshot(vatID, endPos, fn => worker.snapshot(fn));
217218
}
218219

219220
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/snapshots/test-xsnap-store.js.md

+3-3
Original file line numberDiff line numberDiff line change
@@ -11,24 +11,24 @@ Generated by [AVA](https://avajs.dev).
1111
{
1212
compressSeconds: 0,
1313
hash: '8a0e3873976c50462d1b1dac59c912152b0e5cad5eeb9deca0ca64a087b4a873',
14-
rawByteCount: 167887,
1514
rawSaveSeconds: 0,
15+
uncompressedSize: 167887,
1616
}
1717

1818
> after SES boot - sensitive to SES-shim, XS, and supervisor
1919
2020
{
2121
compressSeconds: 0,
2222
hash: '253ffe0fb0b9e555119f046990d58fb85693e1170e681adb52211f49d94e37d0',
23-
rawByteCount: 775831,
2423
rawSaveSeconds: 0,
24+
uncompressedSize: 775831,
2525
}
2626

2727
> after use of harden() - sensitive to SES-shim, XS, and supervisor
2828
2929
{
3030
compressSeconds: 0,
3131
hash: '2bca522840c90b8a0519fe846642746f01f62205154877c2332ad8829b69aa3e',
32-
rawByteCount: 777983,
3332
rawSaveSeconds: 0,
33+
uncompressedSize: 777983,
3434
}
Binary file not shown.

0 commit comments

Comments
 (0)