-
Notifications
You must be signed in to change notification settings - Fork 207
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(swingset): activate metering of dynamic vats
vatManager now does a check, just after the vat finishes with a crank, to see if a meter was active for that vat (`meterRecord`). If so, it checks the meter for exhaustion and uses `notifyTermination` to tell the admin facet about it. Otherwise it refills the meter. Vats get unlimited cranks, but each crank has a limited meter budget. The dynamic-vat creation code makes a new meterRecord, applies the metering-transform (and endowment to let the transformed code do `getMeter`), and prepares a `notifyTermination` callback which will queue a message to the vatAdminWrapper with the details. The vatAdminWrapper now manages an additional `done` promise for each dynamic vat it manages. When it gets the `notifyTermination` message, it resolves this promise. Whichever vat object holds the `adminNode` can retrieve this promise and get notified when the vat dies. The unit test exercises a vat overrunning the "allocate" meter (which is easy to trigger without a long slow loop), and makes sure the vat doesn't respond to future messages after it's been exhausted. The existing dynamic-vat test (vat-admin/test-innerVat.js) was updated to install global metering, because now all dynamic vats are metered, whether the test is exercising metering or not, and swingset emits a warning unless global metering is installed. We don't currently attempt to clean up the vat in any way. We rely upon the vat's one meter remaining exhausted and never being refilled. As long as that remains the case, all vat code will throw (the same exception) immediately upon any message delivery, so while the vat's liveslots code gets to run (and result promises are rejected appropriately), the vat code itself will never get control again. The `notifyTermination` callback will be fired each time, but it has an internal flag to ignore the later ones. We also don't yet have a way to preemptively terminate a vat. We could add this pretty easily by changing `doProcess` to check a new "null or Error" flag just before dispatching into the vat. One missing feature is that any Promises the late vat was controlling will remain forever unresolved. Ideally all those promises should be rejected as soon as the vat is known to be terminated. This is the highest-priority followup task. A further-out task is to delete the vat from memory, decref its c-list entries, and somehow propagate disconnect messages back to holders of now-dangling object references. Another remaining task is to handle state cleanup and "rewind the transaction" better. In the present code, when a vat dies mid-way through a crank, any syscalls it made before the meter exhausted will still get through. External observers will see a prefix of the messages the vat would have sent if it hadn't run out of metering budget. Ideally the entire crank would atomically happen or not happen, which will require us to buffer those syscalls until the crank finishes with time still on the clock. This is visible now, but isn't too serious yet. It will become more important when we consider allowing vats to re-start the message that killed them (using a bigger meter).
- Loading branch information
Showing
7 changed files
with
226 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
import harden from '@agoric/harden'; | ||
import meterMe from './metered-code'; | ||
|
||
export default function buildRoot(_dynamicVatPowers) { | ||
return harden({ | ||
async run() { | ||
meterMe([], 'no'); | ||
return 42; | ||
}, | ||
|
||
async explode(how) { | ||
meterMe([], how); | ||
return -1; | ||
}, | ||
}); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
/* global harden */ | ||
import '@agoric/install-metering-and-ses'; | ||
import bundleSource from '@agoric/bundle-source'; | ||
import tap from 'tap'; | ||
import { buildVatController } from '../../src/index'; | ||
import makeNextLog from '../make-nextlog'; | ||
|
||
function capdata(body, slots = []) { | ||
return harden({ body, slots }); | ||
} | ||
|
||
function capargs(args, slots = []) { | ||
return capdata(JSON.stringify(args), slots); | ||
} | ||
|
||
tap.test('metering dynamic vats', async t => { | ||
// we'll give this bundle to the loader vat, which will use it to create a | ||
// new (metered) dynamic vat | ||
const dynamicVatBundle = await bundleSource( | ||
require.resolve('./metered-dynamic-vat.js'), | ||
); | ||
const config = { | ||
vats: new Map(), | ||
bootstrapIndexJS: require.resolve('./vat-load-dynamic.js'), | ||
}; | ||
const c = await buildVatController(config, true, []); | ||
const nextLog = makeNextLog(c); | ||
|
||
// let the vatAdminService get wired up before we create any new vats | ||
await c.run(); | ||
|
||
// 'createVat' will import the bundle | ||
c.queueToVatExport( | ||
'_bootstrap', | ||
'o+0', | ||
'createVat', | ||
capargs([dynamicVatBundle]), | ||
); | ||
await c.run(); | ||
t.deepEqual(nextLog(), ['created'], 'first create'); | ||
|
||
// First, send a message to the dynamic vat that runs normally | ||
c.queueToVatExport('_bootstrap', 'o+0', 'run', capargs([])); | ||
await c.run(); | ||
|
||
t.deepEqual(nextLog(), ['did run'], 'first run ok'); | ||
|
||
// Now send a message that makes the dynamic vat exhaust its meter. The | ||
// message result promise should be rejected, and the control facet should | ||
// report the vat's demise | ||
c.queueToVatExport('_bootstrap', 'o+0', 'explode', capargs(['allocate'])); | ||
await c.run(); | ||
|
||
t.deepEqual( | ||
nextLog(), | ||
[ | ||
'did explode: RangeError: Allocate meter exceeded', | ||
'terminated: RangeError: Allocate meter exceeded', | ||
], | ||
'first boom', | ||
); | ||
|
||
// the dead vat should stay dead | ||
c.queueToVatExport('_bootstrap', 'o+0', 'run', capargs([])); | ||
await c.run(); | ||
t.deepEqual( | ||
nextLog(), | ||
['run exploded: RangeError: Allocate meter exceeded'], | ||
'stay dead', | ||
); | ||
|
||
t.end(); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
import harden from '@agoric/harden'; | ||
import { E } from '@agoric/eventual-send'; | ||
|
||
function build(buildStuff) { | ||
const { log } = buildStuff; | ||
let service; | ||
let control; | ||
|
||
return harden({ | ||
async bootstrap(argv, vats, devices) { | ||
service = await E(vats.vatAdmin).createVatAdminService(devices.vatAdmin); | ||
}, | ||
|
||
async createVat(bundle) { | ||
control = await E(service).createVat(bundle); | ||
E(control.adminNode) | ||
.done() | ||
.then( | ||
() => log('finished'), | ||
err => log(`terminated: ${err}`), | ||
); | ||
log(`created`); | ||
}, | ||
|
||
async run() { | ||
try { | ||
await E(control.root).run(); | ||
log('did run'); | ||
} catch (err) { | ||
log(`run exploded: ${err}`); | ||
} | ||
}, | ||
|
||
async explode(how) { | ||
try { | ||
await E(control.root).explode(how); | ||
log('failed to explode'); | ||
} catch (err) { | ||
log(`did explode: ${err}`); | ||
} | ||
}, | ||
}); | ||
} | ||
|
||
export default function setup(syscall, state, helpers, vatPowers0) { | ||
const { log, makeLiveSlots } = helpers; | ||
return makeLiveSlots( | ||
syscall, | ||
state, | ||
(_E, _D, _vatPowers) => build({ log }), | ||
helpers.vatID, | ||
vatPowers0, | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters