9.9 Processing Model of WeakRef and FinalizationRegistry Objects

9.9.1 Objectives

This specification does not make any guarantees that any object will be garbage collected. Objects which are not live may be released after long periods of time, or never at all. For this reason, this specification uses the term "may" when describing behaviour triggered by garbage collection.

The semantics of WeakRef and FinalizationRegistry objects is based on two operations which happen at particular points in time:

  • When WeakRef.prototype.deref is called, the referent (if undefined is not returned) is kept alive so that subsequent, synchronous accesses also return the object. This list is reset when synchronous work is done using the ClearKeptObjects abstract operation.
  • When an object which is registered with a FinalizationRegistry becomes unreachable, a call of the FinalizationRegistry's cleanup callback may eventually be made, after synchronous ECMAScript execution completes. The FinalizationRegistry cleanup is performed with the CleanupFinalizationRegistry abstract operation.

Neither of these actions (ClearKeptObjects or CleanupFinalizationRegistry) may interrupt synchronous ECMAScript execution. Because hosts may assemble longer, synchronous ECMAScript execution runs, this specification defers the scheduling of ClearKeptObjects and CleanupFinalizationRegistry to the host environment.

Some ECMAScript implementations include garbage collector implementations which run in the background, including when ECMAScript is idle. Letting the host environment schedule CleanupFinalizationRegistry allows it to resume ECMAScript execution in order to run finalizer work, which may free up held values, reducing overall memory usage.

9.9.2 Liveness

For some set of objects S, a hypothetical WeakRef-oblivious execution with respect to S is an execution whereby the abstract operation WeakRefDeref of a WeakRef whose referent is an element of S always returns undefined.

Note 1
WeakRef-obliviousness, together with liveness, capture two notions. One, that a WeakRef itself does not keep an object alive. Two, that cycles in liveness does not imply that an object is live. To be concrete, if determining obj's liveness depends on determining the liveness of another WeakRef referent, obj2, obj2's liveness cannot assume obj's liveness, which would be circular reasoning.
Note 2
WeakRef-obliviousness is defined on sets of objects instead of individual objects to account for cycles. If it were defined on individual objects, then an object in a cycle will be considered live even though its Object value is only observed via WeakRefs of other objects in the cycle.
Note 3
Colloquially, we say that an individual object is live if every set of objects containing it is live.

At any point during evaluation, a set of objects S is considered live if either of the following conditions is met:

  • Any element in S is included in any agent's [[KeptAlive]] List.
  • There exists a valid future hypothetical WeakRef-oblivious execution with respect to S that observes the Object value of any object in S.
Note 4
The intuition the second condition above intends to capture is that an object is live if its identity is observable via non-WeakRef means. An object's identity may be observed by observing a strict equality comparison between objects or observing the object being used as key in a Map.
Note 5

Presence of an object in a field, an internal slot, or a property does not imply that the object is live. For example if the object in question is never passed back to the program, then it cannot be observed.

This is the case for keys in a WeakMap, members of a WeakSet, as well as the [[WeakRefTarget]] and [[UnregisterToken]] fields of a FinalizationRegistry Cell record.

The above definition implies that, if a key in a WeakMap is not live, then its corresponding value is not necessarily live either.

Note 6
Liveness is the lower bound for guaranteeing which WeakRefs engines must not empty. Liveness as defined here is undecidable. In practice, engines use conservative approximations such as reachability. There is expected to be significant implementation leeway.

9.9.3 Execution

At any time, if a set of objects S is not live, an ECMAScript implementation may perform the following steps atomically:

  1. For each element obj of S, do
    1. For each WeakRef ref such that ref.[[WeakRefTarget]] is obj, do
      1. Set ref.[[WeakRefTarget]] to empty.
    2. For each FinalizationRegistry fg such that fg.[[Cells]] contains a Record cell such that cell.[[WeakRefTarget]] is obj, do
      1. Set cell.[[WeakRefTarget]] to empty.
      2. Optionally, perform ! HostEnqueueFinalizationRegistryCleanupJob(fg).
    3. For each WeakMap map such that map.[[WeakMapData]] contains a Record r such that r.[[Key]] is obj, do
      1. Set r.[[Key]] to empty.
      2. Set r.[[Value]] to empty.
    4. For each WeakSet set such that set.[[WeakSetData]] contains obj, do
      1. Replace the element of set.[[WeakSetData]] whose value is obj with an element whose value is empty.
Note 1

Together with the definition of liveness, this clause prescribes legal optimizations that an implementation may apply regarding WeakRefs.

It is possible to access an object without observing its identity. Optimizations such as dead variable elimination and scalar replacement on properties of non-escaping objects whose identity is not observed are allowed. These optimizations are thus allowed to observably empty WeakRefs that point to such objects.

On the other hand, if an object's identity is observable, and that object is in the [[WeakRefTarget]] internal slot of a WeakRef, optimizations such as rematerialization that observably empty the WeakRef are prohibited.

Because calling HostEnqueueFinalizationRegistryCleanupJob is optional, registered objects in a FinalizationRegistry do not necessarily hold that FinalizationRegistry live. Implementations may omit FinalizationRegistry callbacks for any reason, e.g., if the FinalizationRegistry itself becomes dead, or if the application is shutting down.

Note 2

Implementations are not obligated to empty WeakRefs for maximal sets of non-live objects.

If an implementation chooses a non-live set S in which to empty WeakRefs, it must empty WeakRefs for all objects in S simultaneously. In other words, an implementation must not empty a WeakRef pointing to an object obj without emptying out other WeakRefs that, if not emptied, could result in an execution that observes the Object value of obj.

9.9.4 Host Hooks

9.9.4.1 HostEnqueueFinalizationRegistryCleanupJob ( finalizationRegistry )

The abstract operation HostEnqueueFinalizationRegistryCleanupJob takes argument finalizationRegistry (a FinalizationRegistry). HostEnqueueFinalizationRegistryCleanupJob is an implementation-defined abstract operation that is expected to call CleanupFinalizationRegistry(finalizationRegistry) at some point in the future, if possible. The host's responsibility is to make this call at a time which does not interrupt synchronous ECMAScript code execution.