@agoric/swingset-liveslots 0.10.3-u16.1 → 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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agoric/swingset-liveslots",
3
- "version": "0.10.3-u16.1",
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/assert": "^0.6.1-u16.0",
21
- "@agoric/internal": "^0.4.0-u16.1",
22
- "@agoric/store": "^0.9.3-u16.0",
23
- "@endo/env-options": "^1.1.4",
24
- "@endo/errors": "^1.2.2",
25
- "@endo/eventual-send": "^1.2.2",
26
- "@endo/exo": "^1.5.0",
27
- "@endo/far": "^1.1.2",
28
- "@endo/init": "^1.1.2",
29
- "@endo/marshal": "^1.5.0",
30
- "@endo/nat": "^5.0.7",
31
- "@endo/pass-style": "^1.4.0",
32
- "@endo/patterns": "^1.4.0",
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-u16.0",
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.29
69
+ "atLeast": 75.1
71
70
  },
72
- "gitHead": "2eaeab90b4015e355faea534c1c933351d25b1b9"
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
@@ -1,4 +1,4 @@
1
- import { Fail } from '@agoric/assert';
1
+ import { Fail } from '@endo/errors';
2
2
 
3
3
  /**
4
4
  * @template V
package/src/capdata.js CHANGED
@@ -1,4 +1,4 @@
1
- import { Fail } from '@agoric/assert';
1
+ import { Fail } from '@endo/errors';
2
2
 
3
3
  /**
4
4
  * Assert function to ensure that something expected to be a capdata object