@agoric/swingset-liveslots 0.10.3-u16.0 → 0.10.3-u17.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 +17 -18
- package/src/boyd-gc.js +598 -0
- package/src/cache.js +1 -1
- package/src/capdata.js +1 -1
- package/src/collectionManager.js +65 -21
- package/src/facetiousness.js +1 -1
- package/src/liveslots.js +83 -202
- package/src/message.js +1 -1
- package/src/parseVatSlots.js +1 -1
- package/src/vatDataTypes.d.ts +7 -2
- package/src/virtualObjectManager.js +1 -6
- package/src/virtualReferences.js +83 -25
- package/src/watchedPromises.js +7 -2
- package/test/clear-collection.test.js +586 -0
- package/test/collections.test.js +41 -0
- package/test/dropped-weakset-9939.test.js +80 -0
- package/test/dummyMeterControl.js +1 -1
- package/test/durabilityChecks.test.js +1 -1
- package/test/gc-before-finalizer.test.js +230 -0
- package/test/handled-promises.test.js +266 -50
- package/test/liveslots-helpers.js +6 -1
- package/test/liveslots-mock-gc.test.js +99 -0
- package/test/liveslots-real-gc.test.js +18 -2
- package/test/liveslots.test.js +1 -1
- package/test/vat-environment.test.js +65 -0
- package/test/vat-util.js +1 -1
- package/test/virtual-objects/rep-tostring.test.js +1 -2
- package/test/vpid-liveslots.test.js +102 -1
- package/test/weakset-dropped-remotable.test.js +50 -0
- package/tools/fakeVirtualSupport.js +1 -3
- package/tools/setup-vat-data.js +10 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agoric/swingset-liveslots",
|
|
3
|
-
"version": "0.10.3-
|
|
3
|
+
"version": "0.10.3-u17.0",
|
|
4
4
|
"description": "SwingSet ocap support layer",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.js",
|
|
@@ -17,23 +17,22 @@
|
|
|
17
17
|
"lint:eslint": "eslint ."
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"@agoric/
|
|
21
|
-
"@agoric/
|
|
22
|
-
"@
|
|
23
|
-
"@endo/
|
|
24
|
-
"@endo/
|
|
25
|
-
"@endo/
|
|
26
|
-
"@endo/
|
|
27
|
-
"@endo/
|
|
28
|
-
"@endo/
|
|
29
|
-
"@endo/
|
|
30
|
-
"@endo/
|
|
31
|
-
"@endo/
|
|
32
|
-
"@endo/
|
|
33
|
-
"@endo/promise-kit": "^1.1.2"
|
|
20
|
+
"@agoric/internal": "^0.4.0-u17.0",
|
|
21
|
+
"@agoric/store": "^0.9.3-u17.0",
|
|
22
|
+
"@endo/env-options": "^1.1.6",
|
|
23
|
+
"@endo/errors": "^1.2.5",
|
|
24
|
+
"@endo/eventual-send": "^1.2.5",
|
|
25
|
+
"@endo/exo": "^1.5.3",
|
|
26
|
+
"@endo/far": "^1.1.5",
|
|
27
|
+
"@endo/init": "^1.1.4",
|
|
28
|
+
"@endo/marshal": "^1.5.3",
|
|
29
|
+
"@endo/nat": "^5.0.10",
|
|
30
|
+
"@endo/pass-style": "^1.4.3",
|
|
31
|
+
"@endo/patterns": "^1.4.3",
|
|
32
|
+
"@endo/promise-kit": "^1.1.5"
|
|
34
33
|
},
|
|
35
34
|
"devDependencies": {
|
|
36
|
-
"@agoric/kmarshal": "^0.1.1-
|
|
35
|
+
"@agoric/kmarshal": "^0.1.1-u17.0",
|
|
37
36
|
"ava": "^5.3.0"
|
|
38
37
|
},
|
|
39
38
|
"files": [
|
|
@@ -67,7 +66,7 @@
|
|
|
67
66
|
"access": "public"
|
|
68
67
|
},
|
|
69
68
|
"typeCoverage": {
|
|
70
|
-
"atLeast": 75.
|
|
69
|
+
"atLeast": 75.1
|
|
71
70
|
},
|
|
72
|
-
"gitHead": "
|
|
71
|
+
"gitHead": "515c4c0efccfc91b97da30037c10fc4b076851e2"
|
|
73
72
|
}
|
package/src/boyd-gc.js
ADDED
|
@@ -0,0 +1,598 @@
|
|
|
1
|
+
import { parseVatSlot } from './parseVatSlots.js';
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
Theory of Operation (vref logical objects)
|
|
5
|
+
|
|
6
|
+
Liveslots coordinates access to "logical objects", sometimes known
|
|
7
|
+
as "vref objects" because each one is named with a distinct vref
|
|
8
|
+
identifier (o+1, o-2, o+d3/4, etc). The SwingSet kernel coordinates
|
|
9
|
+
access to the objects between vats, using a kref identifier (ko56),
|
|
10
|
+
but vats never see krefs.
|
|
11
|
+
|
|
12
|
+
When vat code (written in JavaScript) needs to interact with a
|
|
13
|
+
logical object, it needs some JS `Object` to pass in arguments, or
|
|
14
|
+
to invoke methods upon, or to receive in the arguments of a method
|
|
15
|
+
call. We use Presence objects for imported vrefs, Remotable/heap
|
|
16
|
+
objects for locally-created (potentially-exported) ephemeral
|
|
17
|
+
objects, and Representatives for virtual/durable objects initially
|
|
18
|
+
created by calling the Kind's maker function. Liveslots is
|
|
19
|
+
responsible for recognizing these `Object` objects when they appear
|
|
20
|
+
at its doorstep (e.g. in a remote method send, or when assigned to
|
|
21
|
+
the `state` of a virtual object), and knowing which vref is being
|
|
22
|
+
referenced. Liveslots is also the one to create all Presences and
|
|
23
|
+
Representatives (vat code creates the Remotable/heap objects, by
|
|
24
|
+
calling `Far()`).
|
|
25
|
+
|
|
26
|
+
For garbage-collection purposes, liveslots tracks the "reachable"
|
|
27
|
+
and "recognizable" status for these logical objects. A logical
|
|
28
|
+
object is either VREF-REACHABLE, VREF-RECOGNIZABLE, or
|
|
29
|
+
nothing. Strong references from a VREF-REACHABLE object makes the
|
|
30
|
+
target also VREF-REACHABLE. Weak references from a VREF-REACHABLE
|
|
31
|
+
makes the target at least VREF-RECOGNIZABLE, although it might be
|
|
32
|
+
VREF-REACHABLE if there is also a strong reference to it.
|
|
33
|
+
|
|
34
|
+
Weak collections provide weak access to their keys, and strong
|
|
35
|
+
access to their values. Strong collections provide strong access to
|
|
36
|
+
both keys and values. Virtual/durable objects provide strong access
|
|
37
|
+
to their `state`. The "reachable set" is the transitive closure of
|
|
38
|
+
strong edges, given a set of anchors. There will also be a
|
|
39
|
+
"recognizable set", with weak edges from some members of the
|
|
40
|
+
reachable set.
|
|
41
|
+
|
|
42
|
+
Liveslots needs to keep track of the reachable or recognizable
|
|
43
|
+
status of logical objects to limit storage consumption. It must
|
|
44
|
+
preserve all reachable data, but it can delete data that can no
|
|
45
|
+
longer be reached. Liveslots also needs to coordinate status updates
|
|
46
|
+
with the kernel, to enable the kernel to do the same.
|
|
47
|
+
|
|
48
|
+
The logical object for an imported vref (known as a "Presence-style
|
|
49
|
+
vref", o-NN) can be kept alive by either the existence of an actual
|
|
50
|
+
Presence `Object`, or by a (strong) virtual/durable collection that
|
|
51
|
+
uses the vref as its key or inside its value, or by a (weak)
|
|
52
|
+
collection that uses the vref inside its value, or by a
|
|
53
|
+
virtual/durable object that uses the vref inside its `state`. We
|
|
54
|
+
call the Presence the "RAM pillar", and the virtual/durable
|
|
55
|
+
references the "vdata pillar". The "vdata pillar" is tracked as a
|
|
56
|
+
record in the vatstore DB. We say the logical object is
|
|
57
|
+
VREF-REACHABLE if either pillar is up, and it becomes
|
|
58
|
+
VREF-UNREACHABLE if all pillars are down.
|
|
59
|
+
|
|
60
|
+
The logical object for a virtual/durable object (known as
|
|
61
|
+
"Representative-style", o+vKK/II or o+dKK/II) can be kept alive by
|
|
62
|
+
any of three pillars: the Representative (RAM pillar), a
|
|
63
|
+
virtual/durable collection or object state (vdata pillar), or by
|
|
64
|
+
virtue of being exported to the kernel (export pillar). Sending a
|
|
65
|
+
Representative in a message to some other vat will cause the vref to
|
|
66
|
+
be exported, and the kernel will tell us if/when that other vat ever
|
|
67
|
+
drops their strong reference, which will then drop the export
|
|
68
|
+
pillar. The "export status" for a vref is one of EXPORT-REACHABLE,
|
|
69
|
+
EXPORT-RECOGNIZABLE, or nothing.
|
|
70
|
+
|
|
71
|
+
The third category of vrefs (o+NN) are for locally-created ephmeral
|
|
72
|
+
objects, created by calling `Far()`. Liveslots frequently calls
|
|
73
|
+
these "Remotable-style vrefs", although "heap" might be a less
|
|
74
|
+
ambiguous term. They have only the RAM pillar: the lifetime of the
|
|
75
|
+
logical object is the same as that of the "Remotable" heap value.
|
|
76
|
+
|
|
77
|
+
Once all pillars for a logical object have dropped, liveslots needs
|
|
78
|
+
to delete the logical object. For virtual/durable objects, this
|
|
79
|
+
means deleting the `state` data, and since the state can hold
|
|
80
|
+
references to other objects, it means decrementing refcounts and
|
|
81
|
+
dropping vdata pillars, which might trigger more deletions. For
|
|
82
|
+
logical objects that have been imported from the kernel, it must
|
|
83
|
+
also notify the kernel of the transition from "reachable" status to
|
|
84
|
+
"unreachable", so the kernel can propagate the event outwards to the
|
|
85
|
+
upstream vat that exported the object, deleting table entries all
|
|
86
|
+
the way.
|
|
87
|
+
|
|
88
|
+
Objects which are unreachable by our vat might still be reachable by
|
|
89
|
+
others, or by something in the exporting vat, so becoming
|
|
90
|
+
unreachable is not the end of the story. The logical object might
|
|
91
|
+
still be *recognizable* by our vat, as a key in one or more weak
|
|
92
|
+
collections. While in this VREF-RECOGNIZABLE state, three things
|
|
93
|
+
might happen:
|
|
94
|
+
|
|
95
|
+
* The owner, or someone who still has access, may send it to us in a
|
|
96
|
+
message. The vat code receives a newly-minted Presence or
|
|
97
|
+
Representative object, and now the logical object is
|
|
98
|
+
VREF-REACHABLE once more.
|
|
99
|
+
* The owner may declare the object "retired", meaning they've
|
|
100
|
+
deleted it. We should drop our matching WeakMap entries, and free
|
|
101
|
+
their data (which might make other objects become
|
|
102
|
+
unreachable). This is triggered by "dispatch.retireImports".
|
|
103
|
+
* We might delete our last WeakMap that recognized the vref,
|
|
104
|
+
allowing us to tell the kernel that we don't care about the vref
|
|
105
|
+
anymore, so it can remove the bookkeeping records. This uses
|
|
106
|
+
"possiblyRetiredSet", and may invoke "syscall.retireImports".
|
|
107
|
+
|
|
108
|
+
We track recognizability status using "recognizer records". When the
|
|
109
|
+
recognizer is a virtual/durable collection or object, the record is
|
|
110
|
+
stored in the vatstore DB.
|
|
111
|
+
|
|
112
|
+
The kernel tracks the vat's imported vrefs with an "import status",
|
|
113
|
+
one of IMPORT-REACHABLE, IMPORT-RECOGNIZABLE, or nothing. This
|
|
114
|
+
status is stored in the c-list entry, and is not visible to the
|
|
115
|
+
vat. It changes when the vat receives a vref in a delivery, or
|
|
116
|
+
performs syscall.dropImports or syscall.retireImports, or receives a
|
|
117
|
+
dispatch.retireImports.
|
|
118
|
+
|
|
119
|
+
/*
|
|
120
|
+
Theory of Operation (imports/Presences)
|
|
121
|
+
|
|
122
|
+
This describes the states that a Presence `Object` might be in, and
|
|
123
|
+
how we track changes in that status. The Presence forms the "RAM
|
|
124
|
+
pillar" that may support the logical object (vref).
|
|
125
|
+
|
|
126
|
+
A Presence object is in one of 5 states: UNKNOWN, REACHABLE,
|
|
127
|
+
UNREACHABLE, COLLECTED, FINALIZED. Note that there's no actual state
|
|
128
|
+
machine with those values, and we can't observe all of the
|
|
129
|
+
transitions from JavaScript, but we can describe what operations
|
|
130
|
+
could cause a transition, and what our observations allow us to
|
|
131
|
+
deduce about the state:
|
|
132
|
+
|
|
133
|
+
* UNKNOWN moves to REACHABLE when a crank introduces a new import
|
|
134
|
+
* userspace holds a reference only in REACHABLE
|
|
135
|
+
* REACHABLE moves to UNREACHABLE only during a userspace crank
|
|
136
|
+
* UNREACHABLE moves to COLLECTED when engine GC runs, which queues the finalizer
|
|
137
|
+
* COLLECTED moves to FINALIZED when a new turn runs the finalizer
|
|
138
|
+
* FINALIZED moves to UNKNOWN when liveslots sends a dropImports syscall
|
|
139
|
+
|
|
140
|
+
convertSlotToVal either imports a vref for the first time, or
|
|
141
|
+
re-introduces a previously-seen vref. It transitions from:
|
|
142
|
+
|
|
143
|
+
* UNKNOWN to REACHABLE by creating a new Presence
|
|
144
|
+
* UNREACHABLE to REACHABLE by re-using the old Presence that userspace
|
|
145
|
+
forgot about
|
|
146
|
+
* COLLECTED/FINALIZED to REACHABLE by creating a new Presence
|
|
147
|
+
|
|
148
|
+
Our tracking tables hold data that depends on the current state:
|
|
149
|
+
|
|
150
|
+
* slotToVal holds a WeakRef only in [REACHABLE, UNREACHABLE, COLLECTED]
|
|
151
|
+
* that WeakRef .deref()s into something only in [REACHABLE, UNREACHABLE]
|
|
152
|
+
* possiblyDeadSet holds the vref only in FINALIZED
|
|
153
|
+
* (TODO) re-introduction could remove the vref from possiblyDeadSet
|
|
154
|
+
|
|
155
|
+
Each state thus has a set of perhaps-measurable properties:
|
|
156
|
+
|
|
157
|
+
* UNKNOWN: slotToVal[baseRef] is missing, baseRef not in possiblyDeadSet
|
|
158
|
+
* REACHABLE: slotToVal[baseRef] has live weakref, userspace can reach
|
|
159
|
+
* UNREACHABLE: slotToVal[baseRef] has live weakref, userspace cannot reach
|
|
160
|
+
* COLLECTED: slotToVal[baseRef] has dead weakref
|
|
161
|
+
* FINALIZED: slotToVal[baseRef] is missing, baseRef is in possiblyDeadSet
|
|
162
|
+
|
|
163
|
+
Our finalizer callback is queued by the engine's transition from
|
|
164
|
+
UNREACHABLE to COLLECTED, but the baseRef might be re-introduced
|
|
165
|
+
before the callback has a chance to run. There might even be
|
|
166
|
+
multiple copies of the finalizer callback queued. So the callback
|
|
167
|
+
must deduce the current state and only perform cleanup (i.e. delete
|
|
168
|
+
the slotToVal entry and add the baseRef to possiblyDeadSet) in the
|
|
169
|
+
COLLECTED state.
|
|
170
|
+
|
|
171
|
+
Our general rule is "trust the finalizer". The GC code below
|
|
172
|
+
considers a Presence to be reachable (the vref's "RAM pillar"
|
|
173
|
+
remains "up") until it moves to the FINALIZED state. We do this to
|
|
174
|
+
avoid race conditions between some other pillar dropping (and a BOYD
|
|
175
|
+
sampling the WeakRef) while it is in the COLLECTED state. If we
|
|
176
|
+
treated COLLECTED as the RAM pillar being "down"), then the
|
|
177
|
+
subsequent finalizer callback would examine the vref a second time,
|
|
178
|
+
potentially causing a vat-fatal double syscall.dropImports. This
|
|
179
|
+
rule may change if/when we use FinalizationRegistry better, by
|
|
180
|
+
explicitly de-registering the vref when we drop it, which JS
|
|
181
|
+
guarantees will remove and pending callback from the queue. This may
|
|
182
|
+
help us avoid probing the WeakRef during BOYD (instead relying upon
|
|
183
|
+
the fired/not-fired state of the FR), since that probing can cause
|
|
184
|
+
engines to retain objects longer than necessary.
|
|
185
|
+
|
|
186
|
+
*/
|
|
187
|
+
|
|
188
|
+
/*
|
|
189
|
+
Additional notes:
|
|
190
|
+
|
|
191
|
+
There are three categories of vrefs:
|
|
192
|
+
* Presence-style (o-NN, imports, durable)
|
|
193
|
+
* Remotable-style (o+NN, exportable, ephemeral)
|
|
194
|
+
* Representative-style (o+vKK/II or o+dKK/II, exportable, virtual/durable)
|
|
195
|
+
|
|
196
|
+
We don't necessarily have a Presence for every o-NN vref that the
|
|
197
|
+
vat can reach, because the vref might be held in virtual/durable
|
|
198
|
+
data ("vdata") while the in-RAM `Presence` object was
|
|
199
|
+
dropped. Likewise the in-RAM `Representative` can be dropped while
|
|
200
|
+
the o+dKK/II vref is kept VREF-REACHABLE by either vdata or an
|
|
201
|
+
export to the kernel. We *do* always have a Remotable for every o+NN
|
|
202
|
+
vref that the vat knows about, because Remotables are ephemeral.
|
|
203
|
+
|
|
204
|
+
The vat does not record any information about the kernel-facing
|
|
205
|
+
import status (c-list state) for Presence-style vrefs (o-NN), and
|
|
206
|
+
cannot ask the kernel for it, so we rely upon the invariant that you
|
|
207
|
+
only add a vref to possiblyDeadSet if it was VREF-REACHABLE
|
|
208
|
+
first. That way, possiblyDeadSet.has(vref) means that the c-list
|
|
209
|
+
import status was IMPORT-REACHABLE. Likewise, code should only add
|
|
210
|
+
to possiblyRetiredSet if the vref was at least VREF-RECOGNIZABLE
|
|
211
|
+
beforehand, meaning the c-list status was at least
|
|
212
|
+
IMPORT-RECOGNIZABLE. This helps us avoid a vat-fatal duplicate
|
|
213
|
+
dropImports or retireImports syscall.
|
|
214
|
+
|
|
215
|
+
For imports, the lifetime is controlled by the upstream vat: we
|
|
216
|
+
might drop VREF-REACHABLE today and maintain VREF-RECOGNIZABLE for
|
|
217
|
+
days until the object is finally retired. For exports *we* control
|
|
218
|
+
the lifetime, so when we determine an export is no longer
|
|
219
|
+
VREF-REACHABLE, we delete it and retire the vref immediately, and it
|
|
220
|
+
does not observably linger in the VREF-RECOGNIZABLE state. This
|
|
221
|
+
simplifies our tracking, and allows the deletion of Remotables and
|
|
222
|
+
Representative-type vrefs to be idempotent.
|
|
223
|
+
|
|
224
|
+
Each vref's reachability status is determined by a set of
|
|
225
|
+
"pillars". For Presence-style vrefs, there are two: the RAM pillar
|
|
226
|
+
(the `Presence` object), and the vdata pillar. The vdata pillar is
|
|
227
|
+
tracked in a vatstore key named `vom.rc.${vref}`, which stores the
|
|
228
|
+
reachable/recognizable refcounts.
|
|
229
|
+
|
|
230
|
+
For Representative-style vrefs, we add the export-status pillar,
|
|
231
|
+
because anything that we've exported to the kernel must be kept
|
|
232
|
+
alive until the kernel issues a dispatch.dropExports. That gives us
|
|
233
|
+
three pillars:
|
|
234
|
+
* the RAM pillar is the `Representative` object
|
|
235
|
+
* the vdata pillar is stored in `vom.rc.${vref}`
|
|
236
|
+
* the export-status pillar is stored in `vom.es.${vref}`
|
|
237
|
+
|
|
238
|
+
Remotables have only the RAM pillar. When a Remotable-style vref is
|
|
239
|
+
exported to the kernel, the Remotable is added to the
|
|
240
|
+
`exportedRemotables` set. And when it is stored in vdata, it appears
|
|
241
|
+
as a key of the `remotableRefCounts` map. That keeps the Remotable
|
|
242
|
+
itself alive until the other reachability pathways have gone
|
|
243
|
+
away. We don't do this for Representatives because it would violate
|
|
244
|
+
our "don't use RAM for inactive virtual objects" rule.
|
|
245
|
+
|
|
246
|
+
When an imported Presence becomes VREF-UNREACHABLE, it might still
|
|
247
|
+
be VREF-RECOGNIZABLE, by virtue of being the key of one or more weak
|
|
248
|
+
collections. If not, it might transit from VREF-REACHABLE directly
|
|
249
|
+
to NONE. The code that reacts to the VREF-UNREACHABLE transition
|
|
250
|
+
must check for recognizers, and do a retireImports right away if
|
|
251
|
+
there are none. Otherwise, recognizer will remain until either the
|
|
252
|
+
kernel retires the object (dispatch.retireImports), or the weak
|
|
253
|
+
collection is deleted, in which case possiblyRetiredSet will be
|
|
254
|
+
updated with the vref that might no longer be recognized. There will
|
|
255
|
+
be a race between us ceasing to recognize the vref (which should
|
|
256
|
+
trigger a syscall.retireImports), and the kernel telling us the
|
|
257
|
+
object has been deleted (via dispatch.retireImports). Either one
|
|
258
|
+
must inhibit the other.
|
|
259
|
+
|
|
260
|
+
possiblyRetiredSet only cares about Presence-style vrefs, because
|
|
261
|
+
they represent imports, whose lifetime is not under our control. The
|
|
262
|
+
collection-deletion code will add Remotable- and Representative-
|
|
263
|
+
style vrefs in possiblyRetiredSet, but we can remove and ignore
|
|
264
|
+
them.
|
|
265
|
+
|
|
266
|
+
We use slotToVal.has(vref) everywhere for our "is it still
|
|
267
|
+
reachable" check, which returns true for the Presence's REACHABLE /
|
|
268
|
+
UNREACHABLE / COLLECTED states, and false for the FINALIZED
|
|
269
|
+
state. In contrast, getValForSlot(vref) returns false for both
|
|
270
|
+
COLLECTED and FINALIZED. We want COLLECTED to qualify as "still
|
|
271
|
+
reachable" because it means there's still a finalizer callback
|
|
272
|
+
queued, which will be run eventually, and we need that callback to
|
|
273
|
+
not trigger a duplicate drop. We use slotToVal.has() in the
|
|
274
|
+
possiblyRetiredSet loop (to inhibit retirement of imported vrefs
|
|
275
|
+
whose Presences are in the COLLECTED state, and which just lost a
|
|
276
|
+
recognizer), because getValForSlot would allow such COLLECTED vrefs
|
|
277
|
+
to be retired, even before the finalizer had fired and could do a
|
|
278
|
+
dropImports.
|
|
279
|
+
|
|
280
|
+
When we decide to delete a virtual object, we will delete its
|
|
281
|
+
`state`, decrementing the refcounts of any objects therein, which
|
|
282
|
+
might shake loose more data. So we keep looping until we've stopped
|
|
283
|
+
adding things to possiblyDeadSet. The same can happen when we use
|
|
284
|
+
vrm.ceaseRecognition() to delete any weak collection values keyed by
|
|
285
|
+
it. We also call ceaseRecognition when we realize that a Remotable
|
|
286
|
+
has been deleted. But the possiblyDeadSet processing loop cannot
|
|
287
|
+
make the decision to retire a Presence-style vref: those are deleted
|
|
288
|
+
outside our vat, and the kernel notifies us of the vref's retirement
|
|
289
|
+
with dispatch.retireImports (which also calls ceaseRecognition). The
|
|
290
|
+
only thing possiblyDeadSet can tell us about Presences is that our
|
|
291
|
+
vat can no longer *reach* the vref, which means we need to do a
|
|
292
|
+
syscall.dropImports, which cannot immediately release more data.
|
|
293
|
+
|
|
294
|
+
When the kernel sends us a dispatch.bringOutYourDead (or "BOYD" for
|
|
295
|
+
short), this scanForDeadObjects() will be called. This is the only
|
|
296
|
+
appropriate time for the syscall behavior to depend upon engine GC
|
|
297
|
+
behavior: during all other deliveries, we want the syscalls to be a
|
|
298
|
+
deterministic function of delivery contents, userspace behavior, and
|
|
299
|
+
vatstore data.
|
|
300
|
+
|
|
301
|
+
During BOYD, we still try to minimize the variation of behavior as
|
|
302
|
+
much as possible. The first step is to ask the engine to perform a
|
|
303
|
+
full GC sweep, to collect any remaining UNREACHABLE objects, and
|
|
304
|
+
allow the finalizer callbacks to run before looking at the
|
|
305
|
+
results. We also sort the vrefs before processing them, to remove
|
|
306
|
+
sensitivity to the exact order of finalizer invocation.
|
|
307
|
+
|
|
308
|
+
That makes BOYD a safe time to look inside WeakRefs and make
|
|
309
|
+
syscalls based on the contents, or to read the sets that are
|
|
310
|
+
modified during FinalizationRegistry callbacks and make syscalls to
|
|
311
|
+
query their state further. This this is the only time we examine and
|
|
312
|
+
clear possiblyDeadSet and possiblyRetiredSet, or probe
|
|
313
|
+
slotToVal. Outside of BOYD, in convertSlotToVal, we must probe the
|
|
314
|
+
WeakRefs to see whether we must build a new Presence or
|
|
315
|
+
Representative, or not, but we have carefully designed that code to
|
|
316
|
+
avoid making syscalls during the unmarshalling process, so the only
|
|
317
|
+
consequence of GC differences should be differences in metering and
|
|
318
|
+
memory allocation patterns.
|
|
319
|
+
|
|
320
|
+
Our general strategy is to look at the baseRefs/vrefs whose state
|
|
321
|
+
might have changed, determine their new reachability /
|
|
322
|
+
recognizability status, and then resolve any discrepancies between
|
|
323
|
+
that status and that of other parties who need to match.
|
|
324
|
+
|
|
325
|
+
The kernel is one such party. If the kernel thinks we can reach an
|
|
326
|
+
imported o-NN vref, but we've now determined that we cannot, we must
|
|
327
|
+
send a syscall.dropImports to resolve the difference. Once sent, the
|
|
328
|
+
kernel will update our c-list entry to reflect the unreachable (but
|
|
329
|
+
still recognizable) status. Likewise, if the kernel thinks *it* can
|
|
330
|
+
recognize an exported o+NN vref, but we've just retired it, we need
|
|
331
|
+
to update the kernel with a syscall.retireExports, so it can notify
|
|
332
|
+
downstream vats that have weak collections with our vref as a key.
|
|
333
|
+
|
|
334
|
+
The DB-backed `state` of a virtual object is another such party. If
|
|
335
|
+
the object is unreachable, but still has state data, we must delete
|
|
336
|
+
that state, and decrement refcounts it might have held.
|
|
337
|
+
|
|
338
|
+
Our plan is summarized as:
|
|
339
|
+
* outer loop
|
|
340
|
+
* gcAndFinalize
|
|
341
|
+
* sort possiblyDeadSet, examine each by type
|
|
342
|
+
* all: remove from possiblyDeadSet
|
|
343
|
+
* presence (vref):
|
|
344
|
+
* if unreachable:
|
|
345
|
+
* dropImports
|
|
346
|
+
* add to possiblyRetiredSet
|
|
347
|
+
* remotable (vref):
|
|
348
|
+
* if unreachable:
|
|
349
|
+
* retireExports if kernelRecognizableRemotables
|
|
350
|
+
* vrm.ceaseRecognition
|
|
351
|
+
* VOM (baseRef)
|
|
352
|
+
* if unreachable:
|
|
353
|
+
* deleteVirtualObject (and retireExports retirees)
|
|
354
|
+
* repeat loop if gcAgain or possiblyDeadSet.size > 0
|
|
355
|
+
* now sort and process possiblyRetiredSet. for each:
|
|
356
|
+
* ignore unless presence
|
|
357
|
+
* if unreachable and unrecognizable: retireImport
|
|
358
|
+
(that's a duplicate reachability check, but note the answer might
|
|
359
|
+
be different now)
|
|
360
|
+
*/
|
|
361
|
+
|
|
362
|
+
/*
|
|
363
|
+
BaseRef vs vref
|
|
364
|
+
|
|
365
|
+
For multi-faceted virtual/durable objects (eg `defineKind()` with
|
|
366
|
+
faceted `behavior` argument), each time userspace create a new
|
|
367
|
+
instance, we create a full "cohort" of facets, passing a record of
|
|
368
|
+
Representative objects (one per facet) back to the caller. Each
|
|
369
|
+
facet gets its own vref, but they all share a common prefix, known
|
|
370
|
+
as the "baseRef". For example, `o+d44/2` is a BaseRef for a cohort,
|
|
371
|
+
the second instance created of the `o+d44` Kind, whose individual
|
|
372
|
+
facets would have vrefs of `o+d44/2:0` and `o+d44/2:1`.
|
|
373
|
+
|
|
374
|
+
We use a WeakMap to ensure that holding any facet will keep all the
|
|
375
|
+
others alive, so the cohort lives and dies as a group. The GC
|
|
376
|
+
tracking code needs to track the whole cohort at once, not the
|
|
377
|
+
individual facets, and any data structure which refers to cohorts
|
|
378
|
+
will use the BaseRef instead of a single vref. So `slotToVal` is
|
|
379
|
+
keyed by a BaseRef, and its values are a cohort of facets. But
|
|
380
|
+
`valToSlot` is keyed by the facets, and its values are the
|
|
381
|
+
individual facet's vref.
|
|
382
|
+
|
|
383
|
+
For Presence- and Remotable- style objects, the baseRef is just the
|
|
384
|
+
vref (i.e., every baseRef is either a cohort-identifying prefix or
|
|
385
|
+
an isolated-object vref, and every vref either has a baseRef prefix
|
|
386
|
+
and identifies one facet of a cohort or has no such prefix and
|
|
387
|
+
identifies an isolated object).
|
|
388
|
+
|
|
389
|
+
Most of the GC-related APIs that appear here take vrefs, but the
|
|
390
|
+
exceptions are:
|
|
391
|
+
* slotToVal is keyed by BaseRef
|
|
392
|
+
* possiblyDeadSet holds BaseRefs, that's what our WeakRefs track
|
|
393
|
+
* vrm.isVirtualObjectReachable takes baseRef
|
|
394
|
+
* vrm.deleteVirtualObject takes baseRef, returns [bool, retireees=vrefs]
|
|
395
|
+
* vrm.ceaseRecognition takes either baseRef or vref
|
|
396
|
+
(if given a baseRef, it will process all the facets)
|
|
397
|
+
|
|
398
|
+
*/
|
|
399
|
+
|
|
400
|
+
export const makeBOYDKit = ({
|
|
401
|
+
gcTools,
|
|
402
|
+
slotToVal,
|
|
403
|
+
vrm,
|
|
404
|
+
kernelRecognizableRemotables,
|
|
405
|
+
syscall,
|
|
406
|
+
possiblyDeadSet,
|
|
407
|
+
possiblyRetiredSet,
|
|
408
|
+
}) => {
|
|
409
|
+
// Representative (o+dNN/II or o+vNN/II) lifetimes are also
|
|
410
|
+
// controlled by us. We allow the Representative object to go away
|
|
411
|
+
// without deleting the vref, so we must track all three pillars:
|
|
412
|
+
// Representative (RAM), export, and vdata. When we decide the vref
|
|
413
|
+
// is unreachable, we must delete the virtual object's state, as
|
|
414
|
+
// well as retiring the object (by telling the kernel it has been
|
|
415
|
+
// retired, if the kernel cares, and removing any local recognition
|
|
416
|
+
// records).
|
|
417
|
+
|
|
418
|
+
const checkExportRepresentative = baseRef => {
|
|
419
|
+
// RAM pillar || (vdata pillar || export pillar)
|
|
420
|
+
const isReachable =
|
|
421
|
+
slotToVal.has(baseRef) || vrm.isVirtualObjectReachable(baseRef);
|
|
422
|
+
let gcAgain = false;
|
|
423
|
+
let exportsToRetire = [];
|
|
424
|
+
if (!isReachable) {
|
|
425
|
+
// again, we own the object, so we retire it immediately
|
|
426
|
+
[gcAgain, exportsToRetire] = vrm.deleteVirtualObject(baseRef);
|
|
427
|
+
}
|
|
428
|
+
return { gcAgain, exportsToRetire };
|
|
429
|
+
};
|
|
430
|
+
|
|
431
|
+
// Remotable (o+NN) lifetimes are controlled by us: we delete/retire
|
|
432
|
+
// the object as soon as it becomes unreachable. We only track the
|
|
433
|
+
// Remotable/Far object (the RAM pillar) directly: exports retain
|
|
434
|
+
// the Remotable in the exportedRemotables set, and vdata retains it
|
|
435
|
+
// as keys of the remotableRefCounts map. So when we decide the vref
|
|
436
|
+
// is unreachable, the Remotable is already gone, and it had no
|
|
437
|
+
// other data we need to delete, so our task is to remove any local
|
|
438
|
+
// recognition records, and to inform the kernel with a
|
|
439
|
+
// retireExports if kernelRecognizableRemotables says that the
|
|
440
|
+
// kernel still cares.
|
|
441
|
+
//
|
|
442
|
+
// note: we track export status for remotables in the
|
|
443
|
+
// kernelRecognizableRemotables set, not vom.es.VREF records. We
|
|
444
|
+
// don't currently track recognition records with
|
|
445
|
+
// vom.ir.VREF|COLLECTION, but we should, see #9956
|
|
446
|
+
|
|
447
|
+
const checkExportRemotable = vref => {
|
|
448
|
+
let gcAgain = false;
|
|
449
|
+
let exportsToRetire = [];
|
|
450
|
+
|
|
451
|
+
// Remotables have only the RAM pillar
|
|
452
|
+
const isReachable = slotToVal.has(vref);
|
|
453
|
+
if (!isReachable) {
|
|
454
|
+
// We own the object, so retire it immediately. If the kernel
|
|
455
|
+
// was recognizing it, we tell them it is now retired
|
|
456
|
+
if (kernelRecognizableRemotables.has(vref)) {
|
|
457
|
+
kernelRecognizableRemotables.delete(vref);
|
|
458
|
+
exportsToRetire = [vref];
|
|
459
|
+
// the kernel must not have been able to reach the object,
|
|
460
|
+
// else it would still be pinned by exportedRemotables
|
|
461
|
+
}
|
|
462
|
+
// and remove it from any local weak collections
|
|
463
|
+
gcAgain = vrm.ceaseRecognition(vref);
|
|
464
|
+
}
|
|
465
|
+
return { gcAgain, exportsToRetire };
|
|
466
|
+
};
|
|
467
|
+
|
|
468
|
+
// Presence (o-NN) lifetimes are controlled by the upstream vat, or
|
|
469
|
+
// the kernel. If the vref was in possiblyDeadSet, then it *was*
|
|
470
|
+
// reachable before, so we can safely presume the kernel to think we
|
|
471
|
+
// can reach it.
|
|
472
|
+
|
|
473
|
+
const checkImportPresence = vref => {
|
|
474
|
+
// RAM pillar || vdata pillar
|
|
475
|
+
// use slotToVal.has, not getSlotForVal(), to avoid duplicate drop
|
|
476
|
+
const isReachable = slotToVal.has(vref) || vrm.isPresenceReachable(vref);
|
|
477
|
+
let dropImport;
|
|
478
|
+
if (!isReachable) {
|
|
479
|
+
dropImport = vref;
|
|
480
|
+
}
|
|
481
|
+
return { dropImport };
|
|
482
|
+
};
|
|
483
|
+
|
|
484
|
+
const scanForDeadObjects = async () => {
|
|
485
|
+
await null;
|
|
486
|
+
|
|
487
|
+
// `possiblyDeadSet` holds vrefs which have lost a supporting
|
|
488
|
+
// pillar (in-memory, export, or virtualized data refcount) since
|
|
489
|
+
// the last call to scanForDeadObjects. The vref might still be
|
|
490
|
+
// supported by a remaining pillar, or the pillar which was
|
|
491
|
+
// dropped might have been restored (e.g., re-exported after a
|
|
492
|
+
// drop, or given a new in-memory manifestation).
|
|
493
|
+
|
|
494
|
+
const importsToDrop = new Set();
|
|
495
|
+
const importsToRetire = new Set();
|
|
496
|
+
const exportsToRetire = new Set();
|
|
497
|
+
let gcAgain = false;
|
|
498
|
+
|
|
499
|
+
do {
|
|
500
|
+
gcAgain = false;
|
|
501
|
+
await gcTools.gcAndFinalize();
|
|
502
|
+
|
|
503
|
+
// process a sorted list of vref/baseRefs we need to check for
|
|
504
|
+
// reachability, one at a time
|
|
505
|
+
|
|
506
|
+
for (const vrefOrBaseRef of [...possiblyDeadSet].sort()) {
|
|
507
|
+
// remove the vref now, but some deleteVirtualObject might
|
|
508
|
+
// shake it loose again for a future pass to investigate
|
|
509
|
+
possiblyDeadSet.delete(vrefOrBaseRef);
|
|
510
|
+
|
|
511
|
+
const parsed = parseVatSlot(vrefOrBaseRef);
|
|
512
|
+
assert.equal(parsed.type, 'object', vrefOrBaseRef);
|
|
513
|
+
|
|
514
|
+
let res = {};
|
|
515
|
+
if (parsed.virtual || parsed.durable) {
|
|
516
|
+
const baseRef = vrefOrBaseRef;
|
|
517
|
+
res = checkExportRepresentative(baseRef);
|
|
518
|
+
} else if (parsed.allocatedByVat) {
|
|
519
|
+
const vref = vrefOrBaseRef;
|
|
520
|
+
res = checkExportRemotable(vref);
|
|
521
|
+
} else {
|
|
522
|
+
const vref = vrefOrBaseRef;
|
|
523
|
+
res = checkImportPresence(vref);
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
// prepare our end-of-crank syscalls
|
|
527
|
+
if (res.dropImport) {
|
|
528
|
+
importsToDrop.add(res.dropImport);
|
|
529
|
+
possiblyRetiredSet.add(res.dropImport);
|
|
530
|
+
}
|
|
531
|
+
for (const facetVref of res.exportsToRetire || []) {
|
|
532
|
+
exportsToRetire.add(facetVref);
|
|
533
|
+
}
|
|
534
|
+
gcAgain ||= !!res.gcAgain;
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
// Deleting virtual object state, or freeing weak-keyed
|
|
538
|
+
// collection entries, might shake loose more
|
|
539
|
+
// objects. possiblyDeadSet and possiblyRetiredSet are added
|
|
540
|
+
// when a vdata vref decrefs to zero, and gcAgain means that
|
|
541
|
+
// something in RAM might now be free. In both cases we should
|
|
542
|
+
// do another pass, including gcAndFinalize(), until we've
|
|
543
|
+
// cleared everything we can.
|
|
544
|
+
} while (possiblyDeadSet.size > 0 || gcAgain);
|
|
545
|
+
|
|
546
|
+
// Now we process potential retirements, by which we really mean
|
|
547
|
+
// de-recognitions, where this vat has ceased to even recognize a
|
|
548
|
+
// previously unreachable-yet-recognizable
|
|
549
|
+
// vref. addToPossiblyRetiredSet() is called from
|
|
550
|
+
// ceaseRecognition() when a recognizer goes away, such when a
|
|
551
|
+
// weak collection being deleted and it no longer recognizes all
|
|
552
|
+
// its former keys. ceaseRecognition() can be called from the loop
|
|
553
|
+
// above (when a Remotable-style object is deleted, or from within
|
|
554
|
+
// deleteVirtualObject), or in response to a retireImport()
|
|
555
|
+
// delivery. We assume possiblyRetiredSet is given vrefs of all
|
|
556
|
+
// sorts, but we only care about Presence-type, because we must do
|
|
557
|
+
// retireImports for them: the kernel doesn't care if/when we stop
|
|
558
|
+
// recognizing our own (maybe-exported) Remotable- and
|
|
559
|
+
// Representative- type vrefs.
|
|
560
|
+
|
|
561
|
+
for (const vref of [...possiblyRetiredSet].sort()) {
|
|
562
|
+
possiblyRetiredSet.delete(vref);
|
|
563
|
+
const parsed = parseVatSlot(vref);
|
|
564
|
+
assert.equal(parsed.type, 'object', vref);
|
|
565
|
+
// ignore non-Presences
|
|
566
|
+
if (parsed.allocatedByVat) continue;
|
|
567
|
+
|
|
568
|
+
// if we're dropping the vref, checkImportPresence() already
|
|
569
|
+
// did our isReachable check, so we can safely skip it (and
|
|
570
|
+
// save a vatstore syscall)
|
|
571
|
+
const isReachable =
|
|
572
|
+
!importsToDrop.has(vref) &&
|
|
573
|
+
// Use slotToVal.has, not getValForSlot(), to avoid retirement
|
|
574
|
+
// before the finalizer fires and does dropImport
|
|
575
|
+
(slotToVal.has(vref) || vrm.isPresenceReachable(vref));
|
|
576
|
+
const isRecognizable = isReachable || vrm.isVrefRecognizable(vref);
|
|
577
|
+
if (!isRecognizable) {
|
|
578
|
+
importsToRetire.add(vref);
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
// note that retiring Presence-type vrefs cannot shake loose any
|
|
583
|
+
// local data, so we don't need to loop back around
|
|
584
|
+
|
|
585
|
+
if (importsToDrop.size) {
|
|
586
|
+
syscall.dropImports([...importsToDrop].sort());
|
|
587
|
+
}
|
|
588
|
+
if (importsToRetire.size) {
|
|
589
|
+
syscall.retireImports([...importsToRetire].sort());
|
|
590
|
+
}
|
|
591
|
+
if (exportsToRetire.size) {
|
|
592
|
+
syscall.retireExports([...exportsToRetire].sort());
|
|
593
|
+
}
|
|
594
|
+
};
|
|
595
|
+
|
|
596
|
+
return { scanForDeadObjects };
|
|
597
|
+
};
|
|
598
|
+
harden(makeBOYDKit);
|
package/src/cache.js
CHANGED
package/src/capdata.js
CHANGED