@agoric/swingset-vat 0.32.3-dev-0eaf7cc.0 → 0.32.3-dev-1f816f9.0
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 +12 -12
- package/src/controller/upgradeSwingset.js +8 -0
- package/src/kernel/kernel.js +67 -20
- package/src/kernel/state/kernelKeeper.js +183 -13
- package/src/kernel/state/vatKeeper.js +39 -5
- package/src/kernel/vat-warehouse.js +4 -0
- package/src/lib/runPolicies.js +49 -3
- package/src/types-external.js +7 -2
- package/src/types-internal.js +3 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agoric/swingset-vat",
|
|
3
|
-
"version": "0.32.3-dev-
|
|
3
|
+
"version": "0.32.3-dev-1f816f9.0+1f816f9",
|
|
4
4
|
"description": "Vat/Container Launcher",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.js",
|
|
@@ -27,16 +27,16 @@
|
|
|
27
27
|
"@types/yargs-parser": "^21.0.0"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@agoric/internal": "0.3.3-dev-
|
|
31
|
-
"@agoric/kmarshal": "0.1.1-dev-
|
|
32
|
-
"@agoric/store": "0.9.3-dev-
|
|
33
|
-
"@agoric/swing-store": "0.9.2-dev-
|
|
34
|
-
"@agoric/swingset-liveslots": "0.10.3-dev-
|
|
35
|
-
"@agoric/swingset-xsnap-supervisor": "0.10.3-dev-
|
|
36
|
-
"@agoric/time": "0.3.3-dev-
|
|
37
|
-
"@agoric/vat-data": "0.5.3-dev-
|
|
38
|
-
"@agoric/xsnap": "0.14.3-dev-
|
|
39
|
-
"@agoric/xsnap-lockdown": "0.14.1-dev-
|
|
30
|
+
"@agoric/internal": "0.3.3-dev-1f816f9.0+1f816f9",
|
|
31
|
+
"@agoric/kmarshal": "0.1.1-dev-1f816f9.0+1f816f9",
|
|
32
|
+
"@agoric/store": "0.9.3-dev-1f816f9.0+1f816f9",
|
|
33
|
+
"@agoric/swing-store": "0.9.2-dev-1f816f9.0+1f816f9",
|
|
34
|
+
"@agoric/swingset-liveslots": "0.10.3-dev-1f816f9.0+1f816f9",
|
|
35
|
+
"@agoric/swingset-xsnap-supervisor": "0.10.3-dev-1f816f9.0+1f816f9",
|
|
36
|
+
"@agoric/time": "0.3.3-dev-1f816f9.0+1f816f9",
|
|
37
|
+
"@agoric/vat-data": "0.5.3-dev-1f816f9.0+1f816f9",
|
|
38
|
+
"@agoric/xsnap": "0.14.3-dev-1f816f9.0+1f816f9",
|
|
39
|
+
"@agoric/xsnap-lockdown": "0.14.1-dev-1f816f9.0+1f816f9",
|
|
40
40
|
"@endo/base64": "^1.0.6",
|
|
41
41
|
"@endo/bundle-source": "^3.3.0",
|
|
42
42
|
"@endo/captp": "^4.2.2",
|
|
@@ -102,5 +102,5 @@
|
|
|
102
102
|
"typeCoverage": {
|
|
103
103
|
"atLeast": 75.1
|
|
104
104
|
},
|
|
105
|
-
"gitHead": "
|
|
105
|
+
"gitHead": "1f816f92e8b4f79c43df9608d7a7b443a3ceca20"
|
|
106
106
|
}
|
|
@@ -196,6 +196,14 @@ export const upgradeSwingset = kernelStorage => {
|
|
|
196
196
|
version = 1;
|
|
197
197
|
}
|
|
198
198
|
|
|
199
|
+
if (version < 2) {
|
|
200
|
+
// schema v2: add vats.terminated = []
|
|
201
|
+
assert(!kvStore.has('vats.terminated'));
|
|
202
|
+
kvStore.set('vats.terminated', JSON.stringify([]));
|
|
203
|
+
modified = true;
|
|
204
|
+
version = 2;
|
|
205
|
+
}
|
|
206
|
+
|
|
199
207
|
if (modified) {
|
|
200
208
|
kvStore.set('version', `${version}`);
|
|
201
209
|
}
|
package/src/kernel/kernel.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { assert, Fail } from '@endo/errors';
|
|
4
4
|
import { isNat } from '@endo/nat';
|
|
5
|
+
import { mustMatch, M } from '@endo/patterns';
|
|
5
6
|
import { importBundle } from '@endo/import-bundle';
|
|
6
7
|
import { makeUpgradeDisconnection } from '@agoric/internal/src/upgrade-api.js';
|
|
7
8
|
import { kser, kslot, makeError } from '@agoric/kmarshal';
|
|
@@ -275,12 +276,16 @@ export default function buildKernel(
|
|
|
275
276
|
// (#9157). The fix will add .critical to CrankResults, populated by a
|
|
276
277
|
// getOptions query in deliveryCrankResults() or copied from
|
|
277
278
|
// dynamicOptions in processCreateVat.
|
|
278
|
-
|
|
279
|
+
const vatKeeper = kernelKeeper.provideVatKeeper(vatID);
|
|
280
|
+
critical = vatKeeper.getOptions().critical;
|
|
279
281
|
|
|
280
282
|
// Reject all promises decided by the vat, making sure to capture the list
|
|
281
283
|
// of kpids before that data is deleted.
|
|
282
284
|
const deadPromises = [...kernelKeeper.enumeratePromisesByDecider(vatID)];
|
|
283
|
-
|
|
285
|
+
// remove vatID from the list of live vats, and mark for deletion
|
|
286
|
+
kernelKeeper.deleteVatID(vatID);
|
|
287
|
+
kernelKeeper.markVatAsTerminated(vatID);
|
|
288
|
+
kernelKeeper.removeVatFromSwingStoreExports(vatID);
|
|
284
289
|
for (const kpid of deadPromises) {
|
|
285
290
|
resolveToError(kpid, makeError('vat terminated'), vatID);
|
|
286
291
|
}
|
|
@@ -387,7 +392,8 @@ export default function buildKernel(
|
|
|
387
392
|
* abort?: boolean, // changes should be discarded, not committed
|
|
388
393
|
* consumeMessage?: boolean, // discard the aborted delivery
|
|
389
394
|
* didDelivery?: VatID, // we made a delivery to a vat, for run policy and save-snapshot
|
|
390
|
-
* computrons?:
|
|
395
|
+
* computrons?: bigint, // computron count for run policy
|
|
396
|
+
* cleanups?: number, // cleanup budget spent
|
|
391
397
|
* meterID?: string, // deduct those computrons from a meter
|
|
392
398
|
* measureDirt?: { vatID: VatID, dirt: Dirt }, // dirt counters should increment
|
|
393
399
|
* terminate?: { vatID: VatID, reject: boolean, info: SwingSetCapData }, // terminate vat, notify vat-admin
|
|
@@ -651,16 +657,40 @@ export default function buildKernel(
|
|
|
651
657
|
if (!vatWarehouse.lookup(vatID)) {
|
|
652
658
|
return NO_DELIVERY_CRANK_RESULTS; // can't collect from the dead
|
|
653
659
|
}
|
|
654
|
-
const vatKeeper = kernelKeeper.provideVatKeeper(vatID);
|
|
655
660
|
/** @type { KernelDeliveryBringOutYourDead } */
|
|
656
661
|
const kd = harden([type]);
|
|
657
662
|
const vd = vatWarehouse.kernelDeliveryToVatDelivery(vatID, kd);
|
|
658
663
|
const status = await deliverAndLogToVat(vatID, kd, vd);
|
|
659
|
-
vatKeeper.clearReapDirt(); // BOYD zeros out the when-to-BOYD counters
|
|
660
664
|
// no gcKrefs, BOYD clears them anyways
|
|
661
665
|
return deliveryCrankResults(vatID, status, false); // no meter, BOYD clears dirt
|
|
662
666
|
}
|
|
663
667
|
|
|
668
|
+
/**
|
|
669
|
+
* Perform a small (budget-limited) amount of dead-vat cleanup work.
|
|
670
|
+
*
|
|
671
|
+
* @param {RunQueueEventCleanupTerminatedVat} message
|
|
672
|
+
* 'message' is the run-queue cleanup action, which includes a vatID and budget.
|
|
673
|
+
* A budget of 'undefined' allows unlimited work. Otherwise, the budget is a Number,
|
|
674
|
+
* and cleanup should not touch more than maybe 5*budget DB rows.
|
|
675
|
+
* @returns {Promise<CrankResults>}
|
|
676
|
+
*/
|
|
677
|
+
async function processCleanupTerminatedVat(message) {
|
|
678
|
+
const { vatID, budget } = message;
|
|
679
|
+
const { done, cleanups } = kernelKeeper.cleanupAfterTerminatedVat(
|
|
680
|
+
vatID,
|
|
681
|
+
budget,
|
|
682
|
+
);
|
|
683
|
+
if (done) {
|
|
684
|
+
kernelKeeper.forgetTerminatedVat(vatID);
|
|
685
|
+
kernelSlog.write({ type: 'vat-cleanup-complete', vatID });
|
|
686
|
+
}
|
|
687
|
+
// We don't perform any deliveries here, so there are no computrons to
|
|
688
|
+
// report, but we do tell the runPolicy know how much kernel-side DB
|
|
689
|
+
// work we did, so it can decide how much was too much.
|
|
690
|
+
const computrons = 0n;
|
|
691
|
+
return harden({ computrons, cleanups });
|
|
692
|
+
}
|
|
693
|
+
|
|
664
694
|
/**
|
|
665
695
|
* The 'startVat' event is queued by `initializeKernel` for all static vats,
|
|
666
696
|
* so that we execute their bundle imports and call their `buildRootObject`
|
|
@@ -916,7 +946,6 @@ export default function buildKernel(
|
|
|
916
946
|
const boydVD = vatWarehouse.kernelDeliveryToVatDelivery(vatID, boydKD);
|
|
917
947
|
const boydStatus = await deliverAndLogToVat(vatID, boydKD, boydVD);
|
|
918
948
|
const boydResults = deliveryCrankResults(vatID, boydStatus, false);
|
|
919
|
-
vatKeeper.clearReapDirt();
|
|
920
949
|
|
|
921
950
|
// we don't meter bringOutYourDead since no user code is running, but we
|
|
922
951
|
// still report computrons to the runPolicy
|
|
@@ -1172,6 +1201,7 @@ export default function buildKernel(
|
|
|
1172
1201
|
* @typedef { import('../types-internal.js').RunQueueEventRetireImports } RunQueueEventRetireImports
|
|
1173
1202
|
* @typedef { import('../types-internal.js').RunQueueEventNegatedGCAction } RunQueueEventNegatedGCAction
|
|
1174
1203
|
* @typedef { import('../types-internal.js').RunQueueEventBringOutYourDead } RunQueueEventBringOutYourDead
|
|
1204
|
+
* @typedef { import('../types-internal.js').RunQueueEventCleanupTerminatedVat } RunQueueEventCleanupTerminatedVat
|
|
1175
1205
|
* @typedef { import('../types-internal.js').RunQueueEvent } RunQueueEvent
|
|
1176
1206
|
*/
|
|
1177
1207
|
|
|
@@ -1239,6 +1269,8 @@ export default function buildKernel(
|
|
|
1239
1269
|
} else if (message.type === 'negated-gc-action') {
|
|
1240
1270
|
// processGCActionSet pruned some negated actions, but had no GC
|
|
1241
1271
|
// action to perform. Record the DB changes in their own crank.
|
|
1272
|
+
} else if (message.type === 'cleanup-terminated-vat') {
|
|
1273
|
+
deliverP = processCleanupTerminatedVat(message);
|
|
1242
1274
|
} else if (gcMessages.includes(message.type)) {
|
|
1243
1275
|
deliverP = processGCMessage(message);
|
|
1244
1276
|
} else {
|
|
@@ -1289,18 +1321,13 @@ export default function buildKernel(
|
|
|
1289
1321
|
const crankResults = await deliverRunQueueEvent(message);
|
|
1290
1322
|
// { abort/commit, deduct, terminate+notify, consumeMessage }
|
|
1291
1323
|
|
|
1292
|
-
if (
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
// which should not affect the in-consensus policyInput)
|
|
1300
|
-
policyInput = ['create-vat', {}];
|
|
1301
|
-
} else {
|
|
1302
|
-
policyInput = ['crank', {}];
|
|
1303
|
-
}
|
|
1324
|
+
if (message.type === 'cleanup-terminated-vat') {
|
|
1325
|
+
const { cleanups } = crankResults;
|
|
1326
|
+
assert(cleanups !== undefined);
|
|
1327
|
+
policyInput = ['cleanup', { cleanups }];
|
|
1328
|
+
} else if (crankResults.didDelivery) {
|
|
1329
|
+
const tag = message.type === 'create-vat' ? 'create-vat' : 'crank';
|
|
1330
|
+
policyInput = [tag, {}];
|
|
1304
1331
|
}
|
|
1305
1332
|
|
|
1306
1333
|
// Deliveries cause syscalls, syscalls might cause errors
|
|
@@ -1758,16 +1785,24 @@ export default function buildKernel(
|
|
|
1758
1785
|
}
|
|
1759
1786
|
}
|
|
1760
1787
|
|
|
1788
|
+
const allowCleanupShape = M.or(
|
|
1789
|
+
// 'false' will prohibit cleanup
|
|
1790
|
+
false,
|
|
1791
|
+
// otherwise allow cleanup, optionally with a limiting budget
|
|
1792
|
+
M.splitRecord({}, { budget: M.number() }, M.record()),
|
|
1793
|
+
);
|
|
1794
|
+
|
|
1761
1795
|
/**
|
|
1762
1796
|
* Pulls the next message from the highest-priority queue and returns it
|
|
1763
1797
|
* along with a corresponding processor.
|
|
1764
1798
|
*
|
|
1799
|
+
* @param {RunPolicy} [policy] - a RunPolicy to limit the work being done
|
|
1765
1800
|
* @returns {{
|
|
1766
1801
|
* message: RunQueueEvent | undefined,
|
|
1767
1802
|
* processor: (message: RunQueueEvent) => Promise<PolicyInput>,
|
|
1768
1803
|
* }}
|
|
1769
1804
|
*/
|
|
1770
|
-
function getNextMessageAndProcessor() {
|
|
1805
|
+
function getNextMessageAndProcessor(policy) {
|
|
1771
1806
|
const acceptanceMessage = kernelKeeper.getNextAcceptanceQueueMsg();
|
|
1772
1807
|
if (acceptanceMessage) {
|
|
1773
1808
|
return {
|
|
@@ -1775,7 +1810,12 @@ export default function buildKernel(
|
|
|
1775
1810
|
processor: processAcceptanceMessage,
|
|
1776
1811
|
};
|
|
1777
1812
|
}
|
|
1813
|
+
// Absent specific configuration, allow unlimited cleanup.
|
|
1814
|
+
const allowCleanup = policy?.allowCleanup?.() ?? {};
|
|
1815
|
+
mustMatch(harden(allowCleanup), allowCleanupShape);
|
|
1816
|
+
|
|
1778
1817
|
const message =
|
|
1818
|
+
kernelKeeper.nextCleanupTerminatedVatAction(allowCleanup) ||
|
|
1779
1819
|
processGCActionSet(kernelKeeper) ||
|
|
1780
1820
|
kernelKeeper.nextReapAction() ||
|
|
1781
1821
|
kernelKeeper.getNextRunQueueMsg();
|
|
@@ -1885,7 +1925,7 @@ export default function buildKernel(
|
|
|
1885
1925
|
kernelKeeper.startCrank();
|
|
1886
1926
|
try {
|
|
1887
1927
|
kernelKeeper.establishCrankSavepoint('start');
|
|
1888
|
-
const { processor, message } = getNextMessageAndProcessor();
|
|
1928
|
+
const { processor, message } = getNextMessageAndProcessor(policy);
|
|
1889
1929
|
if (!message) {
|
|
1890
1930
|
break;
|
|
1891
1931
|
}
|
|
@@ -1907,6 +1947,13 @@ export default function buildKernel(
|
|
|
1907
1947
|
case 'crank-failed':
|
|
1908
1948
|
policyOutput = policy.crankFailed(policyInput[1]);
|
|
1909
1949
|
break;
|
|
1950
|
+
case 'cleanup': {
|
|
1951
|
+
// Give the policy a chance to interrupt kernel execution,
|
|
1952
|
+
// but default to continuing.
|
|
1953
|
+
const { didCleanup } = policy;
|
|
1954
|
+
policyOutput = didCleanup ? didCleanup(policyInput[1]) : true;
|
|
1955
|
+
break;
|
|
1956
|
+
}
|
|
1910
1957
|
case 'none':
|
|
1911
1958
|
policyOutput = policy.emptyCrank();
|
|
1912
1959
|
break;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/* eslint-disable no-use-before-define */
|
|
2
2
|
import { Nat, isNat } from '@endo/nat';
|
|
3
3
|
import { assert, Fail } from '@endo/errors';
|
|
4
|
+
import { objectMetaMap } from '@agoric/internal';
|
|
4
5
|
import {
|
|
5
6
|
initializeVatState,
|
|
6
7
|
makeVatKeeper,
|
|
@@ -50,7 +51,7 @@ const enableKernelGC = true;
|
|
|
50
51
|
export { DEFAULT_REAP_DIRT_THRESHOLD_KEY };
|
|
51
52
|
|
|
52
53
|
// most recent DB schema version
|
|
53
|
-
export const CURRENT_SCHEMA_VERSION =
|
|
54
|
+
export const CURRENT_SCHEMA_VERSION = 2;
|
|
54
55
|
|
|
55
56
|
// Kernel state lives in a key-value store supporting key retrieval by
|
|
56
57
|
// lexicographic range. All keys and values are strings.
|
|
@@ -69,14 +70,15 @@ export const CURRENT_SCHEMA_VERSION = 1;
|
|
|
69
70
|
// only modified by a call to upgradeSwingset(). See below for
|
|
70
71
|
// deltas/upgrades from one version to the next.
|
|
71
72
|
//
|
|
72
|
-
// The current ("
|
|
73
|
+
// The current ("v2") schema keys/values are:
|
|
73
74
|
//
|
|
74
|
-
// version = '
|
|
75
|
+
// version = '2'
|
|
75
76
|
// vat.names = JSON([names..])
|
|
76
77
|
// vat.dynamicIDs = JSON([vatIDs..])
|
|
77
78
|
// vat.name.$NAME = $vatID = v$NN
|
|
78
79
|
// vat.nextID = $NN
|
|
79
80
|
// vat.nextUpgradeID = $NN
|
|
81
|
+
// vats.terminated = JSON([vatIDs..])
|
|
80
82
|
// device.names = JSON([names..])
|
|
81
83
|
// device.name.$NAME = $deviceID = d$NN
|
|
82
84
|
// device.nextID = $NN
|
|
@@ -172,6 +174,9 @@ export const CURRENT_SCHEMA_VERSION = 1;
|
|
|
172
174
|
// * replace `kernel.defaultReapInterval` with `kernel.defaultReapDirtThreshold`
|
|
173
175
|
// * replace vat's `vNN.reapInterval`/`vNN.reapCountdown` with `vNN.reapDirt`
|
|
174
176
|
// and a `vNN.reapDirtThreshold` in `vNN.options`
|
|
177
|
+
// v2:
|
|
178
|
+
// * change `version` to `'2'`
|
|
179
|
+
// * add `vats.terminated` with `[]` as initial value
|
|
175
180
|
|
|
176
181
|
export function commaSplit(s) {
|
|
177
182
|
if (s === '') {
|
|
@@ -180,6 +185,11 @@ export function commaSplit(s) {
|
|
|
180
185
|
return s.split(',');
|
|
181
186
|
}
|
|
182
187
|
|
|
188
|
+
export function stripPrefix(prefix, str) {
|
|
189
|
+
assert(str.startsWith(prefix), str);
|
|
190
|
+
return str.slice(prefix.length);
|
|
191
|
+
}
|
|
192
|
+
|
|
183
193
|
function insistMeterID(m) {
|
|
184
194
|
assert.typeof(m, 'string');
|
|
185
195
|
assert.equal(m[0], 'm');
|
|
@@ -248,6 +258,12 @@ export default function makeKernelKeeper(
|
|
|
248
258
|
|
|
249
259
|
insistStorageAPI(kvStore);
|
|
250
260
|
|
|
261
|
+
// the terminated-vats cache is normally populated from
|
|
262
|
+
// 'vats.terminated', but for initialization purposes we need give
|
|
263
|
+
// it a value here, and then populate it for real if we're dealing
|
|
264
|
+
// with an up-to-date DB
|
|
265
|
+
let terminatedVats = [];
|
|
266
|
+
|
|
251
267
|
const versionString = kvStore.get('version');
|
|
252
268
|
const version = Number(versionString || '0');
|
|
253
269
|
if (expectedVersion === 'uninitialized') {
|
|
@@ -261,6 +277,9 @@ export default function makeKernelKeeper(
|
|
|
261
277
|
throw Error(
|
|
262
278
|
`kernel DB is too old: has version v${version}, but expected v${expectedVersion}`,
|
|
263
279
|
);
|
|
280
|
+
} else {
|
|
281
|
+
// DB is up-to-date, so populate any caches we use
|
|
282
|
+
terminatedVats = JSON.parse(getRequired('vats.terminated'));
|
|
264
283
|
}
|
|
265
284
|
|
|
266
285
|
/**
|
|
@@ -399,6 +418,7 @@ export default function makeKernelKeeper(
|
|
|
399
418
|
kvStore.set('vat.dynamicIDs', '[]');
|
|
400
419
|
kvStore.set('vat.nextID', `${FIRST_VAT_ID}`);
|
|
401
420
|
kvStore.set('vat.nextUpgradeID', `1`);
|
|
421
|
+
kvStore.set('vats.terminated', '[]');
|
|
402
422
|
kvStore.set('device.names', '[]');
|
|
403
423
|
kvStore.set('device.nextID', `${FIRST_DEVICE_ID}`);
|
|
404
424
|
kvStore.set('ko.nextID', `${FIRST_OBJECT_ID}`);
|
|
@@ -679,8 +699,12 @@ export default function makeKernelKeeper(
|
|
|
679
699
|
function ownerOfKernelObject(kernelSlot) {
|
|
680
700
|
insistKernelType('object', kernelSlot);
|
|
681
701
|
const owner = kvStore.get(`${kernelSlot}.owner`);
|
|
682
|
-
if (owner) {
|
|
683
|
-
|
|
702
|
+
if (!owner) {
|
|
703
|
+
return undefined;
|
|
704
|
+
}
|
|
705
|
+
insistVatID(owner);
|
|
706
|
+
if (terminatedVats.includes(owner)) {
|
|
707
|
+
return undefined;
|
|
684
708
|
}
|
|
685
709
|
return owner;
|
|
686
710
|
}
|
|
@@ -885,13 +909,70 @@ export default function makeKernelKeeper(
|
|
|
885
909
|
kvStore.set(`${kernelSlot}.data.slots`, capdata.slots.join(','));
|
|
886
910
|
}
|
|
887
911
|
|
|
888
|
-
function
|
|
912
|
+
function removeVatFromSwingStoreExports(vatID) {
|
|
913
|
+
// Delete primary swingstore records for this vat, in preparation
|
|
914
|
+
// for (slow) deletion. After this, swingstore exports will omit
|
|
915
|
+
// this vat. This is called from the kernel's terminateVat, which
|
|
916
|
+
// initiates (but does not complete) deletion.
|
|
917
|
+
snapStore.stopUsingLastSnapshot(vatID);
|
|
918
|
+
transcriptStore.stopUsingTranscript(vatID);
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
/**
|
|
922
|
+
* Perform some cleanup work for a specific (terminated but not
|
|
923
|
+
* fully-deleted) vat, possibly limited by a budget. Returns 'done'
|
|
924
|
+
* (where false means "please call me again", and true means "you
|
|
925
|
+
* can delete the vatID now"), and a count of how much work was done
|
|
926
|
+
* (so the runPolicy can decide when to stop).
|
|
927
|
+
*
|
|
928
|
+
* @param {string} vatID
|
|
929
|
+
* @param {number} [budget]
|
|
930
|
+
* @returns {{ done: boolean, cleanups: number }}
|
|
931
|
+
*
|
|
932
|
+
*/
|
|
933
|
+
function cleanupAfterTerminatedVat(vatID, budget = undefined) {
|
|
889
934
|
insistVatID(vatID);
|
|
890
|
-
|
|
891
|
-
const
|
|
892
|
-
|
|
935
|
+
let cleanups = 0;
|
|
936
|
+
const work = {
|
|
937
|
+
exports: 0,
|
|
938
|
+
imports: 0,
|
|
939
|
+
kv: 0,
|
|
940
|
+
snapshots: 0,
|
|
941
|
+
transcripts: 0,
|
|
942
|
+
};
|
|
943
|
+
let spend = _did => false; // returns "stop now"
|
|
944
|
+
if (budget !== undefined) {
|
|
945
|
+
assert.typeof(budget, 'number');
|
|
946
|
+
spend = (did = 1) => {
|
|
947
|
+
assert(budget !== undefined); // hush TSC
|
|
948
|
+
cleanups += did;
|
|
949
|
+
budget -= did;
|
|
950
|
+
return budget <= 0;
|
|
951
|
+
};
|
|
952
|
+
}
|
|
953
|
+
const logWork = () => {
|
|
954
|
+
const w = objectMetaMap(work, desc => (desc.value ? desc : undefined));
|
|
955
|
+
kernelSlog?.write({ type: 'vat-cleanup', vatID, work: w });
|
|
956
|
+
};
|
|
957
|
+
|
|
958
|
+
// TODO: it would be slightly cheaper to walk all kvStore keys in
|
|
959
|
+
// order, and act on each one according to its category (c-list
|
|
960
|
+
// export, c-list import, vatstore, other), so we use a single
|
|
961
|
+
// enumeratePrefixedKeys() call each time. Until we do that, the
|
|
962
|
+
// last phase of the cleanup (where we've deleted all the exports
|
|
963
|
+
// and imports, and are working on the remaining keys) will waste
|
|
964
|
+
// two DB queries on each call. OTOH, those queries will probably
|
|
965
|
+
// hit the same SQLite index page as the successful one, so it
|
|
966
|
+
// probably won't cause any extra disk IO. So we can defer this
|
|
967
|
+
// optimization for a while. Note: when we implement it, be
|
|
968
|
+
// prepared to encounter the clist entries in eiher order (kref
|
|
969
|
+
// first or vref first), and delete the other one in the same
|
|
970
|
+
// call, so we don't wind up with half an entry.
|
|
893
971
|
|
|
894
|
-
vatKeeper
|
|
972
|
+
const vatKeeper = provideVatKeeper(vatID);
|
|
973
|
+
const clistPrefix = `${vatID}.c.`;
|
|
974
|
+
const exportPrefix = `${clistPrefix}o+`;
|
|
975
|
+
const importPrefix = `${clistPrefix}o-`;
|
|
895
976
|
|
|
896
977
|
// Note: ASCII order is "+,-./", and we rely upon this to split the
|
|
897
978
|
// keyspace into the various o+NN/o-NN/etc spaces. If we were using a
|
|
@@ -915,8 +996,27 @@ export default function makeKernelKeeper(
|
|
|
915
996
|
// begin with `vMM.c.o+`. In addition to deleting the c-list entry, we
|
|
916
997
|
// must also delete the corresponding kernel owner entry for the object,
|
|
917
998
|
// since the object will no longer be accessible.
|
|
999
|
+
const vref = stripPrefix(clistPrefix, k);
|
|
1000
|
+
assert(vref.startsWith('o+'), vref);
|
|
918
1001
|
const kref = kvStore.get(k);
|
|
919
|
-
|
|
1002
|
+
// we must delete the c-list entry, but we don't need to
|
|
1003
|
+
// manipulate refcounts like the way vatKeeper/deleteCListEntry
|
|
1004
|
+
// does, so just delete the keys directly
|
|
1005
|
+
// vatKeeper.deleteCListEntry(kref, vref);
|
|
1006
|
+
kvStore.delete(`${vatID}.c.${kref}`);
|
|
1007
|
+
kvStore.delete(`${vatID}.c.${vref}`);
|
|
1008
|
+
// if this object became unreferenced, processRefcounts will
|
|
1009
|
+
// delete it, so don't be surprised. TODO: this results in an
|
|
1010
|
+
// extra get() for each delete(), see if there's a way to avoid
|
|
1011
|
+
// this. TODO: think carefully about other such races.
|
|
1012
|
+
if (kvStore.has(`${kref}.owner`)) {
|
|
1013
|
+
orphanKernelObject(kref, vatID);
|
|
1014
|
+
}
|
|
1015
|
+
work.exports += 1;
|
|
1016
|
+
if (spend()) {
|
|
1017
|
+
logWork();
|
|
1018
|
+
return { done: false, cleanups };
|
|
1019
|
+
}
|
|
920
1020
|
}
|
|
921
1021
|
|
|
922
1022
|
// then scan for imported objects, which must be decrefed
|
|
@@ -924,9 +1024,14 @@ export default function makeKernelKeeper(
|
|
|
924
1024
|
// abandoned imports: delete the clist entry as if the vat did a
|
|
925
1025
|
// drop+retire
|
|
926
1026
|
const kref = kvStore.get(k) || Fail`getNextKey ensures get`;
|
|
927
|
-
const vref = k
|
|
1027
|
+
const vref = stripPrefix(clistPrefix, k);
|
|
928
1028
|
vatKeeper.deleteCListEntry(kref, vref);
|
|
929
1029
|
// that will also delete both db keys
|
|
1030
|
+
work.imports += 1;
|
|
1031
|
+
if (spend()) {
|
|
1032
|
+
logWork();
|
|
1033
|
+
return { done: false, cleanups };
|
|
1034
|
+
}
|
|
930
1035
|
}
|
|
931
1036
|
|
|
932
1037
|
// the caller used enumeratePromisesByDecider() before calling us,
|
|
@@ -935,8 +1040,31 @@ export default function makeKernelKeeper(
|
|
|
935
1040
|
// now loop back through everything and delete it all
|
|
936
1041
|
for (const k of enumeratePrefixedKeys(kvStore, `${vatID}.`)) {
|
|
937
1042
|
kvStore.delete(k);
|
|
1043
|
+
work.kv += 1;
|
|
1044
|
+
if (spend()) {
|
|
1045
|
+
logWork();
|
|
1046
|
+
return { done: false, cleanups };
|
|
1047
|
+
}
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
// this will internally loop through 'budget' deletions
|
|
1051
|
+
const dsc = vatKeeper.deleteSnapshots(budget);
|
|
1052
|
+
work.snapshots += dsc.cleanups;
|
|
1053
|
+
if (spend(dsc.cleanups)) {
|
|
1054
|
+
logWork();
|
|
1055
|
+
return { done: false, cleanups };
|
|
938
1056
|
}
|
|
939
1057
|
|
|
1058
|
+
// same
|
|
1059
|
+
const dts = vatKeeper.deleteTranscripts(budget);
|
|
1060
|
+
work.transcripts += dts.cleanups;
|
|
1061
|
+
// last task, so increment cleanups, but dc.done is authoritative
|
|
1062
|
+
spend(dts.cleanups);
|
|
1063
|
+
logWork();
|
|
1064
|
+
return { done: dts.done, cleanups };
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
function deleteVatID(vatID) {
|
|
940
1068
|
// TODO: deleting entries from the dynamic vat IDs list requires a linear
|
|
941
1069
|
// scan of the list; arguably this collection ought to be represented in a
|
|
942
1070
|
// different way that makes it efficient to remove an entry from it, though
|
|
@@ -1221,6 +1349,41 @@ export default function makeKernelKeeper(
|
|
|
1221
1349
|
return makeUpgradeID(nextID);
|
|
1222
1350
|
}
|
|
1223
1351
|
|
|
1352
|
+
function markVatAsTerminated(vatID) {
|
|
1353
|
+
if (!terminatedVats.includes(vatID)) {
|
|
1354
|
+
terminatedVats.push(vatID);
|
|
1355
|
+
kvStore.set(`vats.terminated`, JSON.stringify(terminatedVats));
|
|
1356
|
+
}
|
|
1357
|
+
}
|
|
1358
|
+
|
|
1359
|
+
function getFirstTerminatedVat() {
|
|
1360
|
+
if (terminatedVats.length) {
|
|
1361
|
+
return terminatedVats[0];
|
|
1362
|
+
}
|
|
1363
|
+
return undefined;
|
|
1364
|
+
}
|
|
1365
|
+
|
|
1366
|
+
function forgetTerminatedVat(vatID) {
|
|
1367
|
+
terminatedVats = terminatedVats.filter(id => id !== vatID);
|
|
1368
|
+
kvStore.set(`vats.terminated`, JSON.stringify(terminatedVats));
|
|
1369
|
+
}
|
|
1370
|
+
|
|
1371
|
+
function nextCleanupTerminatedVatAction(allowCleanup) {
|
|
1372
|
+
if (!allowCleanup) {
|
|
1373
|
+
return undefined;
|
|
1374
|
+
}
|
|
1375
|
+
// budget === undefined means "unlimited"
|
|
1376
|
+
const { budget } = allowCleanup;
|
|
1377
|
+
// if (getGCActions().size) {
|
|
1378
|
+
// return undefined;
|
|
1379
|
+
// }
|
|
1380
|
+
const vatID = getFirstTerminatedVat();
|
|
1381
|
+
if (vatID) {
|
|
1382
|
+
return { type: 'cleanup-terminated-vat', vatID, budget };
|
|
1383
|
+
}
|
|
1384
|
+
return undefined;
|
|
1385
|
+
}
|
|
1386
|
+
|
|
1224
1387
|
// As refcounts are decremented, we accumulate a set of krefs for which
|
|
1225
1388
|
// action might need to be taken:
|
|
1226
1389
|
// * promises which are now resolved and unreferenced can be deleted
|
|
@@ -1413,7 +1576,7 @@ export default function makeKernelKeeper(
|
|
|
1413
1576
|
|
|
1414
1577
|
function vatIsAlive(vatID) {
|
|
1415
1578
|
insistVatID(vatID);
|
|
1416
|
-
return kvStore.has(`${vatID}.o.nextID`);
|
|
1579
|
+
return kvStore.has(`${vatID}.o.nextID`) && !terminatedVats.includes(vatID);
|
|
1417
1580
|
}
|
|
1418
1581
|
|
|
1419
1582
|
/**
|
|
@@ -1705,11 +1868,18 @@ export default function makeKernelKeeper(
|
|
|
1705
1868
|
provideVatKeeper,
|
|
1706
1869
|
vatIsAlive,
|
|
1707
1870
|
evictVatKeeper,
|
|
1871
|
+
removeVatFromSwingStoreExports,
|
|
1708
1872
|
cleanupAfterTerminatedVat,
|
|
1709
1873
|
addDynamicVatID,
|
|
1710
1874
|
getDynamicVats,
|
|
1711
1875
|
getStaticVats,
|
|
1712
1876
|
getDevices,
|
|
1877
|
+
deleteVatID,
|
|
1878
|
+
|
|
1879
|
+
markVatAsTerminated,
|
|
1880
|
+
getFirstTerminatedVat,
|
|
1881
|
+
forgetTerminatedVat,
|
|
1882
|
+
nextCleanupTerminatedVatAction,
|
|
1713
1883
|
|
|
1714
1884
|
allocateUpgradeID,
|
|
1715
1885
|
|
|
@@ -675,11 +675,44 @@ export function makeVatKeeper(
|
|
|
675
675
|
});
|
|
676
676
|
}
|
|
677
677
|
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
678
|
+
/**
|
|
679
|
+
* Perform some (possibly-limited) cleanup work for a vat. Returns
|
|
680
|
+
* 'done' (where false means "please call me again", and true means
|
|
681
|
+
* "you can delete the vatID now"), and a count of how much work was
|
|
682
|
+
* done (so the runPolicy can decide when to stop).
|
|
683
|
+
*
|
|
684
|
+
* @param {number} [budget]
|
|
685
|
+
* @returns {{ done: boolean, cleanups: number }}
|
|
686
|
+
*
|
|
687
|
+
*/
|
|
688
|
+
function deleteSnapshots(budget = undefined) {
|
|
689
|
+
// Each budget=1 allows us to delete one snapshot entry.
|
|
690
|
+
if (!snapStore) {
|
|
691
|
+
return { done: true, cleanups: 0 };
|
|
681
692
|
}
|
|
682
|
-
|
|
693
|
+
// initially uses 2+2*budget DB statements, then just 1 when done
|
|
694
|
+
return snapStore.deleteVatSnapshots(vatID, budget);
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
/**
|
|
698
|
+
* Perform some (possibly-limited) cleanup work for a vat. Returns
|
|
699
|
+
* 'done' (where false means "please call me again", and true means
|
|
700
|
+
* "you can delete the vatID now"), and a count of how much work was
|
|
701
|
+
* done (so the runPolicy can decide when to stop).
|
|
702
|
+
*
|
|
703
|
+
* @param {number} [budget]
|
|
704
|
+
* @returns {{ done: boolean, cleanups: number }}
|
|
705
|
+
*
|
|
706
|
+
*/
|
|
707
|
+
function deleteTranscripts(budget = undefined) {
|
|
708
|
+
// Each budget=1 allows us to delete one transcript span and any
|
|
709
|
+
// transcript items associated with that span. Some nodes will
|
|
710
|
+
// have historical transcript items, some will not. Using budget=5
|
|
711
|
+
// and snapshotInterval=200 means we delete 5 span records and
|
|
712
|
+
// maybe 1000 span items.
|
|
713
|
+
|
|
714
|
+
// initially uses 2+3*budget DB statements, then just 1 when done
|
|
715
|
+
return transcriptStore.deleteVatTranscripts(vatID, budget);
|
|
683
716
|
}
|
|
684
717
|
|
|
685
718
|
function beginNewIncarnation() {
|
|
@@ -761,7 +794,8 @@ export function makeVatKeeper(
|
|
|
761
794
|
dumpState,
|
|
762
795
|
saveSnapshot,
|
|
763
796
|
getSnapshotInfo,
|
|
764
|
-
|
|
797
|
+
deleteSnapshots,
|
|
798
|
+
deleteTranscripts,
|
|
765
799
|
beginNewIncarnation,
|
|
766
800
|
});
|
|
767
801
|
}
|
|
@@ -544,6 +544,10 @@ export function makeVatWarehouse({
|
|
|
544
544
|
vatKeeper.addToTranscript(getTranscriptEntry(vd, deliveryResult));
|
|
545
545
|
}
|
|
546
546
|
|
|
547
|
+
if (kd[0] === 'bringOutYourDead') {
|
|
548
|
+
vatKeeper.clearReapDirt(); // BOYD zeros out the when-to-BOYD counters
|
|
549
|
+
}
|
|
550
|
+
|
|
547
551
|
// TODO: if per-vat policy decides it wants a BOYD or heap snapshot,
|
|
548
552
|
// now is the time to do it, or to ask the kernel to schedule it
|
|
549
553
|
|
package/src/lib/runPolicies.js
CHANGED
|
@@ -3,6 +3,9 @@ import { assert } from '@endo/errors';
|
|
|
3
3
|
export function foreverPolicy() {
|
|
4
4
|
/** @type { RunPolicy } */
|
|
5
5
|
return harden({
|
|
6
|
+
allowCleanup() {
|
|
7
|
+
return {}; // unlimited budget
|
|
8
|
+
},
|
|
6
9
|
vatCreated(_details) {
|
|
7
10
|
return true;
|
|
8
11
|
},
|
|
@@ -27,6 +30,9 @@ export function crankCounter(
|
|
|
27
30
|
let vats = 0;
|
|
28
31
|
/** @type { RunPolicy } */
|
|
29
32
|
const policy = harden({
|
|
33
|
+
allowCleanup() {
|
|
34
|
+
return { budget: 100 }; // limited budget
|
|
35
|
+
},
|
|
30
36
|
vatCreated() {
|
|
31
37
|
vats += 1;
|
|
32
38
|
return vats < maxCreateVats;
|
|
@@ -47,13 +53,21 @@ export function crankCounter(
|
|
|
47
53
|
return policy;
|
|
48
54
|
}
|
|
49
55
|
|
|
50
|
-
export function computronCounter(limit) {
|
|
56
|
+
export function computronCounter(limit, options = {}) {
|
|
51
57
|
assert.typeof(limit, 'bigint');
|
|
58
|
+
const {
|
|
59
|
+
cleanupBudget = 100,
|
|
60
|
+
vatCreatedComputrons = 100_000n, // pretend that's the cost
|
|
61
|
+
crankFailedComputrons = 1_000_000n,
|
|
62
|
+
} = options;
|
|
52
63
|
let total = 0n;
|
|
53
64
|
/** @type { RunPolicy } */
|
|
54
65
|
const policy = harden({
|
|
66
|
+
allowCleanup() {
|
|
67
|
+
return { budget: cleanupBudget }; // limited budget
|
|
68
|
+
},
|
|
55
69
|
vatCreated() {
|
|
56
|
-
total +=
|
|
70
|
+
total += vatCreatedComputrons;
|
|
57
71
|
return total < limit;
|
|
58
72
|
},
|
|
59
73
|
crankComplete(details = {}) {
|
|
@@ -65,7 +79,7 @@ export function computronCounter(limit) {
|
|
|
65
79
|
return total < limit;
|
|
66
80
|
},
|
|
67
81
|
crankFailed() {
|
|
68
|
-
total +=
|
|
82
|
+
total += crankFailedComputrons;
|
|
69
83
|
return total < limit;
|
|
70
84
|
},
|
|
71
85
|
emptyCrank() {
|
|
@@ -79,6 +93,7 @@ export function wallClockWaiter(seconds) {
|
|
|
79
93
|
const timeout = Date.now() + 1000 * seconds;
|
|
80
94
|
/** @type { RunPolicy } */
|
|
81
95
|
const policy = harden({
|
|
96
|
+
allowCleanup: () => ({}), // unlimited budget
|
|
82
97
|
vatCreated: () => Date.now() < timeout,
|
|
83
98
|
crankComplete: () => Date.now() < timeout,
|
|
84
99
|
crankFailed: () => Date.now() < timeout,
|
|
@@ -86,3 +101,34 @@ export function wallClockWaiter(seconds) {
|
|
|
86
101
|
});
|
|
87
102
|
return policy;
|
|
88
103
|
}
|
|
104
|
+
|
|
105
|
+
export function noCleanup() {
|
|
106
|
+
/** @type { RunPolicy } */
|
|
107
|
+
const policy = harden({
|
|
108
|
+
allowCleanup: () => false,
|
|
109
|
+
vatCreated: () => true,
|
|
110
|
+
crankComplete: () => true,
|
|
111
|
+
crankFailed: () => true,
|
|
112
|
+
emptyCrank: () => true,
|
|
113
|
+
});
|
|
114
|
+
return policy;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export function someCleanup(budget) {
|
|
118
|
+
let once = true;
|
|
119
|
+
/** @type { RunPolicy } */
|
|
120
|
+
const policy = harden({
|
|
121
|
+
allowCleanup: () => {
|
|
122
|
+
if (once) {
|
|
123
|
+
once = false;
|
|
124
|
+
return { budget };
|
|
125
|
+
}
|
|
126
|
+
return false;
|
|
127
|
+
},
|
|
128
|
+
vatCreated: () => true,
|
|
129
|
+
crankComplete: () => true,
|
|
130
|
+
crankFailed: () => true,
|
|
131
|
+
emptyCrank: () => true,
|
|
132
|
+
});
|
|
133
|
+
return policy;
|
|
134
|
+
}
|
package/src/types-external.js
CHANGED
|
@@ -224,12 +224,17 @@ export {};
|
|
|
224
224
|
* @typedef { [tag: 'create-vat', details: PolicyInputDetails ]} PolicyInputCreateVat
|
|
225
225
|
* @typedef { [tag: 'crank', details: PolicyInputDetails ] } PolicyInputCrankComplete
|
|
226
226
|
* @typedef { [tag: 'crank-failed', details: PolicyInputDetails ]} PolicyInputCrankFailed
|
|
227
|
-
* @typedef {
|
|
227
|
+
* @typedef { [tag: 'cleanup', details: { cleanups: number, computrons?: number }] } PolicyInputCleanup
|
|
228
|
+
* @typedef { PolicyInputNone | PolicyInputCreateVat | PolicyInputCrankComplete |
|
|
229
|
+
* PolicyInputCrankFailed | PolicyInputCleanup } PolicyInput
|
|
228
230
|
* @typedef { boolean } PolicyOutput
|
|
229
|
-
* @typedef { {
|
|
231
|
+
* @typedef { {
|
|
232
|
+
* allowCleanup?: () => false | { budget?: number },
|
|
233
|
+
* vatCreated: (details: {}) => PolicyOutput,
|
|
230
234
|
* crankComplete: (details: { computrons?: bigint }) => PolicyOutput,
|
|
231
235
|
* crankFailed: (details: {}) => PolicyOutput,
|
|
232
236
|
* emptyCrank: () => PolicyOutput,
|
|
237
|
+
* didCleanup?: (details: { cleanups: number }) => PolicyOutput,
|
|
233
238
|
* } } RunPolicy
|
|
234
239
|
*
|
|
235
240
|
* @typedef {object} VatWarehousePolicy
|
package/src/types-internal.js
CHANGED
|
@@ -143,10 +143,12 @@ export {};
|
|
|
143
143
|
* @typedef { { type: 'retireImports', vatID: VatID, krefs: string[] } } RunQueueEventRetireImports
|
|
144
144
|
* @typedef { { type: 'negated-gc-action', vatID?: VatID } } RunQueueEventNegatedGCAction
|
|
145
145
|
* @typedef { { type: 'bringOutYourDead', vatID: VatID } } RunQueueEventBringOutYourDead
|
|
146
|
+
* @typedef { { type: 'cleanup-terminated-vat', vatID: VatID,
|
|
147
|
+
* budget: number | undefined } } RunQueueEventCleanupTerminatedVat
|
|
146
148
|
* @typedef { RunQueueEventNotify | RunQueueEventSend | RunQueueEventCreateVat |
|
|
147
149
|
* RunQueueEventUpgradeVat | RunQueueEventChangeVatOptions | RunQueueEventStartVat |
|
|
148
150
|
* RunQueueEventDropExports | RunQueueEventRetireExports | RunQueueEventRetireImports |
|
|
149
|
-
* RunQueueEventNegatedGCAction | RunQueueEventBringOutYourDead
|
|
151
|
+
* RunQueueEventNegatedGCAction | RunQueueEventBringOutYourDead | RunQueueEventCleanupTerminatedVat
|
|
150
152
|
* } RunQueueEvent
|
|
151
153
|
*/
|
|
152
154
|
|