Skip to content

Commit

Permalink
Merge pull request #8312 from Agoric/benchmarkerator
Browse files Browse the repository at this point in the history
Introducing The Benchmarkerator, a platform for standalone performance benchmarks
  • Loading branch information
mergify[bot] authored Sep 27, 2023
2 parents ef466d3 + 87dbbda commit 31ed402
Show file tree
Hide file tree
Showing 25 changed files with 1,398 additions and 741 deletions.
31 changes: 28 additions & 3 deletions packages/SwingSet/src/controller/controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import engineGC from '@agoric/internal/src/lib-nodejs/engine-gc.js';
import { startSubprocessWorker } from '@agoric/internal/src/lib-nodejs/spawnSubprocessWorker.js';
import { waitUntilQuiescent } from '@agoric/internal/src/lib-nodejs/waitUntilQuiescent.js';
import { makeGcAndFinalize } from '@agoric/internal/src/lib-nodejs/gc-and-finalize.js';
import { kslot } from '../lib/kmarshal.js';
import { kslot, krefOf } from '../lib/kmarshal.js';
import { insistStorageAPI } from '../lib/storageAPI.js';
import {
buildKernelBundle,
Expand Down Expand Up @@ -360,6 +360,7 @@ export async function makeSwingsetController(
kernelStorage.emitCrankHashes();
return result;
},

vatNameToID(vatName) {
return kernel.vatNameToID(vatName);
},
Expand All @@ -371,13 +372,15 @@ export async function makeSwingsetController(
* Queue a method call into the named vat
*
* @param {string} vatName
* @param {string} method
* @param {string|symbol} method
* @param {unknown[]} args
* @param {ResolutionPolicy} resultPolicy
*/
queueToVatRoot(vatName, method, args = [], resultPolicy = 'ignore') {
const vatID = kernel.vatNameToID(vatName);
assert.typeof(method, 'string');
if (typeof method !== 'symbol') {
assert.typeof(method, 'string');
}
const kref = kernel.getRootObject(vatID);
const kpid = kernel.queueToKref(kref, method, args, resultPolicy);
if (kpid) {
Expand All @@ -387,6 +390,28 @@ export async function makeSwingsetController(
return kpid;
},

/**
* Queue a method call to an object represented by a kmarshal token
*
* @param {any} target
* @param {string|symbol} method
* @param {unknown[]} args
* @param {ResolutionPolicy} resultPolicy
*/
queueToVatObject(target, method, args = [], resultPolicy = 'ignore') {
const targetKref = krefOf(target);
assert.typeof(targetKref, 'string');
if (typeof method !== 'symbol') {
assert.typeof(method, 'string');
}
const kpid = kernel.queueToKref(targetKref, method, args, resultPolicy);
if (kpid) {
kernel.kpRegisterInterest(kpid);
}
kernelStorage.emitCrankHashes();
return kpid;
},

upgradeStaticVat(vatName, shouldPauseFirst, bundleID, options = {}) {
const vatID = kernel.vatNameToID(vatName);
let pauseTarget = null;
Expand Down
2 changes: 1 addition & 1 deletion packages/SwingSet/src/kernel/kernelQueue.js
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ export function makeKernelQueueHandler(tools) {
* by some other vat. This requires a kref as a target.
*
* @param {string} kref Target of the message
* @param {string} method The method name
* @param {string|symbol} method The method name
* @param {any[]} args The arguments array
* @param {ResolutionPolicy} [policy] How the kernel should handle an eventual
* resolution or rejection of the message's result promise. Should be
Expand Down
80 changes: 49 additions & 31 deletions packages/SwingSet/src/lib/kmarshal.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Far, passStyleOf } from '@endo/far';
import { makeMarshal } from '@endo/marshal';
import { assert } from '@agoric/assert';
import { assert, Fail } from '@agoric/assert';

// Simple wrapper for serializing and unserializing marshalled values inside the
// kernel, where we don't actually want to use clists nor actually allocate real
Expand All @@ -9,10 +9,10 @@ import { assert } from '@agoric/assert';
// agnostic about the internal details of the serialization encoding.

/**
* @typedef {{getKref: () => string, iface: () => string}} KCap
* @typedef {{getKref: () => string, iface: () => string} | Promise} KCap
*/

const refMap = new WeakMap();
const { toStringTag } = Symbol;

/**
* @param {string} kref
Expand All @@ -32,21 +32,26 @@ export const kslot = (kref, iface = 'undefined') => {
kref.startsWith('lp') ||
kref.startsWith('rp')
) {
// TODO: temporary hack because smallcaps encodes promise references
// differently from remotable object references, and the current version of
// the smallcaps decoder annoyingly insists that if the encoded body string
// says a slot is a promise, then convertSlotToVal had better by damn return
// an actual Promise, even if, as in the intended use case here, we neither
// want nor need a promise, nor the overhead of a map to keep track of it
// with. This behavior is in service of defense against a hypothesized
// security issue whose exact nature has largely been forgotton in the
// months since it was first encountered. MarkM is currently researching
// what the problem was thought to have been, to see if it is real and to
// understand it if so. This will eventually result in either changes to
// the smallcaps encoding or to the marshal setup API to support the purely
// manipulative use case. In the meantime, this ugliness...
const p = new Promise(() => undefined);
refMap.set(p, kref);
// Bizarro World hack for attaching a string property to a Promise, courtesy
// of MarkM. Even though the @@toStringTag property nominally *is* a
// string, some unfortunate stuff in our hardened JS safety checks blows up
// if it actually is. Eventually that will be fixed and we'll be able to
// use the @@toStringTag property directly, but for now we need to use this
// weird construct employing a sneaky getter function. Note that this is
// only necessary in the first place because smallcaps encodes promise
// references differently from remotable object references, and the current
// version of the smallcaps decoder annoyingly insists that if the encoded
// body string says a slot is a promise, then convertSlotToVal had better by
// damn return an actual Promise, even if, as in the intended use case here,
// we neither want nor need a promise nor are capable of making any use of
// the fact that it is one.
const p = Promise.resolve(`${kref} stand in`);
// Bizarro World makes eslint hallucinate
// eslint-disable-next-line @typescript-eslint/no-floating-promises
Object.defineProperty(p, toStringTag, {
get: reveal => (reveal ? kref : NaN),
enumerable: false,
});
return harden(p);
} else {
const o = Far(iface, {
Expand All @@ -62,20 +67,33 @@ export const kslot = (kref, iface = 'undefined') => {
* @returns {string}
*/
export const krefOf = obj => {
const fromMap = refMap.get(obj);
if (fromMap) {
return fromMap;
switch (passStyleOf(obj)) {
case 'promise': {
// Other half of Bizarro World hack for handling promises. Note the
// peculiar way the @@toStringTag getter is used.
const desc = Object.getOwnPropertyDescriptor(obj, toStringTag);
assert(desc !== undefined, 'promise lacks toStringTag getter');
const getter = desc.get;
assert.typeof(getter, 'function', 'toStringTag getter is not a function');
// @ts-expect-error violates the norm that getters have zero parameters
const kref = getter(true);
assert.typeof(kref, 'string');
return kref;
}
case 'remotable': {
const getKref = obj.getKref;
assert.typeof(getKref, 'function', 'object lacks getKref function');
return getKref();
}
default:
// When krefOf() is called as part of kmarshal.serialize, marshal
// will only give it things that are 'remotable' (Promises and the
// Far objects created by kslot()). When krefOf() is called by
// kernel code (as part of extractSingleSlot() or the vat-comms
// equivalent), it ought to throw if 'obj' is not one of the
// objects created by our kslot().
return Fail`krefOf requires a promise or remotable`;
}
// When krefOf() is called as part of kmarshal.serialize, marshal
// will only give it things that are 'remotable' (Promises and the
// Far objects created by kslot()). When krefOf() is called by
// kernel code (as part of extractSingleSlot() or the vat-comms
// equivalent), it ought to throw if 'obj' is not one of the Far
// objects created by our kslot().
assert.equal(passStyleOf(obj), 'remotable', obj);
const getKref = obj.getKref;
assert.typeof(getKref, 'function', 'object lacking getKref function');
return getKref();
};

const kmarshal = makeMarshal(krefOf, kslot, {
Expand Down
38 changes: 23 additions & 15 deletions packages/SwingSet/test/test-promises.js
Original file line number Diff line number Diff line change
Expand Up @@ -238,9 +238,7 @@ test('local promises are rejected by vat upgrade', async t => {
c.pinVatRoot('bootstrap');
await c.run();

const run = async (method, args = []) => {
assert(Array.isArray(args));
const kpid = c.queueToVatRoot('bootstrap', method, args);
const awaitRun = async kpid => {
await c.run();
const status = c.kpStatus(kpid);
if (status === 'fulfilled') {
Expand All @@ -251,24 +249,34 @@ test('local promises are rejected by vat upgrade', async t => {
const err = c.kpResolution(kpid);
throw kunser(err);
};
const messageVat = (name, methodName, args) =>
run('messageVat', [{ name, methodName, args }]);
// eslint-disable-next-line no-unused-vars
const messageObject = (presence, methodName, args) =>
run('messageVatObject', [{ presence, methodName, args }]);

const messageToVat = async (vatName, method, ...args) => {
const kpid = c.queueToVatRoot(vatName, method, args);
return awaitRun(kpid);
};
const messageToObject = async (presence, method, ...args) => {
const kpid = c.queueToVatObject(presence, method, args);
return awaitRun(kpid);
};

const S = Symbol.for('passable');
await run('createVat', [{ name: 'watcher', bundleCapName: 'watcher' }]);
await messageVat('watcher', 'watchLocalPromise', ['orphaned']);
await messageVat('watcher', 'watchLocalPromise', ['fulfilled', S]);
await messageVat('watcher', 'watchLocalPromise', ['rejected', undefined, S]);
const v1Settlements = await messageVat('watcher', 'getSettlements');
const watcher = await messageToVat('bootstrap', 'createVat', {
name: 'watcher',
bundleCapName: 'watcher',
});
await messageToObject(watcher, 'watchLocalPromise', 'orphaned');
await messageToObject(watcher, 'watchLocalPromise', 'fulfilled', S);
await messageToObject(watcher, 'watchLocalPromise', 'rejected', undefined, S);
const v1Settlements = await messageToObject(watcher, 'getSettlements');
t.deepEqual(v1Settlements, {
fulfilled: { status: 'fulfilled', value: S },
rejected: { status: 'rejected', reason: S },
});
await run('upgradeVat', [{ name: 'watcher', bundleCapName: 'watcher' }]);
const v2Settlements = await messageVat('watcher', 'getSettlements');
await messageToVat('bootstrap', 'upgradeVat', {
name: 'watcher',
bundleCapName: 'watcher',
});
const v2Settlements = await messageToObject(watcher, 'getSettlements');
t.deepEqual(v2Settlements, {
fulfilled: { status: 'fulfilled', value: S },
rejected: { status: 'rejected', reason: S },
Expand Down
Loading

0 comments on commit 31ed402

Please sign in to comment.