Skip to content

Commit

Permalink
fix(swingset): partially implement syscall.dropImports and disavow
Browse files Browse the repository at this point in the history
Vat code can now use `vatPowers.disavow(presence)` (if enabled for that vat),
which will invoke `syscall.dropImports`. The kernel will delete the entry
from the vat's c-list, however no further reference-count management will
occur (that is scheduled for #2646).

This should be enough to allow work to proceed on liveslots (using WeakRef and
FinalizationRegistry) in parallel with kernel-side improvements.

Note that referencing a disavowed object is vat-fatal, either as the target
of a message, the argument of a message, or the resolution of a promise.

closes #2635
closes #2636
  • Loading branch information
warner committed Mar 16, 2021
1 parent 8c8ad74 commit c3e81e1
Show file tree
Hide file tree
Showing 12 changed files with 143 additions and 6 deletions.
1 change: 1 addition & 0 deletions packages/SwingSet/docs/vat-worker.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ The VatManager is given access to a `VatSyscallHandler` function. This takes a `
* `['vatstoreGet', key]`
* `['vatstoreSet', key, data]`
* `['vatstoreDelete', key]`
* `['dropImports', slots]`

As with deliveries (but in reverse), the translator converts this from vat-centric identifiers into kernel-centric ones, and emits a `KernelSyscall` object, with one of these forms:

Expand Down
8 changes: 8 additions & 0 deletions packages/SwingSet/src/kernel/kernelSyscall.js
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,12 @@ export function makeKernelSyscallHandler(tools) {
return OKNULL;
}

function dropImports(koids) {
assert(Array.isArray(koids), X`dropImports given non-Array ${koids}`);
console.log(`-- kernel ignoring dropImports ${koids.join(',')}`);
return OKNULL;
}

function doKernelSyscall(ksc) {
const [type, ...args] = ksc;
switch (type) {
Expand All @@ -124,6 +130,8 @@ export function makeKernelSyscallHandler(tools) {
return vatstoreSet(...args);
case 'vatstoreDelete':
return vatstoreDelete(...args);
case 'dropImports':
return dropImports(...args);
default:
assert.fail(X`unknown vatSyscall type ${type}`);
}
Expand Down
36 changes: 34 additions & 2 deletions packages/SwingSet/src/kernel/liveSlots.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,10 @@ function build(
/** Map vat slot strings -> in-vat object references. */
const slotToVal = new Map();

/** Map disavowed Presences to the Error which kills the vat if you try to
* talk to them */
const disavowedPresences = new WeakMap();

const importedPromisesByPromiseID = new Map();
let nextExportID = 1;
let nextPromiseID = 5;
Expand All @@ -80,9 +84,15 @@ function build(

lsdebug(`makeImportedPresence(${slot})`);
const fulfilledHandler = {
applyMethod(_o, prop, args, returnedP) {
applyMethod(o, prop, args, returnedP) {
// Support: o~.[prop](...args) remote method invocation
lsdebug(`makeImportedPresence handler.applyMethod (${slot})`);
const err = disavowedPresences.get(o);
if (err) {
// eslint-disable-next-line no-use-before-define
exitVatWithFailure(err);
throw err;
}
// eslint-disable-next-line no-use-before-define
return queueMessage(slot, prop, args, returnedP);
},
Expand Down Expand Up @@ -255,6 +265,12 @@ function build(
if (isPromise(val)) {
slot = exportPromise(val);
} else {
const err = disavowedPresences.get(val);
if (err) {
// eslint-disable-next-line no-use-before-define
exitVatWithFailure(err);
throw err; // cannot reference a disavowed object
}
assert.equal(passStyleOf(val), REMOTE_STYLE);
slot = exportPassByPresence();
}
Expand Down Expand Up @@ -605,7 +621,23 @@ function build(
syscall.exit(true, m.serialize(harden(reason)));
}

function disavow(_presence) {}
function disavow(presence) {
if (!valToSlot.has(presence)) {
assert.fail(X`attempt to disavow unknown ${presence}`);
}
const slot = valToSlot.get(presence);
const { type, allocatedByVat } = parseVatSlot(slot);
assert.equal(type, 'object', X`attempt to disavow non-object ${presence}`);
// disavow() is only for imports: we'll use a different API to revoke
// exports, one which accepts an Error object
assert.equal(allocatedByVat, false, X`attempt to disavow an export`);
valToSlot.delete(presence);
slotToVal.delete(slot);
const err = harden(Error(`this Presence has been disavowed`));
disavowedPresences.set(presence, err);

syscall.dropImports([slot]);
}

// vats which use D are in: acorn-eventual-send, cosmic-swingset
// (bootstrap, bridge, vat-http), swingset
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ parentPort.on('message', ([type, ...margs]) => {
vatstoreGet: (...args) => doSyscall(['vatstoreGet', ...args]),
vatstoreSet: (...args) => doSyscall(['vatstoreSet', ...args]),
vatstoreDelete: (...args) => doSyscall(['vatstoreDelete', ...args]),
dropImports: (...args) => doSyscall(['dropImports', ...args]),
});

const vatID = 'demo-vatID';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ fromParent.on('data', ([type, ...margs]) => {
vatstoreGet: (...args) => doSyscall(['vatstoreGet', ...args]),
vatstoreSet: (...args) => doSyscall(['vatstoreSet', ...args]),
vatstoreDelete: (...args) => doSyscall(['vatstoreDelete', ...args]),
dropImports: (...args) => doSyscall(['dropImports', ...args]),
});

const vatID = 'demo-vatID';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,7 @@ function makeWorker(port) {
vatstoreGet: (...args) => doSyscall(['vatstoreGet', ...args]),
vatstoreSet: (...args) => doSyscall(['vatstoreSet', ...args]),
vatstoreDelete: (...args) => doSyscall(['vatstoreDelete', ...args]),
dropImports: (...args) => doSyscall(['dropImports', ...args]),
});

const vatPowers = {
Expand Down
1 change: 1 addition & 0 deletions packages/SwingSet/src/kernel/vatManager/syscall.js
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ export function createSyscall(transcriptManager) {
vatstoreGet: (...args) => doSyscall(['vatstoreGet', ...args]),
vatstoreSet: (...args) => doSyscall(['vatstoreSet', ...args]),
vatstoreDelete: (...args) => doSyscall(['vatstoreDelete', ...args]),
dropImports: (...args) => doSyscall(['dropImports', ...args]),
});

return harden({ syscall, doSyscall, setVatSyscallHandler });
Expand Down
17 changes: 17 additions & 0 deletions packages/SwingSet/src/kernel/vatTranslator.js
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,21 @@ function makeTranslateVatSyscallToKernelSyscall(vatID, kernelKeeper) {
return harden(['vatstoreDelete', vatID, key]);
}

function translateDropImports(vrefs) {
assert(Array.isArray(vrefs), X`dropImport() given non-Array ${vrefs}`);
// We delete clist entries as we translate, which will decref the krefs.
// When we're done with that loop, we hand the set of krefs to
// kernelSyscall so it can check newly-decremented refcounts against zero,
// and maybe delete even more.
const krefs = vrefs.map(vref => {
insistVatType('object', vref);
const kref = mapVatSlotToKernelSlot(vref);
vatKeeper.deleteCListEntry(kref, vref);
return kref;
});
return harden(['dropImports', krefs]);
}

function translateCallNow(target, method, args) {
insistCapData(args);
const dev = mapVatSlotToKernelSlot(target);
Expand Down Expand Up @@ -257,6 +272,8 @@ function makeTranslateVatSyscallToKernelSyscall(vatID, kernelKeeper) {
return translateVatstoreSet(...args);
case 'vatstoreDelete':
return translateVatstoreDelete(...args);
case 'dropImports':
return translateDropImports(...args);
default:
assert.fail(X`unknown vatSyscall type ${type}`);
}
Expand Down
74 changes: 72 additions & 2 deletions packages/SwingSet/test/test-liveslots.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,12 @@ function buildSyscall() {
resolve(resolutions) {
log.push({ type: 'resolve', resolutions });
},
dropImports(slots) {
log.push({ type: 'dropImports', slots });
},
exit(isFailure, info) {
log.push({ type: 'exit', isFailure, info });
},
};

return { log, syscall };
Expand Down Expand Up @@ -584,12 +590,48 @@ test('disavow', async t => {
const { log, syscall } = buildSyscall();

function build(vatPowers) {
return Far('root', {
one(pres1) {
const root = Far('root', {
async one(pres1) {
vatPowers.disavow(pres1);
log.push('disavowed pres1');

try {
vatPowers.disavow(pres1);
log.push('oops duplicate disavow worked');
} catch (err) {
log.push(err); // forbidden to disavow twice
}
log.push('tried duplicate disavow');

try {
const pr = Promise.resolve();
vatPowers.disavow(pr);
log.push('oops disavow Promise worked');
} catch (err) {
log.push(err); // forbidden to disavow promises
}
log.push('tried to disavow Promise');

try {
vatPowers.disavow(root);
log.push('oops disavow export worked');
} catch (err) {
log.push(err); // forbidden to disavow exports
}
log.push('tried to disavow export');

const p1 = E(pres1).foo();
// this does a syscall.exit on a subsequent turn
try {
await p1;
log.push('oops send to disavowed worked');
} catch (err) {
log.push(err); // fatal to send to disavowed
}
log.push('tried to send to disavowed');
},
});
return root;
}
const dispatch = makeDispatch(syscall, build, true);
t.deepEqual(log, []);
Expand All @@ -599,6 +641,34 @@ test('disavow', async t => {
// root~.one(import1) // sendOnly
dispatch.deliver(rootA, 'one', caponeslot(import1), undefined);
await waitUntilQuiescent();
t.deepEqual(log.shift(), { type: 'dropImports', slots: [import1] });
t.deepEqual(log.shift(), 'disavowed pres1');

function loggedError(re) {
const l = log.shift();
t.truthy(l instanceof Error);
t.truthy(re.test(l.message));
}
loggedError(/attempt to disavow unknown/);
t.deepEqual(log.shift(), 'tried duplicate disavow');
loggedError(/attempt to disavow unknown/);
t.deepEqual(log.shift(), 'tried to disavow Promise');
loggedError(/attempt to disavow an export/);
t.deepEqual(log.shift(), 'tried to disavow export');
t.deepEqual(log.shift(), {
type: 'exit',
isFailure: true,
info: {
body: JSON.stringify({
'@qclass': 'error',
errorId: 'error:liveSlots:vatA#1',
message: 'this Presence has been disavowed',
name: 'Error',
}),
slots: [],
},
});
t.deepEqual(log.shift(), Error('this Presence has been disavowed'));
t.deepEqual(log.shift(), 'tried to send to disavowed');
t.deepEqual(log, []);
});
3 changes: 3 additions & 0 deletions packages/SwingSet/test/workers/bootstrap.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ export function buildRootObject() {
const precD = makePromiseKit();
const precE = makePromiseKit();

const dropMe = Far('dropMe', {});

function checkResB(resB) {
if (resB === callbackObj) {
return 'B good';
Expand Down Expand Up @@ -61,6 +63,7 @@ export function buildRootObject() {
precD.promise,
precE.promise,
devices.add,
dropMe,
);
const rp3 = E(vats.target).one();
precD.resolve(callbackObj); // two
Expand Down
2 changes: 1 addition & 1 deletion packages/SwingSet/test/workers/test-worker.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ const expected = [['B good', 'C good', 'F good', 'three good'], 'rp3 good'];

async function makeController(managerType) {
const config = await loadBasedir(__dirname);
config.vats.target.creationOptions = { managerType };
config.vats.target.creationOptions = { managerType, enableDisavow: true };
const canCallNow = ['local', 'xs-worker'].indexOf(managerType) !== -1;
config.vats.target.parameters = { canCallNow };
config.devices = {
Expand Down
4 changes: 3 additions & 1 deletion packages/SwingSet/test/workers/vat-target.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,17 @@ export function buildRootObject(vatPowers, vatParameters) {
// syscall.subscribe(pD)
// syscall.subscribe(pE)
// syscall.callNow(adder, args=[1, 2]) -> 3
// syscall.dropImports([dropMe])
// syscall.send(callbackObj, method="callback", result=rp2, args=[11, 12]);
// syscall.subscribe(rp2)
// syscall.fulfillToData(pA, [pB, pC, 3]);
function zero(obj, pD, pE, adder) {
function zero(obj, pD, pE, adder, dropMe) {
callbackObj = obj;
const pF = E(callbackObj).callback(11, 12); // syscall.send
ignore(pD);
ignore(pE);
const three = canCallNow ? vatPowers.D(adder).add(1, 2) : 3;
vatPowers.disavow(dropMe);
return [precB.promise, precC.promise, pF, three]; // syscall.fulfillToData
}

Expand Down

0 comments on commit c3e81e1

Please sign in to comment.