Skip to content

Commit 2490de5

Browse files
committed
fix(swingset): partially implement syscall.dropImports and disavow
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
1 parent 4f43a5c commit 2490de5

12 files changed

+143
-6
lines changed

packages/SwingSet/docs/vat-worker.md

+1
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ The VatManager is given access to a `VatSyscallHandler` function. This takes a `
5656
* `['vatstoreGet', key]`
5757
* `['vatstoreSet', key, data]`
5858
* `['vatstoreDelete', key]`
59+
* `['dropImports', slots]`
5960

6061
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:
6162

packages/SwingSet/src/kernel/kernelSyscall.js

+8
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,12 @@ export function makeKernelSyscallHandler(tools) {
105105
return OKNULL;
106106
}
107107

108+
function dropImports(koids) {
109+
assert(Array.isArray(koids), X`dropImports given non-Array ${koids}`);
110+
console.log(`-- kernel ignoring dropImports ${koids.join(',')}`);
111+
return OKNULL;
112+
}
113+
108114
function doKernelSyscall(ksc) {
109115
const [type, ...args] = ksc;
110116
switch (type) {
@@ -124,6 +130,8 @@ export function makeKernelSyscallHandler(tools) {
124130
return vatstoreSet(...args);
125131
case 'vatstoreDelete':
126132
return vatstoreDelete(...args);
133+
case 'dropImports':
134+
return dropImports(...args);
127135
default:
128136
assert.fail(X`unknown vatSyscall type ${type}`);
129137
}

packages/SwingSet/src/kernel/liveSlots.js

+34-2
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,10 @@ function build(
7070
/** Map vat slot strings -> in-vat object references. */
7171
const slotToVal = new Map();
7272

73+
/** Map disavowed Presences to the Error which kills the vat if you try to
74+
* talk to them */
75+
const disavowedPresences = new WeakMap();
76+
7377
const importedPromisesByPromiseID = new Map();
7478
let nextExportID = 1;
7579
let nextPromiseID = 5;
@@ -82,9 +86,15 @@ function build(
8286

8387
lsdebug(`makeImportedPresence(${slot})`);
8488
const fulfilledHandler = {
85-
applyMethod(_o, prop, args, returnedP) {
89+
applyMethod(o, prop, args, returnedP) {
8690
// Support: o~.[prop](...args) remote method invocation
8791
lsdebug(`makeImportedPresence handler.applyMethod (${slot})`);
92+
const err = disavowedPresences.get(o);
93+
if (err) {
94+
// eslint-disable-next-line no-use-before-define
95+
exitVatWithFailure(err);
96+
throw err;
97+
}
8898
// eslint-disable-next-line no-use-before-define
8999
return queueMessage(slot, prop, args, returnedP);
90100
},
@@ -263,6 +273,12 @@ function build(
263273
if (isPromise(val)) {
264274
slot = exportPromise(val);
265275
} else {
276+
const err = disavowedPresences.get(val);
277+
if (err) {
278+
// eslint-disable-next-line no-use-before-define
279+
exitVatWithFailure(err);
280+
throw err; // cannot reference a disavowed object
281+
}
266282
assert.equal(passStyleOf(val), REMOTE_STYLE);
267283
slot = exportPassByPresence();
268284
}
@@ -613,7 +629,23 @@ function build(
613629
syscall.exit(true, m.serialize(harden(reason)));
614630
}
615631

616-
function disavow(_presence) {}
632+
function disavow(presence) {
633+
if (!valToSlot.has(presence)) {
634+
assert.fail(X`attempt to disavow unknown ${presence}`);
635+
}
636+
const slot = valToSlot.get(presence);
637+
const { type, allocatedByVat } = parseVatSlot(slot);
638+
assert.equal(type, 'object', X`attempt to disavow non-object ${presence}`);
639+
// disavow() is only for imports: we'll use a different API to revoke
640+
// exports, one which accepts an Error object
641+
assert.equal(allocatedByVat, false, X`attempt to disavow an export`);
642+
valToSlot.delete(presence);
643+
slotToVal.delete(slot);
644+
const err = harden(Error(`this Presence has been disavowed`));
645+
disavowedPresences.set(presence, err);
646+
647+
syscall.dropImports([slot]);
648+
}
617649

618650
// vats which use D are in: acorn-eventual-send, cosmic-swingset
619651
// (bootstrap, bridge, vat-http), swingset

packages/SwingSet/src/kernel/vatManager/supervisor-nodeworker.js

+1
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ parentPort.on('message', ([type, ...margs]) => {
9696
vatstoreGet: (...args) => doSyscall(['vatstoreGet', ...args]),
9797
vatstoreSet: (...args) => doSyscall(['vatstoreSet', ...args]),
9898
vatstoreDelete: (...args) => doSyscall(['vatstoreDelete', ...args]),
99+
dropImports: (...args) => doSyscall(['dropImports', ...args]),
99100
});
100101

101102
const vatID = 'demo-vatID';

packages/SwingSet/src/kernel/vatManager/supervisor-subprocess-node.js

+1
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ fromParent.on('data', ([type, ...margs]) => {
116116
vatstoreGet: (...args) => doSyscall(['vatstoreGet', ...args]),
117117
vatstoreSet: (...args) => doSyscall(['vatstoreSet', ...args]),
118118
vatstoreDelete: (...args) => doSyscall(['vatstoreDelete', ...args]),
119+
dropImports: (...args) => doSyscall(['dropImports', ...args]),
119120
});
120121

121122
const vatID = 'demo-vatID';

packages/SwingSet/src/kernel/vatManager/supervisor-subprocess-xsnap.js

+1
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,7 @@ function makeWorker(port) {
210210
vatstoreGet: (...args) => doSyscall(['vatstoreGet', ...args]),
211211
vatstoreSet: (...args) => doSyscall(['vatstoreSet', ...args]),
212212
vatstoreDelete: (...args) => doSyscall(['vatstoreDelete', ...args]),
213+
dropImports: (...args) => doSyscall(['dropImports', ...args]),
213214
});
214215

215216
const vatPowers = {

packages/SwingSet/src/kernel/vatManager/syscall.js

+1
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ export function createSyscall(transcriptManager) {
103103
vatstoreGet: (...args) => doSyscall(['vatstoreGet', ...args]),
104104
vatstoreSet: (...args) => doSyscall(['vatstoreSet', ...args]),
105105
vatstoreDelete: (...args) => doSyscall(['vatstoreDelete', ...args]),
106+
dropImports: (...args) => doSyscall(['dropImports', ...args]),
106107
});
107108

108109
return harden({ syscall, doSyscall, setVatSyscallHandler });

packages/SwingSet/src/kernel/vatTranslator.js

+17
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,21 @@ function makeTranslateVatSyscallToKernelSyscall(vatID, kernelKeeper) {
182182
return harden(['vatstoreDelete', vatID, key]);
183183
}
184184

185+
function translateDropImports(vrefs) {
186+
assert(Array.isArray(vrefs), X`dropImport() given non-Array ${vrefs}`);
187+
// We delete clist entries as we translate, which will decref the krefs.
188+
// When we're done with that loop, we hand the set of krefs to
189+
// kernelSyscall so it can check newly-decremented refcounts against zero,
190+
// and maybe delete even more.
191+
const krefs = vrefs.map(vref => {
192+
insistVatType('object', vref);
193+
const kref = mapVatSlotToKernelSlot(vref);
194+
vatKeeper.deleteCListEntry(kref, vref);
195+
return kref;
196+
});
197+
return harden(['dropImports', krefs]);
198+
}
199+
185200
function translateCallNow(target, method, args) {
186201
insistCapData(args);
187202
const dev = mapVatSlotToKernelSlot(target);
@@ -257,6 +272,8 @@ function makeTranslateVatSyscallToKernelSyscall(vatID, kernelKeeper) {
257272
return translateVatstoreSet(...args);
258273
case 'vatstoreDelete':
259274
return translateVatstoreDelete(...args);
275+
case 'dropImports':
276+
return translateDropImports(...args);
260277
default:
261278
assert.fail(X`unknown vatSyscall type ${type}`);
262279
}

packages/SwingSet/test/test-liveslots.js

+72-2
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,12 @@ function buildSyscall() {
3636
resolve(resolutions) {
3737
log.push({ type: 'resolve', resolutions });
3838
},
39+
dropImports(slots) {
40+
log.push({ type: 'dropImports', slots });
41+
},
42+
exit(isFailure, info) {
43+
log.push({ type: 'exit', isFailure, info });
44+
},
3945
};
4046

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

586592
function build(vatPowers) {
587-
return Far('root', {
588-
one(pres1) {
593+
const root = Far('root', {
594+
async one(pres1) {
589595
vatPowers.disavow(pres1);
590596
log.push('disavowed pres1');
597+
598+
try {
599+
vatPowers.disavow(pres1);
600+
log.push('oops duplicate disavow worked');
601+
} catch (err) {
602+
log.push(err); // forbidden to disavow twice
603+
}
604+
log.push('tried duplicate disavow');
605+
606+
try {
607+
const pr = Promise.resolve();
608+
vatPowers.disavow(pr);
609+
log.push('oops disavow Promise worked');
610+
} catch (err) {
611+
log.push(err); // forbidden to disavow promises
612+
}
613+
log.push('tried to disavow Promise');
614+
615+
try {
616+
vatPowers.disavow(root);
617+
log.push('oops disavow export worked');
618+
} catch (err) {
619+
log.push(err); // forbidden to disavow exports
620+
}
621+
log.push('tried to disavow export');
622+
623+
const p1 = E(pres1).foo();
624+
// this does a syscall.exit on a subsequent turn
625+
try {
626+
await p1;
627+
log.push('oops send to disavowed worked');
628+
} catch (err) {
629+
log.push(err); // fatal to send to disavowed
630+
}
631+
log.push('tried to send to disavowed');
591632
},
592633
});
634+
return root;
593635
}
594636
const dispatch = makeDispatch(syscall, build, true);
595637
t.deepEqual(log, []);
@@ -599,6 +641,34 @@ test('disavow', async t => {
599641
// root~.one(import1) // sendOnly
600642
dispatch.deliver(rootA, 'one', caponeslot(import1), undefined);
601643
await waitUntilQuiescent();
644+
t.deepEqual(log.shift(), { type: 'dropImports', slots: [import1] });
602645
t.deepEqual(log.shift(), 'disavowed pres1');
646+
647+
function loggedError(re) {
648+
const l = log.shift();
649+
t.truthy(l instanceof Error);
650+
t.truthy(re.test(l.message));
651+
}
652+
loggedError(/attempt to disavow unknown/);
653+
t.deepEqual(log.shift(), 'tried duplicate disavow');
654+
loggedError(/attempt to disavow unknown/);
655+
t.deepEqual(log.shift(), 'tried to disavow Promise');
656+
loggedError(/attempt to disavow an export/);
657+
t.deepEqual(log.shift(), 'tried to disavow export');
658+
t.deepEqual(log.shift(), {
659+
type: 'exit',
660+
isFailure: true,
661+
info: {
662+
body: JSON.stringify({
663+
'@qclass': 'error',
664+
errorId: 'error:liveSlots:vatA#1',
665+
message: 'this Presence has been disavowed',
666+
name: 'Error',
667+
}),
668+
slots: [],
669+
},
670+
});
671+
t.deepEqual(log.shift(), Error('this Presence has been disavowed'));
672+
t.deepEqual(log.shift(), 'tried to send to disavowed');
603673
t.deepEqual(log, []);
604674
});

packages/SwingSet/test/workers/bootstrap.js

+3
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ export function buildRootObject() {
1313
const precD = makePromiseKit();
1414
const precE = makePromiseKit();
1515

16+
const dropMe = Far('dropMe', {});
17+
1618
function checkResB(resB) {
1719
if (resB === callbackObj) {
1820
return 'B good';
@@ -61,6 +63,7 @@ export function buildRootObject() {
6163
precD.promise,
6264
precE.promise,
6365
devices.add,
66+
dropMe,
6467
);
6568
const rp3 = E(vats.target).one();
6669
precD.resolve(callbackObj); // two

packages/SwingSet/test/workers/test-worker.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ const expected = [['B good', 'C good', 'F good', 'three good'], 'rp3 good'];
77

88
async function makeController(managerType) {
99
const config = await loadBasedir(__dirname);
10-
config.vats.target.creationOptions = { managerType };
10+
config.vats.target.creationOptions = { managerType, enableDisavow: true };
1111
const canCallNow = ['local', 'xs-worker'].indexOf(managerType) !== -1;
1212
config.vats.target.parameters = { canCallNow };
1313
config.devices = {

packages/SwingSet/test/workers/vat-target.js

+3-1
Original file line numberDiff line numberDiff line change
@@ -28,15 +28,17 @@ export function buildRootObject(vatPowers, vatParameters) {
2828
// syscall.subscribe(pD)
2929
// syscall.subscribe(pE)
3030
// syscall.callNow(adder, args=[1, 2]) -> 3
31+
// syscall.dropImports([dropMe])
3132
// syscall.send(callbackObj, method="callback", result=rp2, args=[11, 12]);
3233
// syscall.subscribe(rp2)
3334
// syscall.fulfillToData(pA, [pB, pC, 3]);
34-
function zero(obj, pD, pE, adder) {
35+
function zero(obj, pD, pE, adder, dropMe) {
3536
callbackObj = obj;
3637
const pF = E(callbackObj).callback(11, 12); // syscall.send
3738
ignore(pD);
3839
ignore(pE);
3940
const three = canCallNow ? vatPowers.D(adder).add(1, 2) : 3;
41+
vatPowers.disavow(dropMe);
4042
return [precB.promise, precC.promise, pF, three]; // syscall.fulfillToData
4143
}
4244

0 commit comments

Comments
 (0)