@agoric/swingset-liveslots 0.10.3-mainnet1B-dev-b0c1f78.0 → 0.10.3-orchestration-dev-096c4e8.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/README.md +2 -0
- package/package.json +25 -17
- package/src/cache.js +5 -3
- package/src/collectionManager.js +147 -69
- package/src/index.js +2 -1
- package/src/liveslots.js +52 -79
- package/src/message.js +4 -4
- package/src/types.js +8 -2
- package/src/vatDataTypes.d.ts +234 -0
- package/src/vatDataTypes.js +2 -0
- package/src/vatstore-iterators.js +2 -0
- package/src/virtualObjectManager.js +107 -63
- package/src/virtualReferences.js +51 -0
- package/src/watchedPromises.js +50 -15
- package/test/gc-and-finalize.js +30 -1
- package/test/gc-helpers.js +2 -1
- package/test/liveslots-helpers.js +6 -6
- package/test/mock-gc.js +1 -0
- package/test/storeGC/test-lifecycle.js +2 -2
- package/test/storeGC/test-refcount-management.js +1 -2
- package/test/storeGC/test-scalar-store-kind.js +0 -1
- package/test/storeGC/test-weak-key.js +1 -2
- package/test/test-baggage.js +1 -2
- package/test/test-cache.js +0 -1
- package/test/test-collection-schema-refcount.js +1 -2
- package/test/test-collection-upgrade.js +1 -3
- package/test/test-collections.js +117 -14
- package/test/test-dropped-collection-weakrefs.js +1 -2
- package/test/test-durabilityChecks.js +3 -3
- package/test/test-facetiousness.js +1 -2
- package/test/test-gc-sensitivity.js +2 -2
- package/test/test-handled-promises.js +5 -7
- package/test/test-initial-vrefs.js +2 -3
- package/test/test-liveslots-mock-gc.js +2 -2
- package/test/test-liveslots-real-gc.js +44 -35
- package/test/test-liveslots.js +13 -14
- package/test/test-vo-test-harness.js +0 -1
- package/test/test-vpid-liveslots.js +4 -5
- package/test/util.js +2 -2
- package/test/vat-util.js +1 -1
- package/test/virtual-objects/test-cease-recognition.js +2 -2
- package/test/virtual-objects/test-cross-facet.js +1 -2
- package/test/virtual-objects/test-empty-data.js +1 -2
- package/test/virtual-objects/test-facets.js +1 -2
- package/test/virtual-objects/test-kind-changes.js +2 -2
- package/test/virtual-objects/test-reachable-vrefs.js +2 -2
- package/test/virtual-objects/test-rep-tostring.js +2 -3
- package/test/virtual-objects/test-retain-remotable.js +25 -24
- package/test/virtual-objects/test-state-shape.js +2 -2
- package/test/virtual-objects/test-virtualObjectGC.js +2 -2
- package/test/virtual-objects/test-virtualObjectManager.js +126 -8
- package/test/virtual-objects/test-vo-real-gc.js +8 -8
- package/test/virtual-objects/test-weakcollections-vref-handling.js +1 -2
- package/tools/fakeVirtualSupport.js +48 -21
- package/tools/prepare-test-env.js +13 -0
- package/tools/setup-vat-data.js +62 -0
- package/CHANGELOG.md +0 -85
- package/test/kmarshal.js +0 -79
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
/* global globalThis */
|
|
2
2
|
/* eslint-disable no-use-before-define, jsdoc/require-returns-type */
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import { environmentOptionsListHas } from '@endo/env-options';
|
|
5
|
+
import {
|
|
6
|
+
assert,
|
|
7
|
+
throwRedacted as Fail,
|
|
8
|
+
quote as q,
|
|
9
|
+
bare as b,
|
|
10
|
+
} from '@endo/errors';
|
|
5
11
|
import { assertPattern, mustMatch } from '@agoric/store';
|
|
6
12
|
import { defendPrototype, defendPrototypeKit } from '@endo/exo/tools.js';
|
|
7
13
|
import { Far, passStyleOf } from '@endo/marshal';
|
|
@@ -16,15 +22,15 @@ import {
|
|
|
16
22
|
|
|
17
23
|
/** @template T @typedef {import('@agoric/vat-data').DefineKindOptions<T>} DefineKindOptions */
|
|
18
24
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
25
|
+
/**
|
|
26
|
+
* @typedef {import('@endo/exo/src/exo-tools.js').ClassContextProvider } ClassContextProvider
|
|
27
|
+
*
|
|
28
|
+
* @typedef {import('@endo/exo/src/exo-tools.js').KitContextProvider } KitContextProvider
|
|
29
|
+
*/
|
|
24
30
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
const
|
|
31
|
+
const { hasOwn, defineProperty, getOwnPropertyNames, entries, fromEntries } =
|
|
32
|
+
Object;
|
|
33
|
+
const { ownKeys } = Reflect;
|
|
28
34
|
|
|
29
35
|
// Turn on to give each exo instance its own toStringTag value which exposes
|
|
30
36
|
// the SwingSet vref.
|
|
@@ -33,9 +39,7 @@ const env = (globalThis.process || {}).env || {};
|
|
|
33
39
|
// confidential object-creation activity, so this must not be something
|
|
34
40
|
// that unprivileged vat code (including unprivileged contracts) can do
|
|
35
41
|
// for themselves.
|
|
36
|
-
const LABEL_INSTANCES = (
|
|
37
|
-
.split(':')
|
|
38
|
-
.includes('label-instances');
|
|
42
|
+
const LABEL_INSTANCES = environmentOptionsListHas('DEBUG', 'label-instances');
|
|
39
43
|
|
|
40
44
|
// This file implements the "Virtual Objects" system, currently documented in
|
|
41
45
|
// {@link https://github.com/Agoric/agoric-sdk/blob/master/packages/SwingSet/docs/virtual-objects.md})
|
|
@@ -132,40 +136,6 @@ const makeContextCache = (makeState, makeContext) => {
|
|
|
132
136
|
return makeCache(readBacking, writeBacking, deleteBacking);
|
|
133
137
|
};
|
|
134
138
|
|
|
135
|
-
/**
|
|
136
|
-
* @typedef {import('@endo/exo/src/exo-tools.js').ContextProvider } ContextProvider
|
|
137
|
-
*/
|
|
138
|
-
|
|
139
|
-
/**
|
|
140
|
-
* @param {*} contextCache
|
|
141
|
-
* @param {*} getSlotForVal
|
|
142
|
-
* @returns {ContextProvider}
|
|
143
|
-
*/
|
|
144
|
-
const makeContextProvider = (contextCache, getSlotForVal) => {
|
|
145
|
-
return harden(rep => contextCache.get(getSlotForVal(rep)));
|
|
146
|
-
};
|
|
147
|
-
|
|
148
|
-
const makeContextProviderKit = (contextCache, getSlotForVal, facetNames) => {
|
|
149
|
-
/** @type { Record<string, any> } */
|
|
150
|
-
const contextProviderKit = {};
|
|
151
|
-
for (const [index, name] of facetNames.entries()) {
|
|
152
|
-
contextProviderKit[name] = rep => {
|
|
153
|
-
const vref = getSlotForVal(rep);
|
|
154
|
-
const { baseRef, facet } = parseVatSlot(vref);
|
|
155
|
-
|
|
156
|
-
// Without this check, an attacker (with access to both cohort1.facetA
|
|
157
|
-
// and cohort2.facetB) could effectively forge access to cohort1.facetB
|
|
158
|
-
// and cohort2.facetA. They could not forge the identity of those two
|
|
159
|
-
// objects, but they could invoke all their equivalent methods, by using
|
|
160
|
-
// e.g. cohort1.facetA.foo.apply(cohort2.facetB, [...args])
|
|
161
|
-
Number(facet) === index || Fail`illegal cross-facet access`;
|
|
162
|
-
|
|
163
|
-
return harden(contextCache.get(baseRef));
|
|
164
|
-
};
|
|
165
|
-
}
|
|
166
|
-
return harden(contextProviderKit);
|
|
167
|
-
};
|
|
168
|
-
|
|
169
139
|
// The management of single Representatives (i.e. defineKind) is very similar
|
|
170
140
|
// to that of a cohort of facets (i.e. defineKindMulti). In this description,
|
|
171
141
|
// we use "self/facets" to refer to either 'self' or 'facets', as appropriate
|
|
@@ -260,15 +230,15 @@ const makeFacets = (
|
|
|
260
230
|
};
|
|
261
231
|
|
|
262
232
|
const insistDurableCapdata = (vrm, what, capdata, valueFor) => {
|
|
263
|
-
capdata.slots
|
|
233
|
+
for (const [idx, vref] of entries(capdata.slots)) {
|
|
264
234
|
if (!vrm.isDurable(vref)) {
|
|
265
235
|
if (valueFor) {
|
|
266
|
-
Fail`value for ${what} is not durable: slot ${
|
|
236
|
+
Fail`value for ${what} is not durable: slot ${b(idx)} of ${capdata}`;
|
|
267
237
|
} else {
|
|
268
|
-
Fail`${what} is not durable: slot ${
|
|
238
|
+
Fail`${what} is not durable: slot ${b(idx)} of ${capdata}`;
|
|
269
239
|
}
|
|
270
240
|
}
|
|
271
|
-
}
|
|
241
|
+
}
|
|
272
242
|
};
|
|
273
243
|
|
|
274
244
|
const insistSameCapData = (oldCD, newCD) => {
|
|
@@ -281,11 +251,11 @@ const insistSameCapData = (oldCD, newCD) => {
|
|
|
281
251
|
if (oldCD.slots.length !== newCD.slots.length) {
|
|
282
252
|
Fail`durable Kind stateShape mismatch (slots.length)`;
|
|
283
253
|
}
|
|
284
|
-
oldCD.slots
|
|
254
|
+
for (const [idx, oldVref] of entries(oldCD.slots)) {
|
|
285
255
|
if (newCD.slots[idx] !== oldVref) {
|
|
286
256
|
Fail`durable Kind stateShape mismatch (slot[${idx}])`;
|
|
287
257
|
}
|
|
288
|
-
}
|
|
258
|
+
}
|
|
289
259
|
};
|
|
290
260
|
|
|
291
261
|
/**
|
|
@@ -306,7 +276,7 @@ const insistSameCapData = (oldCD, newCD) => {
|
|
|
306
276
|
* @param {import('@endo/marshal').FromCapData<string>} unserialize Unserializer for this vat
|
|
307
277
|
* @param {*} assertAcceptableSyscallCapdataSize Function to check for oversized
|
|
308
278
|
* syscall params
|
|
309
|
-
* @param {import('./types').LiveSlotsOptions} [liveSlotsOptions]
|
|
279
|
+
* @param {import('./types.js').LiveSlotsOptions} [liveSlotsOptions]
|
|
310
280
|
* @param {{ WeakMap: typeof WeakMap, WeakSet: typeof WeakSet }} [powers]
|
|
311
281
|
* Specifying the underlying WeakMap/WeakSet objects to wrap with
|
|
312
282
|
* VirtualObjectAwareWeakMap/Set. By default, capture the ones currently
|
|
@@ -314,7 +284,7 @@ const insistSameCapData = (oldCD, newCD) => {
|
|
|
314
284
|
* recursion if our returned WeakMap/WeakSet wrappers are subsequently installed
|
|
315
285
|
* on globalThis.
|
|
316
286
|
*
|
|
317
|
-
* @returns
|
|
287
|
+
* @returns a new virtual object manager.
|
|
318
288
|
*
|
|
319
289
|
* The virtual object manager allows the creation of persistent objects that do
|
|
320
290
|
* not need to occupy memory when they are not in use. It provides five
|
|
@@ -707,10 +677,16 @@ export const makeVirtualObjectManager = (
|
|
|
707
677
|
durableKindDescriptor = undefined, // only for durables
|
|
708
678
|
) => {
|
|
709
679
|
const {
|
|
710
|
-
finish,
|
|
680
|
+
finish = undefined,
|
|
711
681
|
stateShape = undefined,
|
|
712
682
|
thisfulMethods = false,
|
|
683
|
+
} = options;
|
|
684
|
+
let {
|
|
685
|
+
// These are "let" rather than "const" only to accommodate code
|
|
686
|
+
// below that tolerates an old version of the vat-data package.
|
|
687
|
+
// See there for more explanation.
|
|
713
688
|
interfaceGuard = undefined,
|
|
689
|
+
interfaceGuardKit = undefined,
|
|
714
690
|
} = options;
|
|
715
691
|
|
|
716
692
|
const statePrototype = {}; // Not frozen yet
|
|
@@ -731,11 +707,38 @@ export const makeVirtualObjectManager = (
|
|
|
731
707
|
switch (assessFacetiousness(behavior)) {
|
|
732
708
|
case 'one': {
|
|
733
709
|
assert(!multifaceted);
|
|
710
|
+
interfaceGuardKit === undefined ||
|
|
711
|
+
Fail`Use an interfaceGuard, not interfaceGuardKit, to protect class ${q(
|
|
712
|
+
tag,
|
|
713
|
+
)}`;
|
|
734
714
|
proposedFacetNames = undefined;
|
|
735
715
|
break;
|
|
736
716
|
}
|
|
737
717
|
case 'many': {
|
|
738
718
|
assert(multifaceted);
|
|
719
|
+
|
|
720
|
+
if (interfaceGuard && interfaceGuardKit === undefined) {
|
|
721
|
+
// This if clause is for the purpose of tolerating versions
|
|
722
|
+
// of the vata-data package that precede
|
|
723
|
+
// https://github.com/Agoric/agoric-sdk/pull/8220 .
|
|
724
|
+
// Before that PR, the options name `interfaceGuard` would
|
|
725
|
+
// actually carry the InterfaceGuardKit.
|
|
726
|
+
//
|
|
727
|
+
// Tolerating the old vat-data with the new types.
|
|
728
|
+
// at-expect-error here causes inconsistent reports, so
|
|
729
|
+
// doing the at-ts-ignore-error ritual instead.
|
|
730
|
+
// eslint-disable-next-line @typescript-eslint/prefer-ts-expect-error
|
|
731
|
+
// @ts-ignore
|
|
732
|
+
interfaceGuardKit = interfaceGuard;
|
|
733
|
+
interfaceGuard = undefined;
|
|
734
|
+
// The rest of the code from here makes no further compromise
|
|
735
|
+
// for that old version of the vat-data package.
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
interfaceGuard === undefined ||
|
|
739
|
+
Fail`Use an interfaceGuardKit, not an interfaceGuard, to protect class kit ${q(
|
|
740
|
+
tag,
|
|
741
|
+
)}`;
|
|
739
742
|
proposedFacetNames = ownKeys(behavior).sort();
|
|
740
743
|
break;
|
|
741
744
|
}
|
|
@@ -850,7 +853,9 @@ export const makeVirtualObjectManager = (
|
|
|
850
853
|
return harden({
|
|
851
854
|
get() {
|
|
852
855
|
const baseRef = getBaseRef(this);
|
|
853
|
-
const
|
|
856
|
+
const record = dataCache.get(baseRef);
|
|
857
|
+
assert(record !== undefined);
|
|
858
|
+
const { valueMap, capdatas } = record;
|
|
854
859
|
if (!valueMap.has(prop)) {
|
|
855
860
|
const value = harden(unserialize(capdatas[prop]));
|
|
856
861
|
checkStatePropertyValue(value, prop);
|
|
@@ -867,6 +872,7 @@ export const makeVirtualObjectManager = (
|
|
|
867
872
|
insistDurableCapdata(vrm, prop, capdata, true);
|
|
868
873
|
}
|
|
869
874
|
const record = dataCache.get(baseRef); // mutable
|
|
875
|
+
assert(record !== undefined);
|
|
870
876
|
const oldSlots = record.capdatas[prop].slots;
|
|
871
877
|
const newSlots = capdata.slots;
|
|
872
878
|
vrm.updateReferenceCounts(oldSlots, newSlots);
|
|
@@ -890,7 +896,9 @@ export const makeVirtualObjectManager = (
|
|
|
890
896
|
const makeState = baseRef => {
|
|
891
897
|
const state = { __proto__: statePrototype };
|
|
892
898
|
if (stateShape === undefined) {
|
|
893
|
-
|
|
899
|
+
const record = dataCache.get(baseRef);
|
|
900
|
+
assert(record !== undefined);
|
|
901
|
+
for (const prop of ownKeys(record.capdatas)) {
|
|
894
902
|
assert(typeof prop === 'string');
|
|
895
903
|
checkStateProperty(prop);
|
|
896
904
|
defineProperty(state, prop, makeFieldDescriptor(prop));
|
|
@@ -941,17 +949,47 @@ export const makeVirtualObjectManager = (
|
|
|
941
949
|
|
|
942
950
|
let proto;
|
|
943
951
|
if (multifaceted) {
|
|
952
|
+
/** @type { Record<string, KitContextProvider> } */
|
|
953
|
+
const contextProviderKit = fromEntries(
|
|
954
|
+
facetNames.map((name, index) => [
|
|
955
|
+
name,
|
|
956
|
+
rep => {
|
|
957
|
+
const vref = getSlotForVal(rep);
|
|
958
|
+
assert(vref !== undefined);
|
|
959
|
+
const { baseRef, facet } = parseVatSlot(vref);
|
|
960
|
+
|
|
961
|
+
// Without this check, an attacker (with access to both
|
|
962
|
+
// cohort1.facetA and cohort2.facetB)
|
|
963
|
+
// could effectively forge access to
|
|
964
|
+
// cohort1.facetB and cohort2.facetA.
|
|
965
|
+
// They could not forge the identity of those two
|
|
966
|
+
// objects, but they could invoke all their equivalent methods,
|
|
967
|
+
// by using e.g.
|
|
968
|
+
// cohort1.facetA.foo.apply(cohort2.facetB, [...args])
|
|
969
|
+
Number(facet) === index || Fail`illegal cross-facet access`;
|
|
970
|
+
|
|
971
|
+
return harden(contextCache.get(baseRef));
|
|
972
|
+
},
|
|
973
|
+
]),
|
|
974
|
+
);
|
|
975
|
+
|
|
944
976
|
proto = defendPrototypeKit(
|
|
945
977
|
tag,
|
|
946
|
-
|
|
978
|
+
harden(contextProviderKit),
|
|
947
979
|
behavior,
|
|
948
980
|
thisfulMethods,
|
|
949
|
-
|
|
981
|
+
interfaceGuardKit,
|
|
950
982
|
);
|
|
951
983
|
} else {
|
|
984
|
+
/** @type {ClassContextProvider} */
|
|
985
|
+
const contextProvider = rep => {
|
|
986
|
+
const slot = getSlotForVal(rep);
|
|
987
|
+
assert(slot !== undefined);
|
|
988
|
+
return harden(contextCache.get(slot));
|
|
989
|
+
};
|
|
952
990
|
proto = defendPrototype(
|
|
953
991
|
tag,
|
|
954
|
-
|
|
992
|
+
harden(contextProvider),
|
|
955
993
|
behavior,
|
|
956
994
|
thisfulMethods,
|
|
957
995
|
interfaceGuard,
|
|
@@ -973,10 +1011,11 @@ export const makeVirtualObjectManager = (
|
|
|
973
1011
|
const deleteStoredVO = baseRef => {
|
|
974
1012
|
let doMoreGC = false;
|
|
975
1013
|
const record = dataCache.get(baseRef);
|
|
1014
|
+
assert(record !== undefined);
|
|
976
1015
|
for (const valueCD of Object.values(record.capdatas)) {
|
|
977
|
-
valueCD.slots
|
|
1016
|
+
for (const vref of valueCD.slots) {
|
|
978
1017
|
doMoreGC = vrm.removeReachableVref(vref) || doMoreGC;
|
|
979
|
-
}
|
|
1018
|
+
}
|
|
980
1019
|
}
|
|
981
1020
|
dataCache.delete(baseRef);
|
|
982
1021
|
return doMoreGC;
|
|
@@ -1015,6 +1054,7 @@ export const makeVirtualObjectManager = (
|
|
|
1015
1054
|
if (isDurable) {
|
|
1016
1055
|
insistDurableCapdata(vrm, prop, valueCD, true);
|
|
1017
1056
|
}
|
|
1057
|
+
// eslint-disable-next-line github/array-foreach
|
|
1018
1058
|
valueCD.slots.forEach(vrm.addReachableVref);
|
|
1019
1059
|
capdatas[prop] = valueCD;
|
|
1020
1060
|
valueMap.set(prop, value);
|
|
@@ -1030,7 +1070,7 @@ export const makeVirtualObjectManager = (
|
|
|
1030
1070
|
val = makeRepresentative(proto, baseRef);
|
|
1031
1071
|
}
|
|
1032
1072
|
registerValue(baseRef, val, multifaceted);
|
|
1033
|
-
finish
|
|
1073
|
+
finish && finish(contextCache.get(baseRef));
|
|
1034
1074
|
return val;
|
|
1035
1075
|
};
|
|
1036
1076
|
|
|
@@ -1082,6 +1122,7 @@ export const makeVirtualObjectManager = (
|
|
|
1082
1122
|
return id;
|
|
1083
1123
|
};
|
|
1084
1124
|
|
|
1125
|
+
/** @type {import('./vatDataTypes').VatData['defineKind']} */
|
|
1085
1126
|
const defineKind = (tag, init, behavior, options) => {
|
|
1086
1127
|
const kindID = `${allocateExportID()}`;
|
|
1087
1128
|
saveVirtualKindDescriptor(kindID, { kindID, tag });
|
|
@@ -1097,6 +1138,7 @@ export const makeVirtualObjectManager = (
|
|
|
1097
1138
|
);
|
|
1098
1139
|
};
|
|
1099
1140
|
|
|
1141
|
+
/** @type {import('./vatDataTypes').VatData['defineKindMulti']} */
|
|
1100
1142
|
const defineKindMulti = (tag, init, behavior, options) => {
|
|
1101
1143
|
const kindID = `${allocateExportID()}`;
|
|
1102
1144
|
saveVirtualKindDescriptor(kindID, { kindID, tag });
|
|
@@ -1136,6 +1178,7 @@ export const makeVirtualObjectManager = (
|
|
|
1136
1178
|
return kindHandle;
|
|
1137
1179
|
};
|
|
1138
1180
|
|
|
1181
|
+
/** @type {import('./vatDataTypes').VatData['defineDurableKind']} */
|
|
1139
1182
|
const defineDurableKind = (kindHandle, init, behavior, options) => {
|
|
1140
1183
|
kindHandleToID.has(kindHandle) || Fail`unknown handle ${kindHandle}`;
|
|
1141
1184
|
const kindID = kindHandleToID.get(kindHandle);
|
|
@@ -1158,6 +1201,7 @@ export const makeVirtualObjectManager = (
|
|
|
1158
1201
|
return maker;
|
|
1159
1202
|
};
|
|
1160
1203
|
|
|
1204
|
+
/** @type {import('./vatDataTypes').VatData['defineDurableKindMulti']} */
|
|
1161
1205
|
const defineDurableKindMulti = (kindHandle, init, behavior, options) => {
|
|
1162
1206
|
kindHandleToID.has(kindHandle) || Fail`unknown handle ${kindHandle}`;
|
|
1163
1207
|
const kindID = kindHandleToID.get(kindHandle);
|
package/src/virtualReferences.js
CHANGED
|
@@ -682,6 +682,54 @@ export function makeVirtualReferenceManager(
|
|
|
682
682
|
return size;
|
|
683
683
|
}
|
|
684
684
|
|
|
685
|
+
/**
|
|
686
|
+
* Counters to track the next number for various categories of allocation.
|
|
687
|
+
* `exportID` starts at 1 because 'o+0' is always automatically
|
|
688
|
+
* pre-assigned to the root object.
|
|
689
|
+
* `promiseID` starts at 5 as a very minor aid to debugging: when puzzling
|
|
690
|
+
* over trace logs and the like, it helps for the numbers in various species
|
|
691
|
+
* of IDs that are jumbled together to be a little out of sync and thus a
|
|
692
|
+
* little less similar to each other.
|
|
693
|
+
*/
|
|
694
|
+
const initialIDCounters = { exportID: 1, collectionID: 1, promiseID: 5 };
|
|
695
|
+
/** @type {Record<string, number>} */
|
|
696
|
+
let idCounters;
|
|
697
|
+
let idCountersAreDirty = false;
|
|
698
|
+
|
|
699
|
+
function initializeIDCounters() {
|
|
700
|
+
if (!idCounters) {
|
|
701
|
+
// the saved value might be missing, or from an older liveslots
|
|
702
|
+
// (with fewer counters), so merge it with our initial values
|
|
703
|
+
const saved = JSON.parse(syscall.vatstoreGet('idCounters') || '{}');
|
|
704
|
+
idCounters = { ...initialIDCounters, ...saved };
|
|
705
|
+
idCountersAreDirty = true;
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
function allocateNextID(name) {
|
|
710
|
+
if (!idCounters) {
|
|
711
|
+
// Normally `initializeIDCounters` would be called from startVat, but some
|
|
712
|
+
// tests bypass that so this is a backstop. Note that the invocation from
|
|
713
|
+
// startVat is there to make vatStore access patterns a bit more
|
|
714
|
+
// consistent from one vat to another, principally as a confusion
|
|
715
|
+
// reduction measure in service of debugging; it is not a correctness
|
|
716
|
+
// issue.
|
|
717
|
+
initializeIDCounters();
|
|
718
|
+
}
|
|
719
|
+
const result = idCounters[name];
|
|
720
|
+
result !== undefined || Fail`unknown idCounters[${name}]`;
|
|
721
|
+
idCounters[name] += 1;
|
|
722
|
+
idCountersAreDirty = true;
|
|
723
|
+
return result;
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
function flushIDCounters() {
|
|
727
|
+
if (idCountersAreDirty) {
|
|
728
|
+
syscall.vatstoreSet('idCounters', JSON.stringify(idCounters));
|
|
729
|
+
idCountersAreDirty = false;
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
|
|
685
733
|
const testHooks = {
|
|
686
734
|
getReachableRefCount,
|
|
687
735
|
countCollectionsForWeakKey,
|
|
@@ -726,6 +774,9 @@ export function makeVirtualReferenceManager(
|
|
|
726
774
|
ceaseRecognition,
|
|
727
775
|
setDeleteCollectionEntry,
|
|
728
776
|
getRetentionStats,
|
|
777
|
+
initializeIDCounters,
|
|
778
|
+
allocateNextID,
|
|
779
|
+
flushIDCounters,
|
|
729
780
|
testHooks,
|
|
730
781
|
});
|
|
731
782
|
}
|
package/src/watchedPromises.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// @ts-check
|
|
1
2
|
// no-lonely-if is a stupid rule that really should be disabled globally
|
|
2
3
|
/* eslint-disable no-lonely-if */
|
|
3
4
|
|
|
@@ -6,6 +7,12 @@ import { initEmpty, M } from '@agoric/store';
|
|
|
6
7
|
import { E } from '@endo/eventual-send';
|
|
7
8
|
import { parseVatSlot } from './parseVatSlots.js';
|
|
8
9
|
|
|
10
|
+
/**
|
|
11
|
+
* @template V
|
|
12
|
+
* @template {any[]} [A=unknown[]]
|
|
13
|
+
* @typedef {[watcher: import('./types.js').PromiseWatcher<V, A>, ...args: A]} PromiseWatcherTuple
|
|
14
|
+
*/
|
|
15
|
+
|
|
9
16
|
/**
|
|
10
17
|
* @param {object} options
|
|
11
18
|
* @param {*} options.syscall
|
|
@@ -28,17 +35,28 @@ export function makeWatchedPromiseManager({
|
|
|
28
35
|
const { makeScalarBigMapStore } = collectionManager;
|
|
29
36
|
const { defineDurableKind } = vom;
|
|
30
37
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
38
|
+
/**
|
|
39
|
+
* virtual Store (not durable) mapping vpid to Promise objects, to
|
|
40
|
+
* maintain the slotToVal registration until resolution. Without
|
|
41
|
+
* this, slotToVal would forget local Promises that aren't exported.
|
|
42
|
+
*
|
|
43
|
+
* @type {MapStore<string, Promise<unknown>>}
|
|
44
|
+
*/
|
|
34
45
|
let promiseRegistrations;
|
|
35
46
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
47
|
+
/**
|
|
48
|
+
* watched promises by vpid: each entry is an array of watches on the
|
|
49
|
+
* corresponding vpid; each of these is in turn an array of a watcher object
|
|
50
|
+
* and the arguments associated with it by `watchPromise`.
|
|
51
|
+
* @type {MapStore<string, PromiseWatcherTuple<unknown>[]>}
|
|
52
|
+
*/
|
|
39
53
|
let watchedPromiseTable;
|
|
40
54
|
|
|
41
|
-
|
|
55
|
+
/**
|
|
56
|
+
* defined promise watcher objects indexed by kindHandle
|
|
57
|
+
*
|
|
58
|
+
* @type {MapStore<import('./vatDataTypes.js').DurableKindHandle, import('./types.js').PromiseWatcher<unknown>>}
|
|
59
|
+
*/
|
|
42
60
|
let promiseWatcherByKindTable;
|
|
43
61
|
|
|
44
62
|
function preparePromiseWatcherTables() {
|
|
@@ -73,11 +91,17 @@ export function makeWatchedPromiseManager({
|
|
|
73
91
|
}
|
|
74
92
|
|
|
75
93
|
/**
|
|
76
|
-
*
|
|
77
|
-
* @param {Promise<
|
|
94
|
+
* @template T
|
|
95
|
+
* @param {Promise<T>} p
|
|
78
96
|
* @param {string} vpid
|
|
97
|
+
* @returns {void}
|
|
79
98
|
*/
|
|
80
99
|
function pseudoThen(p, vpid) {
|
|
100
|
+
/**
|
|
101
|
+
*
|
|
102
|
+
* @param {T} value
|
|
103
|
+
* @param {boolean} wasFulfilled
|
|
104
|
+
*/
|
|
81
105
|
function settle(value, wasFulfilled) {
|
|
82
106
|
const watches = watchedPromiseTable.get(vpid);
|
|
83
107
|
watchedPromiseTable.delete(vpid);
|
|
@@ -121,20 +145,28 @@ export function makeWatchedPromiseManager({
|
|
|
121
145
|
}
|
|
122
146
|
}
|
|
123
147
|
|
|
148
|
+
/**
|
|
149
|
+
* @template V
|
|
150
|
+
* @template {any[]} A]
|
|
151
|
+
* @param {import('./vatDataTypes.js').DurableKindHandle} kindHandle
|
|
152
|
+
* @param {(value: V, ...args: A) => void} fulfillHandler
|
|
153
|
+
* @param {(reason: any, ...args: A) => void} rejectHandler
|
|
154
|
+
* @returns {import('./types.js').PromiseWatcher<V, A>}
|
|
155
|
+
*/
|
|
124
156
|
function providePromiseWatcher(
|
|
125
157
|
kindHandle,
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
},
|
|
158
|
+
// @ts-expect-error xxx rest params in typedef
|
|
159
|
+
fulfillHandler = _value => {},
|
|
160
|
+
// @ts-expect-error xxx rest params in typedef
|
|
161
|
+
rejectHandler = _reason => {},
|
|
130
162
|
) {
|
|
131
163
|
assert.typeof(fulfillHandler, 'function');
|
|
132
164
|
assert.typeof(rejectHandler, 'function');
|
|
133
165
|
|
|
134
166
|
const makeWatcher = defineDurableKind(kindHandle, initEmpty, {
|
|
135
|
-
|
|
167
|
+
/** @type {(context: unknown, res: V, ...args: A) => void} */
|
|
136
168
|
onFulfilled: (_context, res, ...args) => fulfillHandler(res, ...args),
|
|
137
|
-
|
|
169
|
+
/** @type {(context: unknown, rej: unknown, ...args: A) => void} */
|
|
138
170
|
onRejected: (_context, rej, ...args) => rejectHandler(rej, ...args),
|
|
139
171
|
});
|
|
140
172
|
|
|
@@ -147,6 +179,9 @@ export function makeWatchedPromiseManager({
|
|
|
147
179
|
}
|
|
148
180
|
}
|
|
149
181
|
|
|
182
|
+
/**
|
|
183
|
+
* @type {<P extends Promise<any>, A extends any[]>(p: P, watcher: import('./types.js').PromiseWatcher<Awaited<P>, A>, ...args: A) => void}
|
|
184
|
+
*/
|
|
150
185
|
function watchPromise(p, watcher, ...args) {
|
|
151
186
|
// The following wrapping defers setting up the promise watcher itself to a
|
|
152
187
|
// later turn so that if the promise to be watched was the return value from
|
package/test/gc-and-finalize.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/* global setImmediate */
|
|
1
|
+
/* global setImmediate, FinalizationRegistry */
|
|
2
2
|
|
|
3
3
|
/* A note on our GC terminology:
|
|
4
4
|
*
|
|
@@ -89,3 +89,32 @@ export function makeGcAndFinalize(gcPower) {
|
|
|
89
89
|
await new Promise(setImmediate);
|
|
90
90
|
};
|
|
91
91
|
}
|
|
92
|
+
|
|
93
|
+
const fr = new FinalizationRegistry(({ promise, resolve }) => {
|
|
94
|
+
promise.result = true;
|
|
95
|
+
resolve(true);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
const makeCollectedResultKit = () => {
|
|
99
|
+
/** @type {(val: true) => void} */
|
|
100
|
+
let resolve;
|
|
101
|
+
|
|
102
|
+
const promise = /** @type {Promise<true> & {result: boolean}} */ (
|
|
103
|
+
new Promise(r => {
|
|
104
|
+
resolve = r;
|
|
105
|
+
})
|
|
106
|
+
);
|
|
107
|
+
promise.result = false;
|
|
108
|
+
return {
|
|
109
|
+
promise,
|
|
110
|
+
// @ts-expect-error
|
|
111
|
+
resolve,
|
|
112
|
+
};
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
export function watchCollected(target) {
|
|
116
|
+
const kit = makeCollectedResultKit();
|
|
117
|
+
fr.register(target, kit);
|
|
118
|
+
|
|
119
|
+
return kit.promise;
|
|
120
|
+
}
|
package/test/gc-helpers.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { Far } from '@endo/marshal';
|
|
4
4
|
import { M } from '@agoric/store';
|
|
5
|
-
import { kslot, kser } from '
|
|
5
|
+
import { kslot, kser } from '@agoric/kmarshal';
|
|
6
6
|
import { parseVatSlot } from '../src/parseVatSlots.js';
|
|
7
7
|
|
|
8
8
|
// These tests follow the model described in
|
|
@@ -168,6 +168,7 @@ export function* enumerateKeysWithPrefix(fakestore, prefix) {
|
|
|
168
168
|
}
|
|
169
169
|
}
|
|
170
170
|
}
|
|
171
|
+
harden(enumerateKeysWithPrefix);
|
|
171
172
|
|
|
172
173
|
export function recognizersOf(v, baseref) {
|
|
173
174
|
// the | is followed by the collectionID that can recognize baseref
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/* global WeakRef, FinalizationRegistry */
|
|
2
|
-
import
|
|
2
|
+
import { kser } from '@agoric/kmarshal';
|
|
3
3
|
|
|
4
|
+
import engineGC from './engine-gc.js';
|
|
4
5
|
import { waitUntilQuiescent } from './waitUntilQuiescent.js';
|
|
5
6
|
import { makeGcAndFinalize } from './gc-and-finalize.js';
|
|
6
7
|
import { makeDummyMeterControl } from './dummyMeterControl.js';
|
|
@@ -12,12 +13,11 @@ import {
|
|
|
12
13
|
makeRetireExports,
|
|
13
14
|
makeBringOutYourDead,
|
|
14
15
|
} from './util.js';
|
|
15
|
-
import { kser } from './kmarshal.js';
|
|
16
16
|
|
|
17
17
|
/**
|
|
18
18
|
* @param {object} [options]
|
|
19
|
-
* @param {boolean} [options.skipLogging
|
|
20
|
-
* @param {Map<string, string>} [options.kvStore
|
|
19
|
+
* @param {boolean} [options.skipLogging]
|
|
20
|
+
* @param {Map<string, string>} [options.kvStore]
|
|
21
21
|
*/
|
|
22
22
|
export function buildSyscall(options = {}) {
|
|
23
23
|
const { skipLogging = false, kvStore: fakestore = new Map() } = options;
|
|
@@ -168,9 +168,9 @@ function makeRPMaker(nextNumber = 1) {
|
|
|
168
168
|
* @param {string} vatName
|
|
169
169
|
* @param {object} [options]
|
|
170
170
|
* @param {boolean} [options.forceGC]
|
|
171
|
-
* @param {Map<string, string>} [options.kvStore
|
|
171
|
+
* @param {Map<string, string>} [options.kvStore]
|
|
172
172
|
* @param {number} [options.nextPromiseImportNumber]
|
|
173
|
-
* @param {boolean} [options.skipLogging
|
|
173
|
+
* @param {boolean} [options.skipLogging]
|
|
174
174
|
*/
|
|
175
175
|
export async function setupTestLiveslots(
|
|
176
176
|
t,
|
package/test/mock-gc.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
1
2
|
import test from 'ava';
|
|
2
|
-
import '@endo/init/debug.js';
|
|
3
3
|
|
|
4
|
+
import { kslot, kunser } from '@agoric/kmarshal';
|
|
4
5
|
import {
|
|
5
6
|
setupTestLiveslots,
|
|
6
7
|
findSyscallsByType,
|
|
7
8
|
} from '../liveslots-helpers.js';
|
|
8
9
|
import { buildRootObject, mainHeldIdx, mapRef } from '../gc-helpers.js';
|
|
9
|
-
import { kslot, kunser } from '../kmarshal.js';
|
|
10
10
|
import { parseVatSlot } from '../../src/parseVatSlots.js';
|
|
11
11
|
|
|
12
12
|
// These tests follow the model described in
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import test from 'ava';
|
|
2
|
-
import '@endo/init/debug.js';
|
|
3
2
|
|
|
3
|
+
import { kslot } from '@agoric/kmarshal';
|
|
4
4
|
import {
|
|
5
5
|
findSyscallsByType,
|
|
6
6
|
setupTestLiveslots,
|
|
@@ -12,7 +12,6 @@ import {
|
|
|
12
12
|
refValString,
|
|
13
13
|
assertCollectionDeleted,
|
|
14
14
|
} from '../gc-helpers.js';
|
|
15
|
-
import { kslot } from '../kmarshal.js';
|
|
16
15
|
import { vstr } from '../util.js';
|
|
17
16
|
|
|
18
17
|
// These tests follow the model described in
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import test from 'ava';
|
|
2
|
-
import '@endo/init/debug.js';
|
|
3
2
|
|
|
3
|
+
import { kslot } from '@agoric/kmarshal';
|
|
4
4
|
import { setupTestLiveslots } from '../liveslots-helpers.js';
|
|
5
5
|
import {
|
|
6
6
|
buildRootObject,
|
|
@@ -8,7 +8,6 @@ import {
|
|
|
8
8
|
assertCollectionDeleted,
|
|
9
9
|
deduceCollectionID,
|
|
10
10
|
} from '../gc-helpers.js';
|
|
11
|
-
import { kslot } from '../kmarshal.js';
|
|
12
11
|
import { vstr } from '../util.js';
|
|
13
12
|
|
|
14
13
|
// These tests follow the model described in test-virtualObjectGC.js
|