-
Notifications
You must be signed in to change notification settings - Fork 207
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
vref-aware GC design (was: WeakMap + garbage-collected Presences = non-determinism) #2724
Comments
We should talk through how it worked in E and how it is different. Generally, for objects that had access to anything through CapTP, we assumed it had access to non-determinism, and were not trying to encapsulate all non-determinism within CapTP. Also, E's passByCopy objects had no identity. Like JS strings, there was no way to distinguish a passByCopy object from a copy of it. Nevertheless, it will probably be illuminating to walk through these issues in E. |
Hm.. was the non-determinism they could sense (via CapTP) limited to arrival-order non-determinism? I was hoping to salvage that here, but I'm not seeing a way to do it, since the retain/drop difference causes messages to be sent from the vat, rather than being triggered by messages coming into it. |
Good question! Maybe yes. It'll be interesting to examine and compare in depth. Looking forward! |
We shim WeakMap/WeakSet so that either you can't put a virtualObject into one, or it makes the VO stay in memory. Programmer: it hurts when I do this. |
(argh, @dtribble beat me to it by mere seconds. posting anways.) For vats which wish to behave, we could enhance our virtual-object-aware And of course that doesn't help protect against adversarial vats at all, unless we restrict access to Ok, we could do that, those are Compartment globals and we can shim them as much as we want. We could give vats a wrapped form which throws if you ever try to use a Presence as a key. Since we withhold We've considered making the virtual-object-aware |
Right, so the real "raise you one more" is to modify the global Well, plus we add |
This is looking good. So liveSlots would patch the vat's
I'd prefer the global |
That'd work.. both would have the same vref-aware identity function, by virtue of one being implemented in terms of the other, but the external API would be up to the caller's tastes. It feels kind of invasive, modifying something as built-in as
and the "XXX" box would be a constructor function that returns an object (with behavior) that closes over some per-instance state variables. Our "special vat environment" would be reached through a transform that converts that pattern into calls to |
Just to be clear, we don't modify it, we provide a different one in the nested scope. That way the infrastructure isn't paying any overhead for it. Few contracts require them. |
I'll agree that we're not modifying the I'd disagree that "few contracts require" weak tables. They seem to be used extensively for rights-amplification patterns. The properties that make them useful for rights-amplification patterns are worth looking at. A
The rights-amplification utility comes from the non-enumerability. The weak key and automatic value deallocation is a nice side-effect, and occasionally necessary to avoid accumulating junk, because you might never again get access to the key which you'd need to do a The special Users expect both Map and WeakMap to be "durable", in the sense that submitting a key now and submitting it again later will both get you the same value. Regular WeakMaps use the JS object identity as the key. We're talking about a tool that uses the "Swingset object identity" as the key. For a Presence that outlives its use in the weakmap, this is equivalent to the vref (e.g.
But when we introduce GC, we're saying that when the importing vat drops the last strong reference to Presence-1, the kernel is allowed to drop the c-list entry:
.. and then if someone (perhaps the exporting vat) re-sends that same object, resulting in a new Presence object, that Presence is supposed to be recognized as mapping to the same value as before:
Moreover, if all importers of the object stopped referencing it, and nothing else in the kernel referenced it (e.g. saved promise resolutions, outstanding run-queue message arguments), then the kernel ought to be able to release the exporter's c-list entry as well, and notify the exporting vat (with
If the exporting vat were to re-export the same Remotable, it would get a new export-side
I don't see how to maintain that kind of durability and also be able to delete kernel table entries. The virtual-object manager's So if the upstream export is a virtual object, the vref is stable (if the kernel does Our idea from last night was to use the imported vref as the object key in our special two-phase GCWe might regain some stability if our It might be useful to then use a FinalizationRegistry on that outbound entry and do something in the case that the Remotable went away. Like, telling the kernel that it could drop the kref because it's no longer needed for stability. We could imagine all four c-list entries (importer/exporter vs inbound/outbound) having two states. One would mean "the object is still accessible" and vaguely corresponds to a strong reference. The other means "I might still need the identity" and corresponds to a weak reference. If the importer drops their Presence, but its vref is still a key in a NonEnumerableMap, then the importing vat does a "soft drop" of the c-list entry, moving it into the second (weaker but still identity-maintaining) state. The kernel counts the strong references, and when they all go away, it does a "soft The c-list entries, and the kernel object table entry, would remain until:
Alternatively, if all vats (exporter and importers) have a weak clist entry, but the exporting vat notices that the Remotable has been collected, then now we know that the vref can never again show up in a message: there's no longer any Remotable that could possibly be translated into that identity. The downstream vats' NonEnumerableMap entries are now unreachable. At this point, we'd like to send some sort of revocation message to them, telling them to delete their c-list entries, and also delete the NonEnumerableMap entries for the late vref (to save space by releasing the value). This reminds me a lot of our analysis on non-deterministic ("formal" vs "local") GC behavior. Instead of formal-vs-local, we've got strong-vs-weak. I suppose this is really just WeakRefs extended across multiple vats, making both the strong-to-weak and weak-to-none transitions be end-to-end visible. |
Here's a design that seems sound to me. GeneralitiesEach layer needs one more degree of freedom than the layer it supports. To perform GC for a vat that can only hold strong references (or none at all), the next layer down must have access to WeakRefs. To perform GC for a vat that can also hold WeakRefs (in the form of a WeakMap), the next layer down must be able to sense the presence or absence of a WeakRef. To do that, we must forbid access to WeakRef, and modify/instrument the WeakMap/WeakSet they get (forbidding access to the native form). A "Swingset Object" is the abstract notion of an object, which can cross vat boundaries. Each one has a specific exporting vat (the "owner"; when someone sends a message to the object, the kernel sends this message to the owner). Each Swingset Object may contain some auxiliary data (which can include other objects). Swingset Objects within the kernel are represented by a kref ( A "JavaScript Object" is the real Importing Vat StatesEach importing vat sees the Swingset Object in one of three states:
The object is in the REACHABLE state if there is a The RECOGNIZABLE state occurs when none of those conditions are true, but the object does appear as a key in any Liveslots uses a When it moves from REACHABLE to RECOGNIZABLE, the vat emits a If the vat receives a When the object is RECOGNIZABLE, it can transition to UNKNOWN in two different ways. The first is provoked locally, when the last recognition predicate goes away. This can happen if a The second is provoked externally, when the kernel sends a When the vat performs a locally-provoked transition from RECOGNIZABLE to UNKNOWN, it will emit a When the vat performss a kernel-provoked transition, it does not need to emit If the vat moves an object from REACHABLE directly to UNKNOWN (because the digraph ImportingVatStates {
REACHABLE
RECOGNIZABLE
UNKNOWN
UNKNOWN -> importing [label="dispatch.deliver"]
importing [shape="box" label="add to table"]
importing -> REACHABLE
REACHABLE -> unreaching [label="(finalized,\nnot a value)\nyes a key"]
unreaching [shape="box" label="delete tables\nsyscall.dropImport"]
unreaching -> RECOGNIZABLE
REACHABLE -> fullydropping [label="(finalized,\nnot a value)\nnot a key"]
fullydropping [shape="box" label="delete tables\nsyscall.dropImport\nsyscall.retireImport"]
fullydropping -> UNKNOWN
RECOGNIZABLE -> unrecognizing [label="no longer a key"]
unrecognizing [shape="box" label="syscall.retireImport"]
unrecognizing -> UNKNOWN
RECOGNIZABLE -> obituarying [label="dispatch.retireImport"]
obituarying [shape="box" label="delete from maps\nfree values"]
obituarying -> UNKNOWN
RECOGNIZABLE -> importing [label="dispatch.deliver"]
} Kernel StatesWithin the kernel, there are five data structures we must pay attention to. All use a "kref' (
The kernel tracks both strong and weak references to each Swingset Object. These references can come from:
The kernel understands the same three states as the importing vat (UNKNOWN, RECOGNIZABLE, REACHABLE), although not necessarily at the same time. An object might be UNKNOWN to one vat but remain RECOGNIZABLE or REACHABLE by the kernel. It might be RECOGNIZABLE to a vat while being REACHABLE to the kernel. If there are any strong references to the kref, the kernel considers the Swingset Object to be REACHABLE. If there are only weak references, it is RECOGNIZABLE. If there are no references, the kref is deleted from the kernel object table (and appears nowhere else) and is considered UNKNOWN. Once a kref is created, any vrefs and c-list entries that use it must be retained until it becomes UNKNOWN. Transitioning from REACHABLE to RECOGNIZABLE is not sufficient to delete the c-list entry, because the object's identity must be preserved, just in case the original exporting vat re-exports the object in the future, and some previously-importing vat is prepared to recognize it. digraph KernelStates {
UNKNOWN
REACHABLE
RECOGNIZABLE
UNKNOWN -> creating [label="exporter syscall mention"]
creating [shape="box" label="create kernel state"]
creating -> REACHABLE
REACHABLE -> check1 [label="importer syscall.dropImport"]
REACHABLE -> check1 [label="delete from kernel state"]
check1 [shape="box" label="remaining strongrefs?"]
check1 -> REACHABLE [label="yes"]
check1 -> dropping [label="no"]
dropping [shape="box" label="exporter dispatch.dropImport"]
dropping -> check2
check2 [shape="box" label="remaining weakrefs?"]
check2 -> RECOGNIZABLE [label="yes"]
check2 -> retiring [label="no"]
RECOGNIZABLE -> check3 [label="importer syscall.retireImport"]
check3 [shape="box" label="remaining weakrefs?"]
check3 -> RECOGNIZABLE [label="yes"]
check3 -> retiring [label="no"]
retiring [shape="box" label="exporter dispatch.retireExport"]
retiring -> deleting
RECOGNIZABLE -> forgetting [label="exporter syscall.retireExport"]
forgetting [shape="box" label="importers: dispatch.retireImport"]
forgetting -> deleting
deleting [shape="box" label="delete kernel state"]
deleting -> UNKNOWN
{rank=same; creating deleting}
} Export-side C-ListA vat exports a new vref by including it in the arguments of a Over time, importing vats will release their strong reference by calling From RECOGNIZABLE, three things might happen. First, the object might get re-exported by the original vat, moving it back to REACHABLE. Second, that vat might emit a The kernel interacts with the exporting vat with the following calls:
digraph ExportingVatCListState {
UNKNOWN
REACHABLE
RECOGNIZABLE
UNKNOWN -> exporting [label="syscall from vat"]
exporting [shape="box" label="add to clist"]
exporting -> REACHABLE
REACHABLE -> dropping [label="kernel moves to RECOGNIZABLE"]
dropping [shape="box" label="dispatch.dropExport\nremove kernel->vat side"]
dropping -> RECOGNIZABLE
RECOGNIZABLE -> losting [label="syscall.retireExport"]
losting [shape="box" label="remove kernel->vat side\nimporters: dispatch.retireImport\ndelete kernel state"]
losting -> UNKNOWN
RECOGNIZABLE -> reexporting [label="syscall from vat"]
reexporting [shape="box" label="replace vat->kernel side"]
reexporting -> REACHABLE
RECOGNIZABLE -> forgetting [label="kernel moves to UNKNOWN"]
forgetting [shape="box" label="delete kernel state\ndispatch.retireExport\nremove kernel->vat side"]
forgetting -> UNKNOWN
{rank=same; dropping reexporting}
} Import-side C-Listdigraph ImportingVatCListState {
UNKNOWN
REACHABLE
RECOGNIZABLE
UNKNOWN -> importing [label="delivery to vat"]
importing [shape="box" label="add to clist"]
importing -> REACHABLE
REACHABLE -> dropping [label="syscall.dropImport"]
dropping [shape="box" label="remove vat->kernel side"]
dropping -> RECOGNIZABLE
RECOGNIZABLE -> ignoring [label="syscall.retireImport"]
ignoring [shape="box" label="remove kernel->vat side\ncheck kernel state"]
ignoring -> UNKNOWN
RECOGNIZABLE -> losting [label="kernel moves to UNKNOWN"]
losting [shape="box" label="dispatch.retireImport\nremove kernel->vat side"]
losting -> UNKNOWN
} Exporting Vat StatesThe exporting vat is where the RemotableWhen the exported object is a real JS While in (externally) RECOGNIZABLE, the exporting vat might retain other strong references to the object, so it is locally still reachable. If the vat ever re-exports ths Remotable, it returns to the (externally) REACHABLE state, and liveslots must put it back in the Another transition out of (externally) RECOGNIZABLE is when the kernel abandons its ability to recognize the object, because all other vats have done the same (with The final transition out of (externally) RECOGNIZABLE is when the exporting vat loses the ability to re-export it. We sense this with the FinalizationRegistry on the Remotable. When this fires, we know that, despite some importing vats still being prepared to recognize a re-export, the exporting vat can no longer generate one. So, just like how a Both the digraph ExportingVatRemotableStates {
REACHABLE [label="(externally)\nREACHABLE"]
RECOGNIZABLE [label="(externally)\nRECOGNIZABLE"]
UNKNOWN
REACHABLE -> unreaching [label="dispatch.dropExport"]
unreaching [shape="box" label="exports.delete"]
unreaching -> RECOGNIZABLE
RECOGNIZABLE -> revoking [label="finalized"]
revoking [shape="box" label="delete tables\nsyscall.retireExport"]
revoking -> UNKNOWN
RECOGNIZABLE -> retaining [label="syscall.send(Remotable)"]
retaining [shape="box" label="exports.add"]
retaining -> REACHABLE
RECOGNIZABLE -> forgotten [label="dispatch.retireExport"]
forgotten [shape="box" label="delete tables"]
forgotten -> UNKNOWN
UNKNOWN -> exporting [label="syscall.send(Remotable)"]
exporting [shape="box" label="add to table"]
exporting -> retaining
} If a Remotable appears in the state data of a virtual object, that is effectively an export: the Virtual ObjectsSwingset's "virtual objects" are designed to allow a vat to avoid retaining one JS Instead of exactly one Issue #1968 argues that each virtual object should have zero or one Representative, where liveslots uses a WeakRef to ensure there are never multiple Representatives. This is a fine idea, and I think will be necessary to make GC work well. The biggest constraint on this whole design is that we do not expose non-determinism to userspace code. The virtual object does not go away when there are no Representatives for it. It is kept alive by remote references, the state data of other virtual objects, the keys of enumerable Maps and Sets (which have been augmented to understand their identity properties), and the values of Map/Set/WeakMap/WeakSet/WeakStore tables. Each virtual object has a single vref associated with it. This vref is allocated the moment the virtual object constructor function is called, and persists until the virtual object is thoroughly gone. There are four sources of strong references to a virtual object:
The sources of weak references are:
Since we index virtual objects by their vref (rather than some other local namespace and a kernel-facing c-list within liveslots), we ignore a (In the comms vat, we use a separate local namespace, and a kernel-facing c-list, because the object can also be known to other remotes. Regular vats have only one peer: the kernel.) When all four sources of strongrefs are gone, the exporting vat can no longer create a Representative for the object, so it can be deleted. Other vats might still have been able to recognize a re-export, but that doesn't matter, since the one such source of re-exports is no longer capable of making one). This occurs when the following conditions are met:
Upon deletion, the vat deletes all the state data for the virtual object (which may trigger more GC activity). Then it sends a So the components of the exporting vat state are:
which gives us In the diagram below, the green nodes are the ideal "resting state", in which we're consuming database space but not RAM. digraph ExportingVatVirtualStates {
// Sabc:
// a: kernel state: S=REACHABLE, W=RECOGNIZABLE, 0=UNKNOWN
// b: local representatives: R=1, 0=0
// c: local state has reference: S=1, 0=0
S000 [label="UNKNOWN\nk: 0, rep: 0, state: 0"]
S000 -> creating [label="create"]
creating [shape="box" label="allocate vref\nsave satte\ninitial Representative\nadd to valToSlot"]
creating -> S010
S010 [label="LOCAL\nk:0, rep: 1, state:0"]
S010 -> exporting1 [label="syscall.send(Rep)"]
exporting1 [shape="box" label="add to slotToVal"]
exporting1 -> SS10
SS10 [label="EXPORTED\nk:S, rep:1, state:0"]
S010 -> S011 [label="store as\nvalue"]
S011 [label="STORED\nk:0, rep:1, state:1"]
S010 -> deallocating [label="finalizer"]
deallocating [shape="box" label="delete state"]
deallocating -> S000
SS10 -> SS11 [label="store as\nvalue"]
SS11 [label="EXPORTED+\nSTORED\nk:S, rep:1, state:1"]
S011 -> exporting2 [label="syscall.send(Rep)"]
exporting2 [shape="box" label="add to slotToVal"]
exporting2 -> SS11
S011 -> S001 [label="finalizer"]
S001 [label="OFFLINE\nk:0, rep:0, state:1" style="filled" fillcolor="#f0fff0"]
S001 -> S011 [label="unserialize"]
S011 -> S010 [label="value stops"]
SS11 -> SS10 [label="value stops"]
SS11 -> SS01 [label="finalizer"]
SS01 [label="EXPORTED+\nSTORED+\nOFFLINE\nk:S, rep:0, state:1" style="filled" fillcolor="#c0ffc0"]
SS01 -> SS11 [label="unserialize"]
SS01 -> SS00 [label="value stops"]
SS00 [label="EXPORTED+\nOFFLINE\nk:S, rep:0, state:0" style="filled" fillcolor="#c0ffc0"]
SS00 -> retiring1 [label="dispatch.dropExport"]
retiring1 [shape="box" label="syscall.retireExport"]
retiring1 -> deallocating
SS01 -> SW01 [label="dispatch.dropExport"]
SS00 -> SS10 [label="unserialize"]
// SS00 -> storing2 [label="store as\nvalue"] // cannot: need Representative to store
// storing2 [shape="box" label="set flag"]
// storing2 -> SS
SS11 -> SW11 [label="dispatch.dropExport"]
SW11 [label="RECOGNIZABLE+\nSTORED+\nONLINE\nk:weak, rep:1, state:1"]
SW11 -> exporting2 [label="syscall.send(Rep)"]
SW11 -> SW10 [label="value stops"]
SW10 [label="RECOGNIZABLE+\nONLINE\nk:weak, rep:1, state:0"]
SW11 -> SW01 [label="finalizer"]
SW01 [label="RECOGNIZABLE+\nSTORED\nk:weak, rep:0, state:1" style="filled" fillcolor="#f0fff0"]
SW01 -> SW11 [label="unserialize"]
SW01 -> retiring1 [label="value stops"]
SW01 -> SW01 [label="dispatch.retireExport"]
SS10 -> SW10 [label="dispatch.dropExport"]
SS10 -> SS00 [label="finalizer"]
SW10 -> retiring1 [label="finalizer"]
SW10 -> exporting3 [label="syscall.send(Rep)"]
exporting3 [shape="box" label="add to slotToVal"]
exporting3 -> SS10
SW10 -> SW11 [label="store as value"]
{rank=same; retiring1 deallocating S000}
{rank=same; S010 S011 S001}
/* {rank=same; SS11 exporting2}*/
{rank=same; SS10 SS11 SS01}
{rank=same; SW10 SW11 SW01}
{rank=same; SS00 exporting1}
} Overriding WeakMap/WeakSet and Map/SetTo maintain determinism, we must prevent userspace code from sensing the comings and goings of ephemeral Presences and Representatives. Each time a Presence is GCed and then re-introduced, or a Representative is abandoned and then revived, the JS So we must withhold the real For
We need something more complicated for Both our Our Stages of ImplementationModulo the nightmare of upgrade, we can implement this in stages. Each stage must be safe against nondeterminism, but we can progressively enable more garbage collection (and achieve more potential space savings) as we go. We might be able to defer the One of the last steps is to track the existence/release of weak tables. When a |
I'm struggling to see how the RECOGNIZABLE state comes about. Is a brief example easy to provide? |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
In an importing vat, it'd look like: const wm = new WeakMap();
export function buildRootObject() {
return Far('root', {
rememberBob(bob) { wm.set(bob, 'recognized'); }, // 'bob' Presence is not retained
isThisBob(query) { return wm.get(query); }, // 'query' might be a new Presence
};
} If a caller invokes If GC happens promptly, the However, Our vat is interested in two space-saving transitions from that point. If it stops being able to recognize Bob (say, the WeakMap was deleted), then we can tell the kernel that we no longer need the c-list mapping. If all importing vats stop being able to recognize Bob, and the kernel doesn't reference Bob anywhere itself, then the exporting vat's c-list entry can be removed. The other transition is if the exporting vat (and the kernel, and all other strongly-importing vats) all stop being able to produce Bob. In that case, our importing vat should delete its WeakMap entry, just as if the non-swingset world dropped the last strong reference to Bob. These two transitions correspond to the importing vat sending From the kernel's point of view, an object is in the RECOGNIZABLE state if:
In this state, the kernel is waiting for the exporting vat to re-export it (i.e. |
First, this is a great write-up! In the section `"Overriding WeakMap/WeakSet and Map/Set", why do we need anything special for non-weak collections. The strong collections are enumerable, but the weak ones are not, so I would not expect "ee need something more complicated for Map, because the keys are enumerable in insertion order" " |
It is indeed a great write-up! We need to distinguish between kinds of non-determinism. The adversarial issue is that we cannot let an adversary cause validators to diverge. Under our current assumptions, we can assume that all validators see exactly the same gc behavior, so sensitivity to differences in gc behavior is not an opportunity for that adversary. Then there's the non-determinism for the sake of robust programming against accidents. Here, sensitivity to differences in gc behavior is a threat to the predictability and robustness of the non-malicious programmer's code. The threat here is that the same abstraction, executed at different times and in different states of the world, will behave subtly differently depending of the happenstance of gc behavior. For this threat, we need to be extremely insensitive to gc behavior but we do not need to be totally insensitive to gc behavior. There may be some cases where a difference is observable if you really look for them, but which benign code is highly unlikely to be sensitive to. The reason I bring this up is the issue of representatives --- whether there and any number for a given object, with observably different JS identities. We may not need full non-observability of the consequences of GC for these. I think this affects both ways of doing representatives, but I'm not sure. |
@dtribble is right, non-weak We must still change The rules would be:
We might still be able to have |
Until #2724 is implemented, we cannot safely allow Presences to be collected, because we have WeakMaps that think they'll stick around. Disable collection by putting all imports into the (strong) `exported` Set. We'll remove this when WeakMap/WeakSet is updated to know the difference between non-REACHABLE and non-RECOGNIZABLE.
Until #2724 is implemented, we cannot safely allow Presences to be collected, because we have WeakMaps that think they'll stick around. Disable collection by putting all imports into the (strong) `exported` Set. We'll remove this when WeakMap/WeakSet is updated to know the difference between non-REACHABLE and non-RECOGNIZABLE.
Until #2724 is implemented, we cannot safely allow Presences to be collected, because we have WeakMaps that think they'll stick around. Disable collection by putting all imports into the (strong) `exported` Set. We'll remove this when WeakMap/WeakSet is updated to know the difference between non-REACHABLE and non-RECOGNIZABLE.
Until #2724 is implemented, we cannot safely allow Presences to be collected, because we have WeakMaps that think they'll stick around. Disable collection by putting all imports into the (strong) `exported` Set. We'll remove this when WeakMap/WeakSet is updated to know the difference between non-REACHABLE and non-RECOGNIZABLE.
Ok so I think the full set of APIs for this will be: vat to kernel
When a Presence (which hasn't been used as a WeakMap/Set key) is finalized, vat emits both dropImport and retireImport. If the vref is still used as a WeakMap/Set key, the vat only emits dropImport, and we defer retireImport until the WeakMap/Set is finalized (the other way it could be retired is for the entry to be deleted from the WeakMap/Set, but that requires a Presence, which means we'll be back in the first scenario). When a Remotable is finalized, vat emits retireExport. When a Representative is finalized and the vref is not used as a WeakMap/Set key (nor as a virtual collection value, once we implement those), the vat emits retireExport. I'm going to start with three separate syscalls, each of which takes an array of vrefs as its only argument. It will be pretty common for the vat to tell the kernel about a vref in kernel to vat
In this direction, we have more pressure to merge information together, because the vat cannot wait for the kernel to perform multiple operations before performing a sweep (or whatever). I'm still going to start with multiple dispatch operations, because it's simpler, and I can't yet imagine what the optimal pattern will be. We'll probably be doing more vat-side sweeps than necessary for a while until we figure out something better. The most likely second approach will be What tools to use for whatA Virtual Object provides RAM savings when you have high cardinality (lots of them) but only a few are in active use at any given time, so most of the time there are no Representatives. The RAM usage is num(active) times the size of the data needed for any given one. Our replacement WeakMap will hold the values strongly. So storing a Representative as a value will pin it in RAM, and you won't have any memory. Our replacement WeakSet (assuming we don't just simulate one with a WeakMap that has More complicated rights-amplification patterns, where you care about a value associated with each virtual-object key, would not be suitable for our basic replacement WeakMap, because we'd be keeping high-cardinality values in RAM. We could conceivably augment our WeakMap to serialize the value and keep it offline, but I think instead we'll build a specialized "WeakStore" that requires its values to be pass-by-copy, and its keys to be virtual (or some sort of self-ish pass-by-reference value), so that we can push all of it offline, and aren't required to keep any O(count) RAM around. |
This adda `syscall.retireImports` and `syscall.retireExports` to the pre-existing `syscall.dropImports`. All three are no-ops, but vats should be able to call them without ill effects, so kernel and liveslots development can continue in parallel. refs #2724
This adds `dispatch.retireExports` and `dispatch.retireImports` to the pre-existing `dispatch.dropExports` stub: tolerated but ignored by liveslots, to allow kernel and liveslots development to proceed in parallel. refs #2724
This add `syscall.retireImports` and `syscall.retireExports` to the pre-existing `syscall.dropImports`. All three are no-ops, but vats should be able to call them without ill effects. It also adds `dispatch.retireExports` and `dispatch.retireImports` to the pre-existing `dispatch.dropExports` stub. These are tolerated but ignored by both liveslots and the comms vat Together, this should allow kernel and liveslots GC development to proceed in parallel. refs #2724
This add `syscall.retireImports` and `syscall.retireExports` to the pre-existing `syscall.dropImports`. All three are no-ops, but vats should be able to call them without ill effects. It also adds `dispatch.retireExports` and `dispatch.retireImports` to the pre-existing `dispatch.dropExports` stub. These are tolerated but ignored by both liveslots and the comms vat Together, this should allow kernel and liveslots GC development to proceed in parallel. refs #2724
This add `syscall.retireImports` and `syscall.retireExports` to the pre-existing `syscall.dropImports`. All three are no-ops, but vats should be able to call them without ill effects. It also adds `dispatch.retireExports` and `dispatch.retireImports` to the pre-existing `dispatch.dropExports` stub. These are tolerated but ignored by both liveslots and the comms vat Together, this should allow kernel and liveslots GC development to proceed in parallel. refs #2724
This add `syscall.retireImports` and `syscall.retireExports` to the pre-existing `syscall.dropImports`. All three are no-ops, but vats should be able to call them without ill effects. It also adds `dispatch.retireExports` and `dispatch.retireImports` to the pre-existing `dispatch.dropExports` stub. These are tolerated but ignored by both liveslots and the comms vat Together, this should allow kernel and liveslots GC development to proceed in parallel. refs #2724
Consensus mode will depend upon GC being deterministic, but solo mode does not. Solo mode requires GC be "sufficiently deterministic", which means a finalizer may or may not run in any given crank. To support this, we must not record the GC-related syscalls (dropImport, retireImport, retireExport) in the transcript. When replaying a transcript, we ignore these syscalls as well. closes #3146 refs #2615 refs #2660 refs #2724
Consensus mode will depend upon GC being deterministic, but solo mode does not. Solo mode requires GC be "sufficiently deterministic", which means a finalizer may or may not run in any given crank. To support this, we must not record the GC-related syscalls (dropImport, retireImport, retireExport) in the transcript. When replaying a transcript, we ignore these syscalls as well. closes #3146 refs #2615 refs #2660 refs #2724
Consensus mode will depend upon GC being deterministic, but solo mode does not. Solo mode requires GC be "sufficiently deterministic", which means a finalizer may or may not run in any given crank. To support this, we must not record the GC-related syscalls (dropImport, retireImport, retireExport) in the transcript. When replaying a transcript, we ignore these syscalls as well. closes #3146 refs #2615 refs #2660 refs #2724
This is complete now:
We don't yet collect cycles between virtualized data, but that doesn't threaten determinism. |
While working on #2660 GC tracking, I observed a Zoe test failure in which some Payment or SeatHandle or something was not found in a
weak-store
. I'm still investigating that, but it raised a disturbing possibility.Imagine two (colluding) vats:
Now someone calls
b~.one(a)
, and B asks A for the target. B sees the vref for the first time, and creates a new Presence for it, stuffing it in the WeakSet. Now some time passes before B calls itstwo()
method (maybe one turn, maybe an hour passes, if B has a way to get someone else to wake them up later).If GC has happened in B while no code held a strong reference to
presence1
, the Presence will get pruned, and the WeakSet will be empty. Whentwo()
happens and B re-fetches the target, the vat-B liveslots there will make a brand new Presence object, which won't be in the WeakSet, andtwo()
reports that GC happened.If it did not happen, the vat-B liveslots will still have the Presence, and
two()
gets back the same JS object it had before, which will be in the WeakSet, andtwo()
reports that GC did not happen.(in #2615 terminology, in the first case the vref is in the COLLECTED/FINALIZED/UNKNOWN state at the end of the crank, while In the second case it is in UNREACHABLE. Nothing outside of the JS engine can tell the difference between REACHABLE and UNREACHABLE, and the engine only learns the difference after a full GC sweep).
This will cause intermittently faulty behavior in code which relies upon the JS object identity of a Presence. The vat which exported the object (holding the Remotable) can use its object identity safely, but not a vat which imports a matching Presence.
Worse, this enables vats to behave non-deterministically. They're not supposed to be able to sense the passing of time, or any activity of the garbage collector.
We put a lot of energy into the virtual object code to make sure that adversarial userspace code could not use it to sense GC happening. Code that relies upon the JS object identity of a virtual-object
Representative
will observe a consistent pattern: each new crank sees a newRepresentative
, which both discourages its use for e.g. WeakMap keys, and behaves deterministically. Code that wants to do the right thing will use our WeakStore (which colludes with liveslots to use the vref identity, 1:1 matched to the exported Remotable) rather than a native WeakMap. But code that wants to misbehave doesn't get the tools to sense GC.This import-side problem doesn't lend itself to the same solution. We've always assumed Presences have a stable identity, and it's entirely reasonable for folks to want to use them as WeakMap keys. But that only worked because liveslots was maintaining a strong reference to the Presence it created. To allow liveslots to perform GC and drop the upstream exported Remotable when nothing else in the kernel is referencing it, we need liveslots to use a WeakRef to track the downstream import. But by weakening that import-side reference, we allow the Presence to go away, which loses the stable identity that we had previously assumed.
I haven't yet come up with a clever idea to work around this. Worst case, this means we can't do GC until the entire vat is terminated, or some code deliberately (and deterministically)
disavow()
s the Presence.@erights @dtribble @FUDCo how did this work in E? Given multiple colluding vats, could one vat learn about its own GC activity?
The text was updated successfully, but these errors were encountered: