@agoric/swingset-vat 0.33.0-u20.0 → 0.33.0-u21.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +23 -20
- package/src/controller/controller.js +6 -2
- package/src/controller/upgradeSwingset.js +2 -4
- package/src/devices/bridge/device-bridge.js +2 -0
- package/src/devices/timer/device-timer.js +41 -22
- package/src/kernel/kernel.js +52 -3
- package/src/kernel/state/deviceKeeper.js +4 -3
- package/src/kernel/state/kernelKeeper.js +38 -30
- package/src/kernel/state/storageHelper.js +3 -2
- package/src/kernel/state/vatKeeper.js +5 -3
- package/src/kernel/vat-warehouse.js +17 -9
- package/src/lib/capdata.js +4 -17
- package/src/types-external.js +22 -6
- package/src/vats/plugin-manager.js +2 -7
- package/src/vats/timer/vat-timer.js +12 -5
- package/src/vats/vat-admin/vat-vat-admin.js +2 -2
- package/tools/manual-timer.js +3 -2
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agoric/swingset-vat",
|
|
3
|
-
"version": "0.33.0-
|
|
3
|
+
"version": "0.33.0-u21.0.1",
|
|
4
4
|
"description": "Vat/Container Launcher",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.js",
|
|
7
7
|
"engines": {
|
|
8
|
-
"node": "^
|
|
8
|
+
"node": "^20.9 || ^22.11"
|
|
9
9
|
},
|
|
10
10
|
"bin": {
|
|
11
11
|
"vat": "bin/vat"
|
|
@@ -13,29 +13,32 @@
|
|
|
13
13
|
"scripts": {
|
|
14
14
|
"build": "exit 0",
|
|
15
15
|
"test": "ava",
|
|
16
|
+
"test:c8": "c8 --all $C8_OPTIONS ava",
|
|
16
17
|
"test:xs": "SWINGSET_WORKER_TYPE=xs-worker ava",
|
|
17
18
|
"test:xs-worker": "ava test/workers -m 'xsnap vat manager'",
|
|
18
19
|
"lint-fix": "yarn lint:eslint --fix",
|
|
19
|
-
"lint": "run-s --continue-on-error lint:*",
|
|
20
|
-
"lint:types": "tsc",
|
|
21
|
-
"lint:eslint": "eslint ."
|
|
20
|
+
"lint": "yarn run -T run-s --continue-on-error 'lint:*'",
|
|
21
|
+
"lint:types": "yarn run -T tsc",
|
|
22
|
+
"lint:eslint": "yarn run -T eslint ."
|
|
22
23
|
},
|
|
23
24
|
"devDependencies": {
|
|
24
|
-
"@
|
|
25
|
+
"@agoric/xsnap": "0.14.3-u21.0.1",
|
|
26
|
+
"@types/better-sqlite3": "^7.6.13",
|
|
25
27
|
"@types/microtime": "^2.1.0",
|
|
26
28
|
"@types/tmp": "^0.2.0",
|
|
27
|
-
"@types/yargs-parser": "^21.0.0"
|
|
29
|
+
"@types/yargs-parser": "^21.0.0",
|
|
30
|
+
"ava": "^5.3.0"
|
|
28
31
|
},
|
|
29
32
|
"dependencies": {
|
|
30
|
-
"@agoric/internal": "
|
|
31
|
-
"@agoric/kmarshal": "
|
|
32
|
-
"@agoric/store": "
|
|
33
|
-
"@agoric/swing-store": "
|
|
34
|
-
"@agoric/swingset-liveslots": "
|
|
35
|
-
"@agoric/swingset-xsnap-supervisor": "
|
|
36
|
-
"@agoric/time": "
|
|
37
|
-
"@agoric/vat-data": "
|
|
38
|
-
"@agoric/xsnap-lockdown": "
|
|
33
|
+
"@agoric/internal": "0.4.0-u21.0.1",
|
|
34
|
+
"@agoric/kmarshal": "0.1.1-u21.0.1",
|
|
35
|
+
"@agoric/store": "0.9.3-u21.0.1",
|
|
36
|
+
"@agoric/swing-store": "0.10.0-u21.0.1",
|
|
37
|
+
"@agoric/swingset-liveslots": "0.10.3-u21.0.1",
|
|
38
|
+
"@agoric/swingset-xsnap-supervisor": "0.10.3-u21.0.1",
|
|
39
|
+
"@agoric/time": "0.3.3-u21.0.1",
|
|
40
|
+
"@agoric/vat-data": "0.5.3-u21.0.1",
|
|
41
|
+
"@agoric/xsnap-lockdown": "0.14.1-u21.0.1",
|
|
39
42
|
"@endo/base64": "^1.0.9",
|
|
40
43
|
"@endo/bundle-source": "^4.0.0",
|
|
41
44
|
"@endo/captp": "^4.4.5",
|
|
@@ -56,7 +59,7 @@
|
|
|
56
59
|
"@endo/zip": "^1.0.9",
|
|
57
60
|
"ansi-styles": "^6.2.1",
|
|
58
61
|
"anylogger": "^0.21.0",
|
|
59
|
-
"better-sqlite3": "^
|
|
62
|
+
"better-sqlite3": "^10.1.0",
|
|
60
63
|
"import-meta-resolve": "^4.1.0",
|
|
61
64
|
"microtime": "^3.1.0",
|
|
62
65
|
"semver": "^6.3.0",
|
|
@@ -64,7 +67,7 @@
|
|
|
64
67
|
"yargs-parser": "^21.1.1"
|
|
65
68
|
},
|
|
66
69
|
"peerDependencies": {
|
|
67
|
-
"@agoric/xsnap": "
|
|
70
|
+
"@agoric/xsnap": "*",
|
|
68
71
|
"ava": "^5.3.0"
|
|
69
72
|
},
|
|
70
73
|
"files": [
|
|
@@ -100,7 +103,7 @@
|
|
|
100
103
|
"access": "public"
|
|
101
104
|
},
|
|
102
105
|
"typeCoverage": {
|
|
103
|
-
"atLeast": 76.
|
|
106
|
+
"atLeast": 76.47
|
|
104
107
|
},
|
|
105
|
-
"gitHead": "
|
|
108
|
+
"gitHead": "16519b2de1eb2afda2b4ec866f55eadd4bb18223"
|
|
106
109
|
}
|
|
@@ -425,8 +425,12 @@ export async function makeSwingsetController(
|
|
|
425
425
|
return kernel.shutdown();
|
|
426
426
|
},
|
|
427
427
|
|
|
428
|
-
reapAllVats() {
|
|
429
|
-
kernel.reapAllVats();
|
|
428
|
+
reapAllVats(previousVatPos) {
|
|
429
|
+
return kernel.reapAllVats(previousVatPos);
|
|
430
|
+
},
|
|
431
|
+
|
|
432
|
+
async snapshotAllVats() {
|
|
433
|
+
return kernel.snapshotAllVats();
|
|
430
434
|
},
|
|
431
435
|
|
|
432
436
|
changeKernelOptions(options) {
|
|
@@ -285,11 +285,9 @@ export const upgradeSwingset = kernelStorage => {
|
|
|
285
285
|
|
|
286
286
|
const buggyKPIDs = []; // [kpid, vatID]
|
|
287
287
|
for (const vatID of allVatIDs) {
|
|
288
|
-
const prefix = `${vatID}.c.`;
|
|
289
|
-
const len = prefix.length;
|
|
290
288
|
const ckpPrefix = `${vatID}.c.kp`;
|
|
291
|
-
for (const
|
|
292
|
-
const kpid =
|
|
289
|
+
for (const { suffix } of enumeratePrefixedKeys(kvStore, ckpPrefix)) {
|
|
290
|
+
const kpid = `kp${suffix}`;
|
|
293
291
|
if (isSettled(kpid)) {
|
|
294
292
|
const n = notifies.get(kpid);
|
|
295
293
|
if (!n || !n.includes(vatID)) {
|
|
@@ -86,6 +86,7 @@ function makeTimerMap(state = undefined) {
|
|
|
86
86
|
return copyState(schedule);
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
+
/** @param {bigint} time */
|
|
89
90
|
function eventsFor(time) {
|
|
90
91
|
assert.typeof(time, 'bigint');
|
|
91
92
|
for (let i = 0; i < schedule.length && schedule[i].time <= time; i += 1) {
|
|
@@ -101,17 +102,29 @@ function makeTimerMap(state = undefined) {
|
|
|
101
102
|
// There's some question as to whether it's important to invoke the handlers
|
|
102
103
|
// in the order of their deadlines. If so, we should probably ensure that the
|
|
103
104
|
// recorded deadlines don't have finer granularity than the turns.
|
|
104
|
-
|
|
105
|
+
/**
|
|
106
|
+
*
|
|
107
|
+
* @param {bigint} time
|
|
108
|
+
* @param {Waker} handler
|
|
109
|
+
* @param {number} [index]
|
|
110
|
+
* @returns {bigint}
|
|
111
|
+
*/
|
|
112
|
+
function add(time, handler, index = undefined) {
|
|
105
113
|
assert.typeof(time, 'bigint');
|
|
114
|
+
/** @type {IndexedHandler} */
|
|
106
115
|
const handlerRecord =
|
|
107
|
-
typeof
|
|
116
|
+
typeof index === 'number' ? { handler, index } : { handler };
|
|
108
117
|
const { handlers: records } = eventsFor(time);
|
|
109
118
|
records.push(handlerRecord);
|
|
110
119
|
schedule.sort((a, b) => Number(a.time - b.time));
|
|
111
120
|
return time;
|
|
112
121
|
}
|
|
113
122
|
|
|
114
|
-
|
|
123
|
+
/**
|
|
124
|
+
* Remove and return all pairs indexed by numbers up to target
|
|
125
|
+
*
|
|
126
|
+
* @param {bigint} target
|
|
127
|
+
*/
|
|
115
128
|
function removeEventsThrough(target) {
|
|
116
129
|
assert.typeof(target, 'bigint');
|
|
117
130
|
const returnValues = [];
|
|
@@ -132,33 +145,29 @@ function makeTimerMap(state = undefined) {
|
|
|
132
145
|
}
|
|
133
146
|
|
|
134
147
|
// We don't expect this to be called often, so we don't optimize for it.
|
|
148
|
+
/**
|
|
149
|
+
*
|
|
150
|
+
* @param {Waker} targetHandler
|
|
151
|
+
* @returns {bigint[]} times that have been removed (may contain duplicates)
|
|
152
|
+
*/
|
|
135
153
|
function remove(targetHandler) {
|
|
154
|
+
/** @type {bigint[]} */
|
|
136
155
|
const droppedTimes = [];
|
|
137
156
|
let i = 0;
|
|
138
157
|
while (i < schedule.length) {
|
|
139
158
|
const { time, handlers } = schedule[i];
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
159
|
+
// Nothing prevents a particular handler from appearing more than once
|
|
160
|
+
for (let j = handlers.length - 1; j >= 0; j -= 1) {
|
|
161
|
+
if (handlers[j].handler === targetHandler) {
|
|
162
|
+
handlers.splice(j, 1);
|
|
143
163
|
droppedTimes.push(time);
|
|
144
|
-
} else {
|
|
145
|
-
i += 1;
|
|
146
164
|
}
|
|
165
|
+
}
|
|
166
|
+
if (handlers.length === 0) {
|
|
167
|
+
// Splice out this element, preserving `i` so we visit any successor.
|
|
168
|
+
schedule.splice(i, 1);
|
|
147
169
|
} else {
|
|
148
|
-
|
|
149
|
-
for (const { handler } of handlers) {
|
|
150
|
-
// @ts-expect-error xxx Waker vs IndexedHandler
|
|
151
|
-
if (handler === targetHandler && handlers.indexOf(handler) !== -1) {
|
|
152
|
-
// @ts-expect-error xxx Waker vs IndexedHandler
|
|
153
|
-
handlers.splice(handlers.indexOf(handler), 1);
|
|
154
|
-
droppedTimes.push(time);
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
if (handlers.length === 0) {
|
|
158
|
-
schedule.splice(i, 1);
|
|
159
|
-
} else {
|
|
160
|
-
i += 1;
|
|
161
|
-
}
|
|
170
|
+
i += 1;
|
|
162
171
|
}
|
|
163
172
|
}
|
|
164
173
|
return droppedTimes;
|
|
@@ -223,6 +232,7 @@ export function buildRootDeviceNode(tools) {
|
|
|
223
232
|
// The latest time poll() was called. This might be a block height or it
|
|
224
233
|
// might be a time from Date.now(). The current time is not reflected back
|
|
225
234
|
// to the user.
|
|
235
|
+
/** @type {bigint} */
|
|
226
236
|
let lastPolled = restart ? restart.lastPolled : 0n;
|
|
227
237
|
let nextRepeater = restart ? restart.nextRepeater : 0;
|
|
228
238
|
|
|
@@ -276,6 +286,10 @@ export function buildRootDeviceNode(tools) {
|
|
|
276
286
|
saveState();
|
|
277
287
|
return baseTime;
|
|
278
288
|
},
|
|
289
|
+
/**
|
|
290
|
+
* @param {Waker} handler
|
|
291
|
+
* @returns {bigint[]} times that have been removed (may contain duplicates)
|
|
292
|
+
*/
|
|
279
293
|
removeWakeup(handler) {
|
|
280
294
|
const times = deadlines.remove(handler);
|
|
281
295
|
saveState();
|
|
@@ -303,6 +317,10 @@ export function buildRootDeviceNode(tools) {
|
|
|
303
317
|
saveState();
|
|
304
318
|
return index;
|
|
305
319
|
},
|
|
320
|
+
/**
|
|
321
|
+
* @param {number} index
|
|
322
|
+
* @param {Waker} handler
|
|
323
|
+
*/
|
|
306
324
|
schedule(index, handler) {
|
|
307
325
|
const nextTime = nextScheduleTime(index, repeaters, lastPolled);
|
|
308
326
|
deadlines.add(nextTime, handler, index);
|
|
@@ -311,6 +329,7 @@ export function buildRootDeviceNode(tools) {
|
|
|
311
329
|
},
|
|
312
330
|
});
|
|
313
331
|
}
|
|
332
|
+
/** @typedef {import('../../types-external.js').Device<ReturnType<typeof buildRootDeviceNode>>} TimerDevice */
|
|
314
333
|
|
|
315
334
|
// exported for testing. Only buildRootDeviceNode is intended for production
|
|
316
335
|
// use.
|
package/src/kernel/kernel.js
CHANGED
|
@@ -1946,15 +1946,63 @@ export default function buildKernel(
|
|
|
1946
1946
|
}
|
|
1947
1947
|
}
|
|
1948
1948
|
|
|
1949
|
-
|
|
1949
|
+
/** @returns {Generator<VatID>} */
|
|
1950
|
+
function* getAllVatIds() {
|
|
1950
1951
|
for (const [_, vatID] of kernelKeeper.getStaticVats()) {
|
|
1951
|
-
|
|
1952
|
+
yield vatID;
|
|
1952
1953
|
}
|
|
1953
1954
|
for (const vatID of kernelKeeper.getDynamicVats()) {
|
|
1954
|
-
|
|
1955
|
+
yield vatID;
|
|
1955
1956
|
}
|
|
1956
1957
|
}
|
|
1957
1958
|
|
|
1959
|
+
function* getAllVatPosEntries() {
|
|
1960
|
+
for (const vatID of getAllVatIds()) {
|
|
1961
|
+
const vatKeeper = kernelKeeper.provideVatKeeper(vatID);
|
|
1962
|
+
yield /** @type {const} */ ([
|
|
1963
|
+
vatID,
|
|
1964
|
+
vatKeeper.getTranscriptEndPosition(),
|
|
1965
|
+
]);
|
|
1966
|
+
}
|
|
1967
|
+
}
|
|
1968
|
+
|
|
1969
|
+
function reapAllVats(previousVatPos = {}) {
|
|
1970
|
+
/** @type {Record<string, number>} */
|
|
1971
|
+
const currentVatPos = {};
|
|
1972
|
+
|
|
1973
|
+
for (const [vatID, endPos] of getAllVatPosEntries()) {
|
|
1974
|
+
const vatUsesTranscript = endPos !== 0;
|
|
1975
|
+
if (!vatUsesTranscript) {
|
|
1976
|
+
// The comms vat is a little special. It doesn't use a transcript,
|
|
1977
|
+
// doesn't implement BOYD, and is created with a never reap threshold.
|
|
1978
|
+
//
|
|
1979
|
+
// Here we conflate a vat that doesn't use a transcript with a vat
|
|
1980
|
+
// that cannot reap. We do not bother checking the vat options because
|
|
1981
|
+
// in tests we would actually like to force reap normal vats that may
|
|
1982
|
+
// have been configured with a never threshold.
|
|
1983
|
+
continue;
|
|
1984
|
+
} else if ((previousVatPos[vatID] ?? 0) < endPos) {
|
|
1985
|
+
kernelKeeper.scheduleReap(vatID);
|
|
1986
|
+
// We just added one delivery
|
|
1987
|
+
currentVatPos[vatID] = endPos + 1;
|
|
1988
|
+
} else {
|
|
1989
|
+
currentVatPos[vatID] = endPos;
|
|
1990
|
+
}
|
|
1991
|
+
}
|
|
1992
|
+
|
|
1993
|
+
return harden(currentVatPos);
|
|
1994
|
+
}
|
|
1995
|
+
|
|
1996
|
+
async function snapshotAllVats() {
|
|
1997
|
+
const snapshottedVats = [];
|
|
1998
|
+
await null;
|
|
1999
|
+
for (const vatID of getAllVatIds()) {
|
|
2000
|
+
const snapshotted = await vatWarehouse.maybeSaveSnapshot(vatID, 2);
|
|
2001
|
+
if (snapshotted) snapshottedVats.push(vatID);
|
|
2002
|
+
}
|
|
2003
|
+
return harden(snapshottedVats);
|
|
2004
|
+
}
|
|
2005
|
+
|
|
1958
2006
|
async function step() {
|
|
1959
2007
|
if (kernelPanic) {
|
|
1960
2008
|
throw kernelPanic;
|
|
@@ -2159,6 +2207,7 @@ export default function buildKernel(
|
|
|
2159
2207
|
run,
|
|
2160
2208
|
shutdown,
|
|
2161
2209
|
reapAllVats,
|
|
2210
|
+
snapshotAllVats,
|
|
2162
2211
|
changeKernelOptions,
|
|
2163
2212
|
|
|
2164
2213
|
// the rest are for testing and debugging
|
|
@@ -189,11 +189,12 @@ export function makeDeviceKeeper(kvStore, deviceID, tools) {
|
|
|
189
189
|
/** @type {Array<[string, string, string]>} */
|
|
190
190
|
const res = [];
|
|
191
191
|
const prefix = `${deviceID}.c.`;
|
|
192
|
-
|
|
193
|
-
|
|
192
|
+
const prefixedKeys = enumeratePrefixedKeys(kvStore, prefix);
|
|
193
|
+
|
|
194
|
+
for (const { key, suffix: slot } of prefixedKeys) {
|
|
194
195
|
if (!slot.startsWith('k')) {
|
|
195
196
|
const devSlot = slot;
|
|
196
|
-
const kernelSlot = kvStore.get(
|
|
197
|
+
const kernelSlot = kvStore.get(key);
|
|
197
198
|
res.push([kernelSlot, deviceID, devSlot]);
|
|
198
199
|
}
|
|
199
200
|
}
|
|
@@ -212,9 +212,8 @@ function insistMeterID(m) {
|
|
|
212
212
|
export const getAllStaticVats = kvStore => {
|
|
213
213
|
const result = [];
|
|
214
214
|
const prefix = 'vat.name.';
|
|
215
|
-
for (const
|
|
216
|
-
const vatID = kvStore.get(
|
|
217
|
-
const name = k.slice(prefix.length);
|
|
215
|
+
for (const { key, suffix: name } of enumeratePrefixedKeys(kvStore, prefix)) {
|
|
216
|
+
const vatID = kvStore.get(key) || Fail`getNextKey ensures get`;
|
|
218
217
|
result.push([name, vatID]);
|
|
219
218
|
}
|
|
220
219
|
return result;
|
|
@@ -718,17 +717,22 @@ export default function makeKernelKeeper(
|
|
|
718
717
|
// We iterate through all ephemeral and virtual entries so the kernel
|
|
719
718
|
// can ensure that they are abandoned by a vat being upgraded.
|
|
720
719
|
const prefix = `${vatID}.c.`;
|
|
721
|
-
const ephStart =
|
|
722
|
-
const durStart =
|
|
723
|
-
const virStart =
|
|
720
|
+
const ephStart = `o+`;
|
|
721
|
+
const durStart = `o+d`;
|
|
722
|
+
const virStart = `o+v`;
|
|
724
723
|
/** @type {[string, string?][]} */
|
|
725
724
|
const ranges = [[ephStart, durStart], [virStart]];
|
|
726
725
|
for (const range of ranges) {
|
|
727
|
-
|
|
728
|
-
|
|
726
|
+
const rangeSuffix = range[0];
|
|
727
|
+
const args = /** @type {typeof ranges[0]} */ (
|
|
728
|
+
/** @type {unknown} */ (range.map(s => `${prefix}${s}`))
|
|
729
|
+
);
|
|
730
|
+
const prefixedKeys = enumeratePrefixedKeys(kvStore, ...args);
|
|
731
|
+
for (const { key, suffix } of prefixedKeys) {
|
|
732
|
+
const vref = `${rangeSuffix}${suffix}`;
|
|
729
733
|
// exclude the root object, which is replaced by upgrade
|
|
730
734
|
if (vref !== 'o+0') {
|
|
731
|
-
const kref = kvStore.get(
|
|
735
|
+
const kref = kvStore.get(key);
|
|
732
736
|
assert.typeof(kref, 'string');
|
|
733
737
|
yield { kref, vref };
|
|
734
738
|
}
|
|
@@ -1072,7 +1076,7 @@ export default function makeKernelKeeper(
|
|
|
1072
1076
|
|
|
1073
1077
|
// first, scan for exported objects, which must be orphaned
|
|
1074
1078
|
remaining = budget.exports ?? budget.default;
|
|
1075
|
-
for (const
|
|
1079
|
+
for (const { key } of enumeratePrefixedKeys(kvStore, exportPrefix)) {
|
|
1076
1080
|
// The void for an object exported by a vat will always be of the form
|
|
1077
1081
|
// `o+NN`. The '+' means that the vat exported the object (rather than
|
|
1078
1082
|
// importing it) and therefore the object is owned by (i.e., within) the
|
|
@@ -1080,9 +1084,7 @@ export default function makeKernelKeeper(
|
|
|
1080
1084
|
// begin with `vMM.c.o+`. In addition to deleting the c-list entry, we
|
|
1081
1085
|
// must also delete the corresponding kernel owner entry for the object,
|
|
1082
1086
|
// since the object will no longer be accessible.
|
|
1083
|
-
const
|
|
1084
|
-
assert(vref.startsWith('o+'), vref);
|
|
1085
|
-
const kref = kvStore.get(k);
|
|
1087
|
+
const kref = kvStore.get(key);
|
|
1086
1088
|
// note: adds to maybeFreeKrefs, deletes c-list and .owner
|
|
1087
1089
|
orphanKernelObject(kref, vatID);
|
|
1088
1090
|
work.exports += 1;
|
|
@@ -1094,11 +1096,14 @@ export default function makeKernelKeeper(
|
|
|
1094
1096
|
|
|
1095
1097
|
// then scan for imported objects, which must be decrefed
|
|
1096
1098
|
remaining = budget.imports ?? budget.default;
|
|
1097
|
-
for (const
|
|
1099
|
+
for (const { key, suffix } of enumeratePrefixedKeys(
|
|
1100
|
+
kvStore,
|
|
1101
|
+
importPrefix,
|
|
1102
|
+
)) {
|
|
1098
1103
|
// abandoned imports: delete the clist entry as if the vat did a
|
|
1099
1104
|
// drop+retire
|
|
1100
|
-
const kref = kvStore.get(
|
|
1101
|
-
const vref =
|
|
1105
|
+
const kref = kvStore.get(key) || Fail`getNextKey ensures get`;
|
|
1106
|
+
const vref = `o-${suffix}`;
|
|
1102
1107
|
undertaker.deleteCListEntry(kref, vref);
|
|
1103
1108
|
// that will also delete both db keys
|
|
1104
1109
|
work.imports += 1;
|
|
@@ -1113,9 +1118,12 @@ export default function makeKernelKeeper(
|
|
|
1113
1118
|
// kpids are still present in the dead vat's c-list. Clean those
|
|
1114
1119
|
// up now.
|
|
1115
1120
|
remaining = budget.promises ?? budget.default;
|
|
1116
|
-
for (const
|
|
1117
|
-
|
|
1118
|
-
|
|
1121
|
+
for (const { key, suffix } of enumeratePrefixedKeys(
|
|
1122
|
+
kvStore,
|
|
1123
|
+
promisePrefix,
|
|
1124
|
+
)) {
|
|
1125
|
+
const kref = kvStore.get(key) || Fail`getNextKey ensures get`;
|
|
1126
|
+
const vref = `p${suffix}`;
|
|
1119
1127
|
undertaker.deleteCListEntry(kref, vref);
|
|
1120
1128
|
// that will also delete both db keys
|
|
1121
1129
|
work.promises += 1;
|
|
@@ -1127,8 +1135,8 @@ export default function makeKernelKeeper(
|
|
|
1127
1135
|
|
|
1128
1136
|
// now loop back through everything and delete it all
|
|
1129
1137
|
remaining = budget.kv ?? budget.default;
|
|
1130
|
-
for (const
|
|
1131
|
-
kvStore.delete(
|
|
1138
|
+
for (const { key } of enumeratePrefixedKeys(kvStore, `${vatID}.`)) {
|
|
1139
|
+
kvStore.delete(key);
|
|
1132
1140
|
work.kv += 1;
|
|
1133
1141
|
remaining -= 1;
|
|
1134
1142
|
if (remaining <= 0) {
|
|
@@ -1167,11 +1175,11 @@ export default function makeKernelKeeper(
|
|
|
1167
1175
|
kvStore.set(DYNAMIC_IDS_KEY, JSON.stringify(newDynamicVatIDs));
|
|
1168
1176
|
} else {
|
|
1169
1177
|
kdebug(`removing static vat ${vatID}`);
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1178
|
+
const prefixedKeys = enumeratePrefixedKeys(kvStore, 'vat.name.');
|
|
1179
|
+
for (const { key, suffix: name } of prefixedKeys) {
|
|
1180
|
+
if (kvStore.get(key) === vatID) {
|
|
1181
|
+
kvStore.delete(key);
|
|
1173
1182
|
const VAT_NAMES_KEY = 'vat.names';
|
|
1174
|
-
const name = k.slice('vat.name.'.length);
|
|
1175
1183
|
const oldStaticVatNames = JSON.parse(getRequired(VAT_NAMES_KEY));
|
|
1176
1184
|
const newStaticVatNames = oldStaticVatNames.filter(v => v !== name);
|
|
1177
1185
|
kvStore.set(VAT_NAMES_KEY, JSON.stringify(newStaticVatNames));
|
|
@@ -1240,7 +1248,7 @@ export default function makeKernelKeeper(
|
|
|
1240
1248
|
function* enumeratePromisesByDecider(vatID) {
|
|
1241
1249
|
insistVatID(vatID);
|
|
1242
1250
|
const promisePrefix = `${vatID}.c.p`;
|
|
1243
|
-
for (const
|
|
1251
|
+
for (const { key } of enumeratePrefixedKeys(kvStore, promisePrefix)) {
|
|
1244
1252
|
// The vpid for a promise imported or exported by a vat (and thus
|
|
1245
1253
|
// potentially a promise for which the vat *might* be the decider) will
|
|
1246
1254
|
// always be of the form `p+NN` or `p-NN`. The corresponding vpid->kpid
|
|
@@ -1250,7 +1258,7 @@ export default function makeKernelKeeper(
|
|
|
1250
1258
|
// whether the vat is the decider or not. If it is, we add the promise
|
|
1251
1259
|
// to the list of promises that must be rejected because the dead vat
|
|
1252
1260
|
// will never be able to act upon them.
|
|
1253
|
-
const kpid = getRequired(
|
|
1261
|
+
const kpid = getRequired(key);
|
|
1254
1262
|
const p = getKernelPromise(kpid);
|
|
1255
1263
|
if (p.state === 'unresolved' && p.decider === vatID) {
|
|
1256
1264
|
yield [kpid, p];
|
|
@@ -1449,9 +1457,9 @@ export default function makeKernelKeeper(
|
|
|
1449
1457
|
|
|
1450
1458
|
function getDevices() {
|
|
1451
1459
|
const result = [];
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
const deviceID = kvStore.get(
|
|
1460
|
+
const prefixedKeys = enumeratePrefixedKeys(kvStore, 'device.name.');
|
|
1461
|
+
for (const { key, suffix: name } of prefixedKeys) {
|
|
1462
|
+
const deviceID = kvStore.get(key) || Fail`getNextKey ensures get`;
|
|
1455
1463
|
result.push([name, deviceID]);
|
|
1456
1464
|
}
|
|
1457
1465
|
return result;
|
|
@@ -13,7 +13,8 @@ import { Fail } from '@endo/errors';
|
|
|
13
13
|
* @param {KVStore} kvStore
|
|
14
14
|
* @param {string} prefix
|
|
15
15
|
* @param {string} [exclusiveEnd]
|
|
16
|
-
* @yields {string} the next key with the prefix that is not >= exclusiveEnd
|
|
16
|
+
* @yields {{key: string; suffix: string}} the next `key` with the prefix that is not >= exclusiveEnd
|
|
17
|
+
* and the `suffix` which is obtained by stripping the supplied prefix from the key
|
|
17
18
|
*/
|
|
18
19
|
export function* enumeratePrefixedKeys(kvStore, prefix, exclusiveEnd) {
|
|
19
20
|
/** @type {string | undefined} */
|
|
@@ -26,7 +27,7 @@ export function* enumeratePrefixedKeys(kvStore, prefix, exclusiveEnd) {
|
|
|
26
27
|
if (exclusiveEnd && key >= exclusiveEnd) {
|
|
27
28
|
break;
|
|
28
29
|
}
|
|
29
|
-
yield key;
|
|
30
|
+
yield { key, suffix: key.slice(prefix.length) };
|
|
30
31
|
}
|
|
31
32
|
}
|
|
32
33
|
harden(enumeratePrefixedKeys);
|
|
@@ -761,11 +761,13 @@ export function makeVatKeeper(
|
|
|
761
761
|
function dumpState() {
|
|
762
762
|
const res = [];
|
|
763
763
|
const prefix = `${vatID}.c.`;
|
|
764
|
-
for (const
|
|
765
|
-
|
|
764
|
+
for (const { key, suffix: slot } of enumeratePrefixedKeys(
|
|
765
|
+
kvStore,
|
|
766
|
+
prefix,
|
|
767
|
+
)) {
|
|
766
768
|
if (!slot.startsWith('k')) {
|
|
767
769
|
const vatSlot = slot;
|
|
768
|
-
const kernelSlot = kvStore.get(
|
|
770
|
+
const kernelSlot = kvStore.get(key) || Fail`getNextKey ensures get`;
|
|
769
771
|
/** @type { [string, string, string] } */
|
|
770
772
|
const item = [kernelSlot, vatID, vatSlot];
|
|
771
773
|
res.push(item);
|
|
@@ -566,18 +566,20 @@ export function makeVatWarehouse({
|
|
|
566
566
|
|
|
567
567
|
/**
|
|
568
568
|
* Save a heap snapshot for the given vatID, if the snapshotInterval
|
|
569
|
-
* is
|
|
569
|
+
* is satisfied or a lower explicit delivery count has been reached.
|
|
570
570
|
*
|
|
571
571
|
* @param {VatID} vatID
|
|
572
|
+
* @param {number} [minDeliveryCount]
|
|
572
573
|
*/
|
|
573
|
-
async function maybeSaveSnapshot(vatID) {
|
|
574
|
-
|
|
575
|
-
const
|
|
576
|
-
|
|
577
|
-
|
|
574
|
+
async function maybeSaveSnapshot(vatID, minDeliveryCount = snapshotInterval) {
|
|
575
|
+
kernelKeeper.vatIsAlive(vatID) || Fail`${q(vatID)}: not alive`;
|
|
576
|
+
const vatKeeper = kernelKeeper.provideVatKeeper(vatID);
|
|
577
|
+
const vatOptions = vatKeeper.getOptions();
|
|
578
|
+
|
|
579
|
+
if (!vatOptions.useTranscript) {
|
|
580
|
+
return false;
|
|
578
581
|
}
|
|
579
582
|
|
|
580
|
-
const vatKeeper = kernelKeeper.provideVatKeeper(vatID);
|
|
581
583
|
let reason;
|
|
582
584
|
|
|
583
585
|
const hasSnapshot = !!vatKeeper.getSnapshotInfo();
|
|
@@ -586,15 +588,21 @@ export function makeVatWarehouse({
|
|
|
586
588
|
if (!hasSnapshot && deliveriesInSpan >= snapshotInitial) {
|
|
587
589
|
// begin snapshot after 'snapshotInitial' deliveries in an incarnation
|
|
588
590
|
reason = { snapshotInitial };
|
|
589
|
-
} else if (deliveriesInSpan >=
|
|
591
|
+
} else if (deliveriesInSpan >= minDeliveryCount) {
|
|
590
592
|
// begin snapshot after 'snapshotInterval' deliveries in a span
|
|
591
|
-
reason = { snapshotInterval };
|
|
593
|
+
reason = { snapshotInterval: minDeliveryCount };
|
|
592
594
|
}
|
|
593
595
|
// console.log('maybeSaveSnapshot: reason:', reason);
|
|
594
596
|
if (!reason) {
|
|
595
597
|
return false; // not time to make a snapshot
|
|
596
598
|
}
|
|
597
599
|
|
|
600
|
+
const recreate = true; // PANIC in the failure case
|
|
601
|
+
const { manager } = await ensureVatOnline(vatID, recreate);
|
|
602
|
+
if (!manager.makeSnapshot) {
|
|
603
|
+
return false; // worker cannot make snapshots
|
|
604
|
+
}
|
|
605
|
+
|
|
598
606
|
// always do a bringOutYourDead just before a snapshot, to shake
|
|
599
607
|
// loose as much garbage as we can, and to minimize the GC
|
|
600
608
|
// sensitivity effects of the forced GC that snapshots perform
|
package/src/lib/capdata.js
CHANGED
|
@@ -1,24 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { krefOf, kunser } from '@agoric/kmarshal';
|
|
2
2
|
import { passStyleOf } from '@endo/far';
|
|
3
|
-
import { kunser, krefOf } from '@agoric/kmarshal';
|
|
4
3
|
|
|
5
4
|
/**
|
|
6
|
-
*
|
|
7
|
-
* actually is. A capdata object should have a .body property that's a string
|
|
8
|
-
* and a .slots property that's an array of strings.
|
|
9
|
-
*
|
|
10
|
-
* @param {any} capdata The object to be tested
|
|
11
|
-
* @throws {Error} if, upon inspection, the parameter does not satisfy the above
|
|
12
|
-
* criteria.
|
|
13
|
-
* @returns {asserts capdata is import('@endo/marshal').CapData<string>}
|
|
5
|
+
* @import {CapData} from '@endo/marshal';
|
|
14
6
|
*/
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
Fail`capdata has non-string .body ${capdata.body}`;
|
|
18
|
-
Array.isArray(capdata.slots) ||
|
|
19
|
-
Fail`capdata has non-Array slots ${capdata.slots}`;
|
|
20
|
-
// TODO check that the .slots array elements are actually strings
|
|
21
|
-
}
|
|
7
|
+
|
|
8
|
+
export { insistCapData } from '@agoric/swingset-liveslots/src/capdata.js';
|
|
22
9
|
|
|
23
10
|
/**
|
|
24
11
|
* Returns the slot of a presence if the provided capdata is composed
|
package/src/types-external.js
CHANGED
|
@@ -12,6 +12,13 @@ export {};
|
|
|
12
12
|
* includes standard services which user-provided vat code might interact
|
|
13
13
|
* with, like VatAdminService. */
|
|
14
14
|
|
|
15
|
+
/**
|
|
16
|
+
* @template T
|
|
17
|
+
* @typedef {'Device' & { __deviceType__: T }} Device
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
/** @typedef {<T>(target: Device<T>) => T} DProxy (approximately) */
|
|
21
|
+
|
|
15
22
|
/**
|
|
16
23
|
* @typedef {(extraProps?: SlogDurationProps) => void} FinishSlogDuration
|
|
17
24
|
*/
|
|
@@ -25,12 +32,17 @@ export {};
|
|
|
25
32
|
* @typedef {'getExport' | 'nestedEvaluate' | 'endoZipBase64'} BundleFormat
|
|
26
33
|
* @typedef { { moduleFormat: 'getExport', source: string, sourceMap?: string } } GetExportBundle
|
|
27
34
|
* @typedef { { moduleFormat: 'nestedEvaluate', source: string, sourceMap?: string } } NestedEvaluateBundle
|
|
28
|
-
* @typedef { { moduleFormat: 'test' } } TestBundle
|
|
35
|
+
* @typedef { { moduleFormat: 'test', [x: symbol]: Record<PropertyKey, unknown> } } TestBundle
|
|
29
36
|
* @typedef { EndoZipBase64Bundle | GetExportBundle | NestedEvaluateBundle | TestBundle} Bundle
|
|
30
37
|
*/
|
|
31
38
|
|
|
32
39
|
/**
|
|
33
40
|
* @typedef { 'local' | 'node-subprocess' | 'xsnap' | 'xs-worker' } ManagerType
|
|
41
|
+
* The type of worker for hosting a vat.
|
|
42
|
+
* - **local**: a Compartment in the SwingSet Node.js process
|
|
43
|
+
* - **node-subprocess**: a child process using the same Node.js executable
|
|
44
|
+
* (`process.execPath`)
|
|
45
|
+
* - **xsnap** or **xs-worker**: an {@link @agoric/xsnap! @agoric/xsnap} worker
|
|
34
46
|
*/
|
|
35
47
|
|
|
36
48
|
/**
|
|
@@ -390,7 +402,9 @@ export {};
|
|
|
390
402
|
*
|
|
391
403
|
* @typedef { { vatParameters?: object, upgradeMessage?: string } } VatUpgradeOptions
|
|
392
404
|
* @typedef { { incarnationNumber: number } } VatUpgradeResults
|
|
393
|
-
|
|
405
|
+
*/
|
|
406
|
+
|
|
407
|
+
/**
|
|
394
408
|
* @callback ShutdownWithFailure
|
|
395
409
|
* Called to shut something down because something went wrong, where the reason
|
|
396
410
|
* is supposed to be an Error that describes what went wrong. Some valid
|
|
@@ -401,7 +415,9 @@ export {};
|
|
|
401
415
|
*
|
|
402
416
|
* @param {Error} reason
|
|
403
417
|
* @returns {void}
|
|
404
|
-
|
|
418
|
+
*/
|
|
419
|
+
|
|
420
|
+
/**
|
|
405
421
|
* @typedef {object} VatAdminFacet
|
|
406
422
|
* A powerful object corresponding with a vat
|
|
407
423
|
* that can be used to upgrade it with new code or parameters,
|
|
@@ -419,8 +435,9 @@ export {};
|
|
|
419
435
|
* in which the JS memory space is abandoned. The new image is launched with access to 'baggage'
|
|
420
436
|
* and any durable storage reachable from it, and must fulfill all the obligations of the previous
|
|
421
437
|
* incarnation.
|
|
422
|
-
|
|
423
|
-
|
|
438
|
+
*/
|
|
439
|
+
|
|
440
|
+
/**
|
|
424
441
|
* @typedef {{ adminNode: Guarded<VatAdminFacet>, root: object }} CreateVatResults
|
|
425
442
|
*
|
|
426
443
|
* @typedef {object} VatAdminSvc
|
|
@@ -429,5 +446,4 @@ export {};
|
|
|
429
446
|
* @property {(name: string) => ERef<BundleCap>} getNamedBundleCap
|
|
430
447
|
* @property {(name: string) => ERef<BundleID>} getBundleIDByName
|
|
431
448
|
* @property {(bundleCap: BundleCap, options?: DynamicVatOptions) => ERef<CreateVatResults>} createVat
|
|
432
|
-
*
|
|
433
449
|
*/
|
|
@@ -7,6 +7,8 @@ import { Far, E } from '@endo/far';
|
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* @import {ERef} from '@endo/far';
|
|
10
|
+
* @import {MapStore} from '@agoric/store';
|
|
11
|
+
* @import {Device, DProxy} from '@agoric/swingset-vat/src/types-external.js';
|
|
10
12
|
*/
|
|
11
13
|
|
|
12
14
|
/** @type {{ onReset: (firstTime: Promise<boolean>) => void}} */
|
|
@@ -15,13 +17,6 @@ const DEFAULT_RESETTER = Far('resetter', { onReset: _ => {} });
|
|
|
15
17
|
/** @type {{ walk: (pluginRootP: any) => any }} */
|
|
16
18
|
const DEFAULT_WALKER = Far('walker', { walk: pluginRootP => pluginRootP });
|
|
17
19
|
|
|
18
|
-
/**
|
|
19
|
-
* @template T
|
|
20
|
-
* @typedef {'Device' & { __deviceType__: T }} Device
|
|
21
|
-
*/
|
|
22
|
-
|
|
23
|
-
/** @typedef {<T>(target: Device<T>) => T} DProxy (approximately) */
|
|
24
|
-
|
|
25
20
|
/**
|
|
26
21
|
* @callback LoadPlugin
|
|
27
22
|
* @param {string} specifier
|
|
@@ -15,9 +15,11 @@ import { TimeMath } from '@agoric/time';
|
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
17
|
* @import {LegacyWeakMap, WeakMapStore} from '@agoric/store';
|
|
18
|
-
* @import {MapStore} from '@agoric/swingset-liveslots';
|
|
18
|
+
* @import {Baggage, MapStore} from '@agoric/swingset-liveslots';
|
|
19
19
|
* @import {Passable, RemotableObject} from '@endo/pass-style';
|
|
20
20
|
* @import {Key} from '@endo/patterns';
|
|
21
|
+
* @import {TimerDevice} from '../../devices/timer/device-timer.js';
|
|
22
|
+
* @import {DProxy} from '../../types-external.js';
|
|
21
23
|
*/
|
|
22
24
|
|
|
23
25
|
// This consumes O(N) RAM only for outstanding promises, via wakeAt(),
|
|
@@ -237,9 +239,17 @@ const measureInterval = (start, interval, now) => {
|
|
|
237
239
|
return { latest, next };
|
|
238
240
|
};
|
|
239
241
|
|
|
242
|
+
/**
|
|
243
|
+
* @param {{
|
|
244
|
+
* D: DProxy;
|
|
245
|
+
* }} vatPowers
|
|
246
|
+
* @param {{}} _vatParameters
|
|
247
|
+
* @param {Baggage} baggage
|
|
248
|
+
*/
|
|
240
249
|
export const buildRootObject = (vatPowers, _vatParameters, baggage) => {
|
|
241
250
|
const { D } = vatPowers;
|
|
242
251
|
|
|
252
|
+
/** @type {TimerDevice} */
|
|
243
253
|
let timerDevice;
|
|
244
254
|
const insistDevice = () => {
|
|
245
255
|
assert(timerDevice, 'TimerService used before createTimerService()');
|
|
@@ -455,9 +465,6 @@ export const buildRootObject = (vatPowers, _vatParameters, baggage) => {
|
|
|
455
465
|
},
|
|
456
466
|
};
|
|
457
467
|
|
|
458
|
-
/**
|
|
459
|
-
* @returns { PromiseEvent }
|
|
460
|
-
*/
|
|
461
468
|
const makePromiseEvent = prepareKind(
|
|
462
469
|
baggage,
|
|
463
470
|
'promiseEvent',
|
|
@@ -948,7 +955,7 @@ export const buildRootObject = (vatPowers, _vatParameters, baggage) => {
|
|
|
948
955
|
* device, but we don't prohibit it from being called again (to
|
|
949
956
|
* replace the device), just in case that's useful someday
|
|
950
957
|
*
|
|
951
|
-
* @param {
|
|
958
|
+
* @param {TimerDevice} timerNode
|
|
952
959
|
* @returns {TimerService}
|
|
953
960
|
*/
|
|
954
961
|
const createTimerService = timerNode => {
|
|
@@ -20,7 +20,7 @@ import {
|
|
|
20
20
|
|
|
21
21
|
/**
|
|
22
22
|
* @import {VatAdminRootDeviceNode} from '../../devices/vat-admin/device-vat-admin.js';
|
|
23
|
-
* @import {DProxy} from'
|
|
23
|
+
* @import {DProxy} from'../../types-external.js';
|
|
24
24
|
* @import {Baggage} from '@agoric/vat-data';
|
|
25
25
|
*/
|
|
26
26
|
|
|
@@ -49,7 +49,7 @@ export function buildRootObject(vatPowers, _vatParameters, baggage) {
|
|
|
49
49
|
const pendingBundles = new Map();
|
|
50
50
|
const pendingUpgrades = new Map(); // upgradeID -> Promise<UpgradeResults>
|
|
51
51
|
|
|
52
|
-
/** @type {import('
|
|
52
|
+
/** @type {import('../../types-external.js').Device<VatAdminRootDeviceNode>} */
|
|
53
53
|
let vatAdminDev;
|
|
54
54
|
|
|
55
55
|
const runningVats = new Map(); // vatID -> [doneP, { resolve, reject }]
|
package/tools/manual-timer.js
CHANGED
|
@@ -8,7 +8,7 @@ import { buildRootObject } from '../src/vats/timer/vat-timer.js';
|
|
|
8
8
|
/**
|
|
9
9
|
* @import {Timestamp} from '@agoric/time'
|
|
10
10
|
* @import {TimerService} from '@agoric/time'
|
|
11
|
-
* @import {Waker} from '../src/devices/timer/device-timer.js'
|
|
11
|
+
* @import {TimerDevice, Waker} from '../src/devices/timer/device-timer.js'
|
|
12
12
|
*
|
|
13
13
|
* @typedef {object} ManualTimerCallbacks
|
|
14
14
|
* @property {(newTime: bigint, msg?: string) => void} [advanceTo]
|
|
@@ -34,7 +34,7 @@ const setup = callbacks => {
|
|
|
34
34
|
currentWakeup: undefined,
|
|
35
35
|
currentHandler: undefined,
|
|
36
36
|
};
|
|
37
|
-
const deviceMarker = harden({});
|
|
37
|
+
const deviceMarker = /** @type {TimerDevice} */ (harden({}));
|
|
38
38
|
const timerDeviceFuncs = harden({
|
|
39
39
|
getLastPolled: () => state.now,
|
|
40
40
|
setWakeup: (when, handler) => {
|
|
@@ -54,6 +54,7 @@ const setup = callbacks => {
|
|
|
54
54
|
state.currentHandler = undefined;
|
|
55
55
|
},
|
|
56
56
|
});
|
|
57
|
+
/** @type {any} */
|
|
57
58
|
const D = node => {
|
|
58
59
|
assert.equal(node, deviceMarker, 'fake D only supports devices.timer');
|
|
59
60
|
return timerDeviceFuncs;
|