Skip to content

Commit 4b843a8

Browse files
committed
feat(SwingSet): add "reachable" flag to clist entries
The kernel-to-vat clist entry for `kref` is stored in the kernelDB under a key of `${vatID}.c.${kref}` . This changes the value at that key from `${vref}` to `${flag} ${vref}`, where `flag` is either `R` (for "reachable and recognizable") or `_` (for "recognizable but not reachable). The "neither reachable nor recognizable" state simply deletes the clist entry altogether. This adds vatKeeper methods to set and clear the flag on a given kref. These will be used by the GC syscall handlers for dropImport and friends. closes #3108
1 parent ff6af69 commit 4b843a8

File tree

3 files changed

+173
-17
lines changed

3 files changed

+173
-17
lines changed

packages/SwingSet/src/kernel/state/kernelKeeper.js

+3-1
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,9 @@ const enableKernelPromiseGC = true;
4242
// v$NN.o.nextID = $NN
4343
// v$NN.p.nextID = $NN
4444
// v$NN.d.nextID = $NN
45-
// v$NN.c.$kernelSlot = $vatSlot = o+$NN/o-$NN/p+$NN/p-$NN/d+$NN/d-$NN
45+
// v$NN.c.$kernelSlot = '$R $vatSlot'
46+
// $R is 'R' when reachable, '_' when merely recognizable
47+
// $vatSlot is one of: o+$NN/o-$NN/p+$NN/p-$NN/d+$NN/d-$NN
4648
// v$NN.c.$vatSlot = $kernelSlot = ko$NN/kp$NN/kd$NN
4749
// v$NN.t.$NN = JSON(transcript entry)
4850
// v$NN.t.nextID = $NN

packages/SwingSet/src/kernel/state/vatKeeper.js

+81-16
Original file line numberDiff line numberDiff line change
@@ -78,23 +78,61 @@ export function makeVatKeeper(
7878
return harden({ source, options });
7979
}
8080

81+
function parseReachableAndVatSlot(value) {
82+
assert.typeof(value, 'string', X`non-string value: ${value}`);
83+
const flag = value.slice(0, 1);
84+
assert.equal(value.slice(1, 2), ' ');
85+
const vatSlot = value.slice(2);
86+
let reachable;
87+
if (flag === 'R') {
88+
reachable = true;
89+
} else if (flag === '_') {
90+
reachable = false;
91+
} else {
92+
assert(`flag (${flag}) must be 'R' or '_'`);
93+
}
94+
return { reachable, vatSlot };
95+
}
96+
97+
function buildReachableAndVatSlot(reachable, vatSlot) {
98+
return `${reachable ? 'R' : '_'} ${vatSlot}`;
99+
}
100+
101+
function getReachableAndVatSlot(kernelSlot) {
102+
const kernelKey = `${vatID}.c.${kernelSlot}`;
103+
return parseReachableAndVatSlot(kvStore.get(kernelKey));
104+
}
105+
106+
function setReachableFlag(kernelSlot) {
107+
const kernelKey = `${vatID}.c.${kernelSlot}`;
108+
const { vatSlot } = parseReachableAndVatSlot(kvStore.get(kernelKey));
109+
kvStore.set(kernelKey, buildReachableAndVatSlot(true, vatSlot));
110+
}
111+
112+
function clearReachableFlag(kernelSlot) {
113+
const kernelKey = `${vatID}.c.${kernelSlot}`;
114+
const { vatSlot } = parseReachableAndVatSlot(kvStore.get(kernelKey));
115+
kvStore.set(kernelKey, buildReachableAndVatSlot(false, vatSlot));
116+
}
117+
81118
/**
82-
* Provide the kernel slot corresponding to a given vat slot, creating the
83-
* kernel slot if it doesn't already exist.
119+
* Provide the kernel slot corresponding to a given vat slot, allocating a
120+
* new one (for exports only) if it doesn't already exist. If we're allowed
121+
* to allocate, we also ensure the 'reachable' flag is set on it (whether
122+
* we allocated a new one or used an existing one). If we're not allowed to
123+
* allocate, we insist that the reachable flag was already set.
84124
*
85125
* @param {string} vatSlot The vat slot of interest
86-
*
126+
* @param {bool} setReachable Set the 'reachable' flag on vat exports.
87127
* @returns {string} the kernel slot that vatSlot maps to
88-
*
89128
* @throws {Error} if vatSlot is not a kind of thing that can be exported by vats
90-
* or is otherwise invalid.
129+
* or is otherwise invalid.
91130
*/
92-
function mapVatSlotToKernelSlot(vatSlot) {
131+
function mapVatSlotToKernelSlot(vatSlot, setReachable = true) {
93132
assert.typeof(vatSlot, 'string', X`non-string vatSlot: ${vatSlot}`);
133+
const { type, allocatedByVat } = parseVatSlot(vatSlot);
94134
const vatKey = `${vatID}.c.${vatSlot}`;
95135
if (!kvStore.has(vatKey)) {
96-
const { type, allocatedByVat } = parseVatSlot(vatSlot);
97-
98136
if (allocatedByVat) {
99137
let kernelSlot;
100138
if (type === 'object') {
@@ -109,7 +147,10 @@ export function makeVatKeeper(
109147
incrementRefCount(kernelSlot, `${vatID}|vk|clist`);
110148
const kernelKey = `${vatID}.c.${kernelSlot}`;
111149
incStat('clistEntries');
112-
kvStore.set(kernelKey, vatSlot);
150+
// we add the key as "unreachable", and then rely on
151+
// setReachableFlag() at the end to both mark it reachable and to
152+
// update any necessary refcounts consistently
153+
kvStore.set(kernelKey, buildReachableAndVatSlot(false, vatSlot));
113154
kvStore.set(vatKey, kernelSlot);
114155
kernelSlog &&
115156
kernelSlog.changeCList(
@@ -126,22 +167,32 @@ export function makeVatKeeper(
126167
assert.fail(X`unknown vatSlot ${q(vatSlot)}`);
127168
}
128169
}
170+
const kernelSlot = kvStore.get(vatKey);
129171

130-
return kvStore.get(vatKey);
172+
if (setReachable) {
173+
if (allocatedByVat) {
174+
// exports are marked as reachable, if they weren't already
175+
setReachableFlag(kernelSlot);
176+
} else {
177+
// imports must be reachable
178+
const { reachable } = getReachableAndVatSlot(kernelSlot);
179+
assert(reachable, X`vat tried to access unreachable import`);
180+
}
181+
}
182+
return kernelSlot;
131183
}
132184

133185
/**
134186
* Provide the vat slot corresponding to a given kernel slot, including
135187
* creating the vat slot if it doesn't already exist.
136188
*
137189
* @param {string} kernelSlot The kernel slot of interest
138-
*
190+
* @param {bool} setReachable Set the 'reachable' flag on vat imports.
139191
* @returns {string} the vat slot kernelSlot maps to
140-
*
141192
* @throws {Error} if kernelSlot is not a kind of thing that can be imported by vats
142-
* or is otherwise invalid.
193+
* or is otherwise invalid.
143194
*/
144-
function mapKernelSlotToVatSlot(kernelSlot) {
195+
function mapKernelSlotToVatSlot(kernelSlot, setReachable = true) {
145196
assert.typeof(kernelSlot, 'string', 'non-string kernelSlot');
146197
const kernelKey = `${vatID}.c.${kernelSlot}`;
147198
if (!kvStore.has(kernelKey)) {
@@ -166,7 +217,7 @@ export function makeVatKeeper(
166217
const vatKey = `${vatID}.c.${vatSlot}`;
167218
incStat('clistEntries');
168219
kvStore.set(vatKey, kernelSlot);
169-
kvStore.set(kernelKey, vatSlot);
220+
kvStore.set(kernelKey, buildReachableAndVatSlot(false, vatSlot));
170221
kernelSlog &&
171222
kernelSlog.changeCList(
172223
vatID,
@@ -178,7 +229,19 @@ export function makeVatKeeper(
178229
kdebug(`Add mapping k->v ${kernelKey}<=>${vatKey}`);
179230
}
180231

181-
return kvStore.get(kernelKey);
232+
const { reachable, vatSlot } = getReachableAndVatSlot(kernelSlot);
233+
const { allocatedByVat } = parseVatSlot(vatSlot);
234+
if (setReachable) {
235+
if (!allocatedByVat) {
236+
// imports are marked as reachable, if they weren't already
237+
setReachableFlag(kernelSlot);
238+
} else {
239+
// if the kernel is sending non-reachable exports back into
240+
// exporting vat, that's a kernel bug
241+
assert(reachable, X`kernel sent unreachable export`);
242+
}
243+
}
244+
return vatSlot;
182245
}
183246

184247
/**
@@ -293,6 +356,8 @@ export function makeVatKeeper(
293356
getSourceAndOptions,
294357
mapVatSlotToKernelSlot,
295358
mapKernelSlotToVatSlot,
359+
setReachableFlag,
360+
clearReachableFlag,
296361
hasCListEntry,
297362
deleteCListEntry,
298363
deleteCListEntriesForKernelSlots,

packages/SwingSet/test/test-clist.js

+89
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import { test } from '../tools/prepare-test-env-ava';
2+
3+
// eslint-disable-next-line import/order
4+
import { initSwingStore } from '@agoric/swing-store-simple';
5+
import { makeDummySlogger } from '../src/kernel/slogger';
6+
import makeKernelKeeper from '../src/kernel/state/kernelKeeper';
7+
import { wrapStorage } from '../src/kernel/state/storageWrapper';
8+
9+
test(`clist reachability`, async t => {
10+
const slog = makeDummySlogger({});
11+
const { enhancedCrankBuffer: s } = wrapStorage(initSwingStore().kvStore);
12+
13+
const kk = makeKernelKeeper(s, slog);
14+
kk.createStartingKernelState('local');
15+
const vatID = kk.allocateUnusedVatID();
16+
const vk = kk.allocateVatKeeper(vatID);
17+
18+
t.is(vk.mapKernelSlotToVatSlot('ko1'), 'o-50');
19+
t.is(vk.mapKernelSlotToVatSlot('ko2'), 'o-51');
20+
t.is(vk.mapKernelSlotToVatSlot('ko1'), 'o-50');
21+
22+
t.is(vk.mapVatSlotToKernelSlot('o+1'), 'ko20');
23+
t.is(vk.mapVatSlotToKernelSlot('o+2'), 'ko21');
24+
t.is(vk.mapVatSlotToKernelSlot('o+1'), 'ko20');
25+
26+
t.is(s.get(`${vatID}.c.o+1`), `ko20`);
27+
t.is(s.get(`${vatID}.c.ko20`), 'R o+1');
28+
29+
// newly allocated imports are marked as reachable
30+
t.is(s.get(`${vatID}.c.ko1`), 'R o-50');
31+
// now pretend that the vat drops its o-50 import: the syscall.dropImport
32+
// will cause the kernel to clear the reachability flag
33+
vk.clearReachableFlag('ko1');
34+
t.is(s.get(`${vatID}.c.ko1`), '_ o-50');
35+
// while dropped, the vat may not access the import
36+
t.throws(() => vk.mapVatSlotToKernelSlot('o-50'), {
37+
message: /vat tried to access unreachable import/,
38+
});
39+
40+
// now the kernel sends a new message that references ko1, causing a
41+
// re-import
42+
vk.setReachableFlag('ko1');
43+
t.is(s.get(`${vatID}.c.ko1`), 'R o-50');
44+
// re-import without intervening dropImport is idempotent
45+
vk.setReachableFlag('ko1');
46+
t.is(s.get(`${vatID}.c.ko1`), 'R o-50');
47+
48+
// test the same thing for exports
49+
t.is(s.get(`${vatID}.c.ko20`), 'R o+1');
50+
// kernel tells vat that kernel is dropping the export, clearing the
51+
// reachability flag
52+
vk.clearReachableFlag('ko20');
53+
t.is(s.get(`${vatID}.c.ko20`), '_ o+1');
54+
// while dropped, access by the kernel indicates a bug
55+
t.throws(() => vk.mapKernelSlotToVatSlot('ko20'), {
56+
message: /kernel sent unreachable export/,
57+
});
58+
59+
// vat re-exports o+1
60+
vk.setReachableFlag('ko20');
61+
t.is(s.get(`${vatID}.c.ko20`), 'R o+1');
62+
// re-export without intervening dropExport is idempotent
63+
vk.setReachableFlag('ko20');
64+
t.is(s.get(`${vatID}.c.ko20`), 'R o+1');
65+
66+
// test setReachable=false, used by handlers for GC operations like
67+
// syscall.dropImport to talk about an import without claiming it's
68+
// reachable or causing it to become reachable
69+
70+
t.is(vk.mapKernelSlotToVatSlot('ko3'), 'o-52');
71+
vk.clearReachableFlag('ko3');
72+
t.is(s.get(`${vatID}.c.ko3`), '_ o-52');
73+
t.throws(() => vk.mapVatSlotToKernelSlot('o-52'), {
74+
message: /vat tried to access unreachable import/,
75+
});
76+
t.is(vk.mapVatSlotToKernelSlot('o-52', false), 'ko3');
77+
t.is(vk.mapKernelSlotToVatSlot('ko3', false), 'o-52');
78+
t.is(s.get(`${vatID}.c.ko3`), '_ o-52');
79+
80+
t.is(vk.mapVatSlotToKernelSlot('o+3'), 'ko22');
81+
vk.clearReachableFlag('ko22');
82+
t.is(s.get(`${vatID}.c.ko22`), '_ o+3');
83+
t.throws(() => vk.mapKernelSlotToVatSlot('ko22'), {
84+
message: /kernel sent unreachable export/,
85+
});
86+
t.is(vk.mapKernelSlotToVatSlot('ko22', false), 'o+3');
87+
t.is(vk.mapVatSlotToKernelSlot('o+3', false), 'ko22');
88+
t.is(s.get(`${vatID}.c.ko22`), '_ o+3');
89+
});

0 commit comments

Comments
 (0)