From c0f7d65d963abbe3f35b45c75726e0eb20806089 Mon Sep 17 00:00:00 2001 From: Brian Warner Date: Fri, 25 Jun 2021 11:37:09 -0700 Subject: [PATCH] chore(swingset): add test of GC behavior in xs vat This checks that our normal async / `await E()` pattern doesn't cause the target or its arguments to be retained longer than expected. refs #3406 --- packages/SwingSet/test/gc/bootstrap.js | 21 ++++++ packages/SwingSet/test/gc/test-gc-vat.js | 85 ++++++++++++++++++++++++ packages/SwingSet/test/gc/vat-target.js | 11 +++ 3 files changed, 117 insertions(+) create mode 100644 packages/SwingSet/test/gc/bootstrap.js create mode 100644 packages/SwingSet/test/gc/test-gc-vat.js create mode 100644 packages/SwingSet/test/gc/vat-target.js diff --git a/packages/SwingSet/test/gc/bootstrap.js b/packages/SwingSet/test/gc/bootstrap.js new file mode 100644 index 00000000000..490aeaa7b64 --- /dev/null +++ b/packages/SwingSet/test/gc/bootstrap.js @@ -0,0 +1,21 @@ +import { Far } from '@agoric/marshal'; +import { E } from '@agoric/eventual-send'; + +export function buildRootObject() { + let A = Far('A', { hello() {} }); + let B = Far('B', { hello() {} }); + let target; + + return harden({ + async bootstrap(vats) { + target = vats.target; + }, + async one() { + await E(target).two(A, B); + }, + drop() { + A = null; + B = null; + }, + }); +} diff --git a/packages/SwingSet/test/gc/test-gc-vat.js b/packages/SwingSet/test/gc/test-gc-vat.js new file mode 100644 index 00000000000..abda8a91c5d --- /dev/null +++ b/packages/SwingSet/test/gc/test-gc-vat.js @@ -0,0 +1,85 @@ +/* global __dirname */ +import { test } from '../../tools/prepare-test-env-ava.js'; + +// eslint-disable-next-line import/order +import path from 'path'; +import { provideHostStorage } from '../../src/hostStorage.js'; +import { initializeSwingset, makeSwingsetController } from '../../src/index.js'; +import { capargs } from '../util.js'; + +function dumpObjects(c) { + const out = {}; + for (const row of c.dump().objects) { + const [koid, owner, reachable, recognizable] = row; + out[koid] = [owner, reachable, recognizable]; + } + return out; +} + +async function dropPresence(t, dropExport) { + const config = { + bootstrap: 'bootstrap', + vats: { + bootstrap: { + sourceSpec: path.join(__dirname, 'bootstrap.js'), + }, + target: { + sourceSpec: path.join(__dirname, 'vat-target.js'), + creationOptions: { managerType: 'xs-worker' }, + }, + }, + }; + const hostStorage = provideHostStorage(); + await initializeSwingset(config, [], hostStorage); + const c = await makeSwingsetController(hostStorage); + t.teardown(c.shutdown); + await c.run(); + + const bootstrapID = c.vatNameToID('bootstrap'); + c.queueToVatExport('bootstrap', 'o+0', 'one', capargs([])); + if (dropExport) { + c.queueToVatExport('bootstrap', 'o+0', 'drop', capargs([])); + await c.step(); + } + await c.step(); + + // examine the run-queue to learn the krefs for objects A and B + const rq = c.dump().runQueue; + t.is(rq[0].type, 'send'); + t.is(rq[0].msg.method, 'two'); + const [krefA, krefB] = rq[0].msg.args.slots; + t.is(krefA, 'ko26'); // arbitrary but this is what we currently expect + t.is(krefB, 'ko27'); // same + // both are exported by the bootstrap vat, and are reachable+recognizable + // by the run-queue message, so the refcounts should both be 1,1 + let objs = dumpObjects(c); + t.deepEqual(objs[krefA], [bootstrapID, 1, 1]); + t.deepEqual(objs[krefB], [bootstrapID, 1, 1]); + + // Now let everything complete, and those objects should be dropped by the + // importing vat, which means the exporting vat will be told they've been + // dropped too. The exporting vat still holds the Remotables strongly. + await c.run(); + objs = dumpObjects(c); + + if (dropExport) { + // the exporter wasn't holding a strong ref, so when the drop arrives, + // the exporter will retire, causing the importer to retire, causing the + // object to disappear entirely + t.is(objs[krefA], undefined); + t.is(objs[krefB], undefined); + } else { + // until #3161 is fixed, the importing vat hasn't yet told the kernel that + // the objects are unrecognizable, so the refcounts will be 0,1 + t.deepEqual(objs[krefA], [bootstrapID, 0, 1]); + t.deepEqual(objs[krefB], [bootstrapID, 0, 1]); + + // but when #3161 is fixed, the objects should be retired too, so the c-list + // mappings and valToSlot tables will be empty. + // t.is(objs[krefA], undefined); + // t.is(objs[krefB], undefined); + } +} + +test('drop presence (export retains)', t => dropPresence(t, false)); +test('drop presence (export drops)', t => dropPresence(t, true)); diff --git a/packages/SwingSet/test/gc/vat-target.js b/packages/SwingSet/test/gc/vat-target.js new file mode 100644 index 00000000000..02e76ab71b6 --- /dev/null +++ b/packages/SwingSet/test/gc/vat-target.js @@ -0,0 +1,11 @@ +import { Far } from '@agoric/marshal'; +import { E } from '@agoric/eventual-send'; + +export function buildRootObject() { + return Far('root', { + async two(A, B) { + // A=ko26 B=ko27 + await E(A).hello(B); + }, + }); +}