Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: demand-paged vats in solo, chain #2848

Merged
merged 28 commits into from
Jun 26, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
9582ede
refactor(replay): hoist handle declaration
dckc Jun 20, 2021
eaf5ae6
chore(xsnap): clarify names of snapStore temp files for debugging
dckc Jun 17, 2021
e7708a4
feat(swingset): initializeSwingset snapshots XS supervisor
dckc Jun 15, 2021
7c10cd8
feat(swingset): save snapshot periodically after deliveries
dckc Jun 15, 2021
8e6116b
feat(swingset): load vats from snapshots
dckc Jun 16, 2021
aebbb2f
refactor(vatWarehouse): factor out, test LRU logic
dckc Jun 19, 2021
185704a
fix(vat-warehouse): remove vatID from LRU when evicting
dckc Jun 20, 2021
5477574
chore(vatKeeper): prune debug logging in saveSnapshot (FIXUP)
dckc Jun 20, 2021
66eaad5
feat(swingset): log bringing vats online (esp from snapshot)
dckc Jun 21, 2021
3a8db9f
chore: resove "skip crank buffering?" issue
dckc Jun 24, 2021
dc83a0f
chore: prune makeSnapshot arg from evict()
dckc Jun 24, 2021
f42bdd4
test(swingset): teardown snap-store
dckc Jun 24, 2021
05459c7
chore(swingset): initial sketch of snapshot reload test
warner Jun 24, 2021
73b26a0
refactor: let itemCount be not-optional in StreamPosition
dckc Jun 24, 2021
aec603d
feat: snapshot early then infrequently
dckc Jun 24, 2021
6fe0712
test: provide getLastSnapshot to mock vatKeeper
dckc Jun 25, 2021
20e8351
chore: vattp: turn off managerType local work-around
dckc Jun 25, 2021
7f365b7
chore: vat-warehouse: initial snapshot after 2 deliveries
dckc Jun 25, 2021
5f8ce81
chore: prune deterministic snapshot assertion
dckc Jun 25, 2021
6b48f1e
chore: fix test-snapstore ld.asset
dckc Jun 25, 2021
abc9c9a
chore: never mind supervisorHash optimization
dckc Jun 25, 2021
f1a2bc3
docs(swingset): document lastSnapshot kernel DB key
dckc Jun 25, 2021
3d34bee
refactor: capitalize makeSnapStore consistently
dckc Jun 25, 2021
f9a1eb2
refactor: replayTranscript caller is responsible to getLastSnapshot
dckc Jun 25, 2021
06158c8
test(swingset): consistent vat-warehouse test naming
dckc Jun 25, 2021
bd9563d
refactor(swingset): compute transcriptSnapshotStats in vatKeeper
dckc Jun 25, 2021
7e05a24
chore: use harden, not freeze; clarify lru
dckc Jun 25, 2021
a4b01eb
chore: use distinct fixture directories to avoid collision
warner Jun 25, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 19 additions & 35 deletions packages/SwingSet/src/controller.js
Original file line number Diff line number Diff line change
@@ -1,21 +1,19 @@
/* global require */
// @ts-check
import fs from 'fs';
import path from 'path';
import process from 'process';
import re2 from 're2';
import { performance } from 'perf_hooks';
import { spawn as ambientSpawn } from 'child_process';
import { type as osType } from 'os';
import { Worker } from 'worker_threads';
import anylogger from 'anylogger';
import { tmpName } from 'tmp';

import { assert, details as X } from '@agoric/assert';
import { isTamed, tameMetering } from '@agoric/tame-metering';
import { importBundle } from '@agoric/import-bundle';
import { makeMeteringTransformer } from '@agoric/transform-metering';
import { xsnap, makeSnapstore, recordXSnap } from '@agoric/xsnap';
import { xsnap, recordXSnap } from '@agoric/xsnap';

import engineGC from './engine-gc.js';
import { WeakRef, FinalizationRegistry } from './weakref.js';
Expand Down Expand Up @@ -49,12 +47,12 @@ function unhandledRejectionHandler(e) {
/**
* @param {{ moduleFormat: string, source: string }[]} bundles
* @param {{
* snapstorePath?: string,
* snapStore?: SnapStore,
* spawn: typeof import('child_process').spawn
* env: Record<string, string | undefined>,
* }} opts
*/
export function makeStartXSnap(bundles, { snapstorePath, env, spawn }) {
export function makeStartXSnap(bundles, { snapStore, env, spawn }) {
/** @type { import('@agoric/xsnap/src/xsnap').XSnapOptions } */
const xsnapOpts = {
os: osType(),
Expand All @@ -79,37 +77,27 @@ export function makeStartXSnap(bundles, { snapstorePath, env, spawn }) {
};
}

/** @type { ReturnType<typeof makeSnapstore> } */
let snapStore;

if (snapstorePath) {
fs.mkdirSync(snapstorePath, { recursive: true });

snapStore = makeSnapstore(snapstorePath, {
tmpName,
existsSync: fs.existsSync,
createReadStream: fs.createReadStream,
createWriteStream: fs.createWriteStream,
rename: fs.promises.rename,
unlink: fs.promises.unlink,
resolve: path.resolve,
});
}

let supervisorHash = '';
/**
* @param {string} name
* @param {(request: Uint8Array) => Promise<Uint8Array>} handleCommand
* @param { boolean } [metered]
* @param { string } [snapshotHash]
*/
async function startXSnap(name, handleCommand, metered) {
if (supervisorHash) {
return snapStore.load(supervisorHash, async snapshot => {
async function startXSnap(
name,
handleCommand,
metered,
snapshotHash = undefined,
) {
if (snapStore && snapshotHash) {
// console.log('startXSnap from', { snapshotHash });
return snapStore.load(snapshotHash, async snapshot => {
const xs = doXSnap({ snapshot, name, handleCommand, ...xsnapOpts });
await xs.evaluate('null'); // ensure that spawn is done
return xs;
});
}
// console.log('fresh xsnap', { snapStore: snapStore });
const meterOpts = metered ? {} : { meteringLimit: 0 };
const worker = doXSnap({ handleCommand, name, ...meterOpts, ...xsnapOpts });

Expand All @@ -121,9 +109,6 @@ export function makeStartXSnap(bundles, { snapstorePath, env, spawn }) {
// eslint-disable-next-line no-await-in-loop
await worker.evaluate(`(${bundle.source}\n)()`.trim());
}
if (snapStore) {
supervisorHash = await snapStore.save(async fn => worker.snapshot(fn));
}
return worker;
}
return startXSnap;
Expand All @@ -140,7 +125,6 @@ export function makeStartXSnap(bundles, { snapstorePath, env, spawn }) {
* slogFile?: string,
* testTrackDecref?: unknown,
* warehousePolicy?: { maxVatsOnline?: number },
* snapstorePath?: string,
* spawn?: typeof import('child_process').spawn,
* env?: Record<string, string | undefined>
* }} runtimeOptions
Expand All @@ -162,7 +146,6 @@ export async function makeSwingsetController(
debugPrefix = '',
slogCallbacks,
slogFile,
snapstorePath,
spawn = ambientSpawn,
warehousePolicy = {},
} = runtimeOptions;
Expand Down Expand Up @@ -300,7 +283,11 @@ export async function makeSwingsetController(
// @ts-ignore assume supervisorBundle is set
JSON.parse(kvStore.get('supervisorBundle')),
];
const startXSnap = makeStartXSnap(bundles, { snapstorePath, env, spawn });
const startXSnap = makeStartXSnap(bundles, {
snapStore: hostStorage.snapStore,
env,
spawn,
});

const kernelEndowments = {
waitUntilQuiescent,
Expand Down Expand Up @@ -430,7 +417,6 @@ export async function makeSwingsetController(
* debugPrefix?: string,
* slogCallbacks?: unknown,
* testTrackDecref?: unknown,
* snapstorePath?: string,
* warehousePolicy?: { maxVatsOnline?: number },
* slogFile?: string,
* }} runtimeOptions
Expand All @@ -447,15 +433,13 @@ export async function buildVatController(
kernelBundles,
debugPrefix,
slogCallbacks,
snapstorePath,
warehousePolicy,
slogFile,
} = runtimeOptions;
const actualRuntimeOptions = {
verbose,
debugPrefix,
slogCallbacks,
snapstorePath,
warehousePolicy,
slogFile,
};
Expand Down
5 changes: 0 additions & 5 deletions packages/SwingSet/src/initializeSwingset.js
Original file line number Diff line number Diff line change
Expand Up @@ -310,11 +310,6 @@ export async function initializeSwingset(
// it to comms
config.vats.vattp = {
bundle: kernelBundles.vattp,
creationOptions: {
// we saw evidence of vattp dropping messages, and out of caution,
// we're keeping it on an in-kernel worker for now. See #3039.
managerType: 'local',
},
};

// timer wrapper vat is added automatically, but TODO: bootstraps must
Expand Down
9 changes: 8 additions & 1 deletion packages/SwingSet/src/kernel/kernel.js
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,11 @@ export default function buildKernel(
} = kernelOptions;
const logStartup = verbose ? console.debug : () => 0;

const { kvStore, streamStore } = /** @type { HostStore } */ (hostStorage);
const {
kvStore,
streamStore,
snapStore,
} = /** @type { HostStore } */ (hostStorage);
insistStorageAPI(kvStore);
const { enhancedCrankBuffer, abortCrank, commitCrank } = wrapStorage(kvStore);
const vatAdminRootKref = kvStore.get('vatAdminRootKref');
Expand All @@ -138,6 +142,7 @@ export default function buildKernel(
enhancedCrankBuffer,
streamStore,
kernelSlog,
snapStore,
);

const meterManager = makeMeterManager(replaceGlobalMeter);
Expand Down Expand Up @@ -673,6 +678,8 @@ export default function buildKernel(
if (!didAbort) {
kernelKeeper.processRefcounts();
kernelKeeper.saveStats();
// eslint-disable-next-line no-use-before-define
await vatWarehouse.maybeSaveSnapshot();
}
commitCrank();
kernelKeeper.incrementCrankNumber();
Expand Down
10 changes: 9 additions & 1 deletion packages/SwingSet/src/kernel/state/kernelKeeper.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ const enableKernelGC = true;
// v$NN.nextDeliveryNum = $NN
// v$NN.t.endPosition = $NN
// v$NN.vs.$key = string
// v$NN.lastSnapshot = JSON({ snapshotID, startPos })

// d$NN.o.nextID = $NN
// d$NN.c.$kernelSlot = $deviceSlot = o-$NN/d+$NN/d-$NN
Expand Down Expand Up @@ -109,8 +110,14 @@ const FIRST_CRANK_NUMBER = 0n;
* @param {KVStorePlus} kvStore
* @param {StreamStore} streamStore
* @param {KernelSlog} kernelSlog
* @param {SnapStore=} snapStore
*/
export default function makeKernelKeeper(kvStore, streamStore, kernelSlog) {
export default function makeKernelKeeper(
kvStore,
streamStore,
kernelSlog,
snapStore = undefined,
) {
insistEnhancedStorageAPI(kvStore);

/**
Expand Down Expand Up @@ -939,6 +946,7 @@ export default function makeKernelKeeper(kvStore, streamStore, kernelSlog) {
incStat,
decStat,
getCrankNumber,
snapStore,
);
ephemeral.vatKeepers.set(vatID, vk);
return vk;
Expand Down
58 changes: 57 additions & 1 deletion packages/SwingSet/src/kernel/state/vatKeeper.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export function initializeVatState(kvStore, streamStore, vatID) {
/**
* Produce a vat keeper for a vat.
*
* @param {*} kvStore The keyValue store in which the persistent state will be kept
* @param {KVStorePlus} kvStore The keyValue store in which the persistent state will be kept
* @param {StreamStore} streamStore Accompanying stream store, for the transcripts
* @param {*} kernelSlog
* @param {string} vatID The vat ID string of the vat in question
Expand All @@ -60,6 +60,7 @@ export function initializeVatState(kvStore, streamStore, vatID) {
* @param {*} incStat
* @param {*} decStat
* @param {*} getCrankNumber
* @param { SnapStore= } snapStore
* returns an object to hold and access the kernel's state for the given vat
*/
export function makeVatKeeper(
Expand All @@ -79,6 +80,7 @@ export function makeVatKeeper(
incStat,
decStat,
getCrankNumber,
snapStore = undefined,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

re snapstore vs snapStore, I think this instance is an argument to use the camel-case snapStore throughout

) {
insistVatID(vatID);
const transcriptStream = `transcript-${vatID}`;
Expand Down Expand Up @@ -417,6 +419,57 @@ export function makeVatKeeper(
kvStore.set(`${vatID}.t.endPosition`, `${JSON.stringify(newPos)}`);
}

/** @returns { StreamPosition } */
function getTranscriptEndPosition() {
return JSON.parse(
kvStore.get(`${vatID}.t.endPosition`) ||
assert.fail('missing endPosition'),
);
}

/**
* @returns {{ snapshotID: string, startPos: StreamPosition } | undefined}
*/
function getLastSnapshot() {
const notation = kvStore.get(`${vatID}.lastSnapshot`);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please update the big pseudo-schema comment in kernelKeeper.js with something like v$NN.lastSnapshot = JSON({ snapshotID, startPos })

if (!notation) {
return undefined;
}
const { snapshotID, startPos } = JSON.parse(notation);
assert.typeof(snapshotID, 'string');
assert(startPos);
return { snapshotID, startPos };
}

function transcriptSnapshotStats() {
const totalEntries = getTranscriptEndPosition().itemCount;
const lastSnapshot = getLastSnapshot();
const snapshottedEntries = lastSnapshot
? lastSnapshot.startPos.itemCount
: 0;
return { totalEntries, snapshottedEntries };
}

/**
* Store a snapshot, if given a snapStore.
*
* @param { VatManager } manager
* @returns { Promise<boolean> }
*/
async function saveSnapshot(manager) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This feels awkward, passing a VatManager into the storage layer. I'm inclined to keep VatKeeper only about recording the actions that someone else has taken, rather than taking those actions itself.

What about the alternative, where the Vat Warehouse holds the snapStore, and does something like:

function saveSnapshot() {
  if (!snapStore) {
    return;
  }
  const snapshotID = await manager.makeSnapshot(snapStore).
  vatKeeper.saveSnapshot(snapshotID);
}

and vatKeeper.saveSnapshot(snapshotID) takes responsibility for looking up the current transcript end position and updating the .lastSnapshot key? That would also make vatKeeper.saveSnapshot synchronous again, which is a lot more in keeping with the rest of the vatKeeper API.

if (!snapStore || !manager.makeSnapshot) {
return false;
}

const snapshotID = await manager.makeSnapshot(snapStore);
const endPosition = getTranscriptEndPosition();
kvStore.set(
`${vatID}.lastSnapshot`,
JSON.stringify({ snapshotID, startPos: endPosition }),
);
return true;
}

function vatStats() {
function getCount(key, first) {
const id = Nat(BigInt(kvStore.get(key)));
Expand Down Expand Up @@ -477,8 +530,11 @@ export function makeVatKeeper(
deleteCListEntry,
deleteCListEntriesForKernelSlots,
getTranscript,
transcriptSnapshotStats,
addToTranscript,
vatStats,
dumpState,
saveSnapshot,
getLastSnapshot,
});
}
27 changes: 22 additions & 5 deletions packages/SwingSet/src/kernel/vatManager/manager-helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@ import { makeTranscriptManager } from './transcript.js';

/**
*
* @typedef { { getManager: (shutdown: () => Promise<void>) => VatManager,
* @typedef { { getManager: (shutdown: () => Promise<void>,
* makeSnapshot?: (ss: SnapStore) => Promise<string>) => VatManager,
* syscallFromWorker: (vso: VatSyscallObject) => VatSyscallResult,
* setDeliverToWorker: (dtw: unknown) => void,
* } } ManagerKit
Expand Down Expand Up @@ -178,12 +179,18 @@ function makeManagerKit(
kernelSlog.write({ type: 'finish-replay-delivery', vatID, deliveryNum });
}

async function replayTranscript() {
/**
* @param {StreamPosition | undefined} startPos
* @returns { Promise<number?> } number of deliveries, or null if !useTranscript
*/
async function replayTranscript(startPos) {
// console.log('replay from', { vatID, startPos });

if (transcriptManager) {
const total = vatKeeper.vatStats().transcriptCount;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe not for now, but we should clean up this transcriptCount stuff. We'll need the transcript position type to either provide a subtraction function, or de-opaque-ify the type (which is a lot more comfortable with the new SQLite transcript storage, where we don't need byte offsets in addition to item count). Then we'll want to extract two numbers: the starting position associated with our snapshot, and the finishing position. Then the commented-out log message, which previously would count from 0 to "finish", will now count from "start" to "finish", and it can give useful progress information. Eventually I want this log messsage to feed some sort of queryable "what's the status of the process" API, so a helper application (think /bin/ps or /usr/bin/top, but for vats) can report on the status of all vats, including the ones we're currently busy replaying.

kernelSlog.write({ type: 'start-replay', vatID, deliveries: total });
let deliveryNum = 0;
for (const t of vatKeeper.getTranscript()) {
for (const t of vatKeeper.getTranscript(startPos)) {
// if (deliveryNum % 100 === 0) {
// console.debug(`replay vatID:${vatID} deliveryNum:${deliveryNum} / ${total}`);
// }
Expand All @@ -194,7 +201,10 @@ function makeManagerKit(
}
transcriptManager.checkReplayError();
kernelSlog.write({ type: 'finish-replay', vatID });
return deliveryNum;
}

return null;
}

/**
Expand Down Expand Up @@ -235,10 +245,17 @@ function makeManagerKit(
/**
*
* @param { () => Promise<void>} shutdown
* @param { (ss: SnapStore) => Promise<string> } makeSnapshot
* @returns { VatManager }
*/
function getManager(shutdown) {
return harden({ replayTranscript, replayOneDelivery, deliver, shutdown });
function getManager(shutdown, makeSnapshot) {
return harden({
replayTranscript,
replayOneDelivery,
deliver,
shutdown,
makeSnapshot,
});
}

return harden({ getManager, syscallFromWorker, setDeliverToWorker });
Expand Down
Loading