Stage 3 Draft / December 16, 2020

WeakRefs proposal

1 WeakRef Objects

A WeakRef is an object that is used to refer to a target object without preserving it from garbage collection. WeakRefs can dereference to allow access to the target object, if the target object hasn't been reclaimed by garbage collection.

1.1The WeakRef Constructor

The WeakRef constructor:

  • is the intrinsic object %WeakRef%.
  • is the initial value of the WeakRef property of the global object.
  • creates and initializes a new WeakRef object when called as a constructor.
  • is not intended to be called as a function and will throw an exception when called in that manner.
  • is designed to be subclassable. It may be used as the value in an extends clause of a class definition. Subclass constructors that intend to inherit the specified WeakRef behaviour must include a super call to the WeakRef constructor to create and initialize the subclass instance with the internal state necessary to support the WeakRef.prototype built-in methods.

1.1.1 WeakRef ( target )

When the WeakRef function is called with argument target, the following steps are taken:

  1. If NewTarget is undefined, throw a TypeError exception.
  2. If Type(target) is not Object, throw a TypeError exception.
  3. Let weakRef be ? OrdinaryCreateFromConstructor(NewTarget, "%WeakRefPrototype%", « [[WeakRefTarget]] »).
  4. Perfom ! AddToKeptObjects(target).
  5. Set weakRef.[[WeakRefTarget]] to target.
  6. Return weakRef.

1.2Properties of the WeakRef Constructor

The WeakRef constructor:

  • has a [[Prototype]] internal slot whose value is the intrinsic object %FunctionPrototype%.
  • has the following properties:

1.2.1WeakRef.prototype

The initial value of WeakRef.prototype is the intrinsic %WeakRefPrototype% object.

This property has the attributes { [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: false }.

1.3Properties of the WeakRef Prototype Object

The WeakRef prototype object:

  • is the intrinsic object %WeakRefPrototype%.
  • has a [[Prototype]] internal slot whose value is the intrinsic object %ObjectPrototype%.
  • is an ordinary object.
  • does not have a [[WeakRefTarget]] internal slot.

1.3.1WeakRef.prototype.constructor

The initial value of WeakRef.prototype.constructor is the intrinsic object %WeakRef%.

This property has the attributes { [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: true }.

Editor's Note
This section is to be treated identically to the "Annex B" of ECMA-262, but to be written in-line with the main specification.

1.3.2WeakRef.prototype.deref ( )

The following steps are taken:

  1. Let weakRef be the this value.
  2. Perform ? RequireInternalSlot(weakRef, [[WeakRefTarget]]).
  3. Let target be the value of weakRef.[[WeakRefTarget]].
  4. If target is not empty,
    1. Perform ! AddToKeptObjects(target).
    2. Return target.
  5. Return undefined.
Note
If the WeakRef returns a target Object that is not undefined, then this target object should not be garbage collected in that turn. The AddToKeptObjects operation makes sure read consistency is maintained.
          target = { foo: function() {} };
          let weakRef = new WeakRef(target);

          ... later ...

          if (weakRef.deref()) {
            weakRef.deref().foo();
          }
        
In the above example, if the first deref evaluates to true then the second deref can not fail.

1.3.3WeakRef.prototype [ @@toStringTag ]

The initial value of the @@toStringTag property is the String value "WeakRef".

This property has the attributes { [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: true }.

1.4Properties of WeakRef Instances

WeakRef instances are ordinary objects that inherit properties from the WeakRef prototype. WeakRef instances also have a [[WeakRefTarget]] internal slot.

2Modifications to collection type definitions

Editor's Note

WeakMap and WeakSet keys are not kept alive just because a WeakRef points to them. This proposal rephrases the definition of WeakMaps and WeakSets to explain their observable effects on garbage collection, rather than specifying operationally that non-live keys or members are deleted.

2.1WeakMap Objects

WeakMap objects are collections of key/value pairs where the keys are objects and values may be arbitrary ECMAScript language values. A WeakMap may be queried to see if it contains a key/value pair with a specific key, but no mechanism is provided for enumerating the objects it holds as keys. In certain conditions, objects which are not live are removed as WeakMap keys, as described in 4.1.3. If an object that is being used as the key of a WeakMap key/value pair is only reachable by following a chain of references that start within that WeakMap, then that key/value pair is inaccessible and is automatically removed from the WeakMap. WeakMap implementations must detect and remove such key/value pairs and any associated resources.

2.2WeakSet Objects

WeakSet objects are collections of objects. A distinct object may only occur once as an element of a WeakSet's collection. A WeakSet may be queried to see if it contains a specific object, but no mechanism is provided for enumerating the objects it holds. In certain conditions, objects which are not live are removed as WeakSet elements, as described in 4.1.3. If an object that is contained by a WeakSet is only reachable by following a chain of references that start within that WeakSet, then that object is inaccessible and is automatically removed from the WeakSet. WeakSet implementations must detect and remove such objects and any associated resources.

3 FinalizationRegistry Objects

A FinalizationRegistry is an object that manages registration and unregistration of cleanup operations that are performed when target objects are garbage collected.

3.1The FinalizationRegistry Constructor

The FinalizationRegistry constructor:

  • is the intrinsic object %FinalizationRegistry%.
  • is the initial value of the FinalizationRegistry property of the global object.
  • creates and initializes a new FinalizationRegistry object when called as a constructor.
  • is not intended to be called as a function and will throw an exception when called in that manner.
  • is designed to be subclassable. It may be used as the value in an extends clause of a class definition. Subclass constructors that intend to inherit the specified FinalizationRegistry behaviour must include a super call to the FinalizationRegistry constructor to create and initialize the subclass instance with the internal state necessary to support the FinalizationRegistry.prototype built-in methods.

3.1.1 FinalizationRegistry ( cleanupCallback )

When the FinalizationRegistry function is called with argument cleanupCallback, the following steps are taken:

  1. If NewTarget is undefined, throw a TypeError exception.
  2. If IsCallable(cleanupCallback) is false, throw a TypeError exception.
  3. Let finalizationRegistry be ? OrdinaryCreateFromConstructor(NewTarget, "%FinalizationRegistryPrototype%", « [[Realm]], [[CleanupCallback]], [[Cells]] »).
  4. Let fn be the active function object.
  5. Set finalizationRegistry.[[Realm]] to fn.[[Realm]].
  6. Set finalizationRegistry.[[CleanupCallback]] to cleanupCallback.
  7. Set finalizationRegistry.[[Cells]] to be an empty List.
  8. Return finalizationRegistry.

3.2Properties of the FinalizationRegistry Constructor

The FinalizationRegistry constructor:

  • has a [[Prototype]] internal slot whose value is the intrinsic object %FunctionPrototype%.
  • has the following properties:

3.2.1FinalizationRegistry.prototype

The initial value of FinalizationRegistry.prototype is the intrinsic %FinalizationRegistryPrototype% object.

This property has the attributes { [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: false }.

3.3Properties of the FinalizationRegistry Prototype Object

The FinalizationRegistry prototype object:

  • is the intrinsic object %FinalizationRegistryPrototype%.
  • has a [[Prototype]] internal slot whose value is the intrinsic object %ObjectPrototype%.
  • is an ordinary object.
  • does not have [[Cells]] and [[CleanupCallback]] internal slots.

3.3.1FinalizationRegistry.prototype.constructor

The initial value of FinalizationRegistry.prototype.constructor is the intrinsic object %FinalizationRegistry%.

3.3.2FinalizationRegistry.prototype.register ( target , heldValue [, unregisterToken ] )

The following steps are taken:

  1. Let finalizationRegistry be the this value.
  2. Perform ? RequireInternalSlot(finalizationRegistry, [[Cells]]).
  3. If Type(target) is not Object, throw a TypeError exception.
  4. If SameValue(target, heldValue), throw a TypeError exception.
  5. If Type(unregisterToken) is not Object,
    1. If unregisterToken is not undefined, throw a TypeError exception.
    2. Set unregisterToken to empty.
  6. Let cell be the Record { [[WeakRefTarget]] : target, [[HeldValue]]: heldValue, [[UnregisterToken]]: unregisterToken }.
  7. Append cell to finalizationRegistry.[[Cells]].
  8. Return undefined.
Note
Based on the algorithms and definitions in this specification, across the time when cell is in finalizationRegistry.[[Cells]], then cell.[[Holdings]] is live; however, cell.[[UnregisterToken]] and cell.[[Target]] are not necessarily live. For example, registering an object with itself as its unregister token would not keep the object alive forever.

3.3.3FinalizationRegistry.prototype.unregister ( unregisterToken )

The following steps are taken:

  1. Let finalizationRegistry be the this value.
  2. Perform ? RequireInternalSlot(finalizationRegistry, [[Cells]]).
  3. If Type(unregisterToken) is not Object, throw a TypeError exception.
  4. Let removed be false.
  5. For each Record { [[WeakRefTarget]], [[HeldValue]], [[UnregisterToken]] } cell that is an element of finalizationRegistry.[[Cells]], do
    1. If cell.[[UnregisterToken]] is not empty and SameValue(cell.[[UnregisterToken]], unregisterToken) is true, then
      1. Remove cell from finalizationRegistry.[[Cells]].
      2. Set removed to true.
  6. Return removed.

3.3.4FinalizationRegistry.prototype [ @@toStringTag ]

The initial value of the @@toStringTag property is the String value "FinalizationRegistry".

This property has the attributes { [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: true }.

3.4Properties of FinalizationRegistry Instances

FinalizationRegistry instances are ordinary objects that inherit properties from the FinalizationRegistry prototype. FinalizationRegistry instances also have [[Cells]] and [[CleanupCallback]] internal slots.

4 Abstract Jobs

Table 1: Agent Record Fields
Field Name Value Meaning
[[LittleEndian]] Boolean The default value computed for the isLittleEndian parameter when it is needed by the algorithms GetValueFromBuffer and SetValueInBuffer. The choice is implementation-dependent and should be the alternative that is most efficient for the implementation. Once the value has been observed it cannot change.
[[CanBlock]] Boolean Determines whether the agent can block or not.
[[Signifier]] Any globally-unique value Uniquely identifies the agent within its agent cluster.
[[IsLockFree1]] Boolean true if atomic operations on one-byte values are lock-free, false otherwise.
[[IsLockFree2]] Boolean true if atomic operations on two-byte values are lock-free, false otherwise.
[[CandidateExecution]] A candidate execution Record See the memory model.
[[KeptAlive]] List of objects Initially a new empty List, representing the list of objects to be kept alive until the end of the current Job

4.1Processing model of WeakRef and FinalizationRegistry objects

4.1.1Objectives

This specification does not make any guarantees about garbage collection. Objects which are unreachable from ECMAScript may be released after long periods of time, or never at all. For this reason, this specification uses the term "may" when describing behavior 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 it's not already dead) 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 CleanupFFinalizationRegistry abstract operation.

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

Some ECMAScript implementations include garbage collector implementations which run in the background, including when ECMAScript is idle. Letting the embedding 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.

4.1.2Liveness

For some set of objects S, a hypothetical WeakRef-oblivious execution with respect to S is an execution whereby WeakRef.prototype.deref being called on 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 beg the question.
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.

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 3
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 4
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 5
Liveness is the lower bound for guaranteeing which WeakRefs that engines must not empty. In practice, liveness as defined here is undecidable and engines use conservative approximations. There is expected to be significant implementation leeway.

4.1.3Execution

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

  1. For each 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 cell, such that cell.[[WeakRefTarget]] is obj,
      1. Set cell.[[WeakRefTarget]] to empty.
      2. Optionally, perform ! HostCleanupFinalizationRegistry(fg).
    3. For each WeakMap map such that map.[[WeakMapData]] contains a record r such that r.[[Key]] is obj,
      1. Remove r from map.[[WeakMapData]].
    4. For each WeakSet set such that set.[[WeakSetData]] contains obj,
      1. Remove obj from set.[[WeakSetData]].
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 HostCleanupFinalizationRegistry 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.

4.1.4Host Hooks

4.1.4.1HostCleanupFinalizationRegistry(finalizationRegistry)

HostCleanupFinalizationRegistry 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.

4.2ClearKeptObjects( )

ECMAScript implementations are expected to call ClearKeptObjects when a synchronous sequence of ECMAScript execution completes.

The following steps are performed:

  1. Let agent be the surrounding agent.
  2. Set agent.[[KeptAlive]] to a new empty List.

4.3AddToKeptObjects ( object )

  1. Let agent be the surrounding agent.
  2. Append object to agent.[[KeptAlive]].
Note
When the abstract operation AddToKeptObjects is called with a target object reference, it adds the target to an identity Set that will point strongly at the target until ClearKeptObjects is called.

4.4 CleanupFinalizationRegistry ( finalizationRegistry [ , callback ] )

The following steps are performed:

  1. Assert: finalizationRegistry has [[Cells]] and [[CleanupCallback]] internal slots.
  2. If callback is not present or undefined, set callback to finalizationRegistry.[[CleanupCallback]].
  3. While finalizationRegistry.[[Cells]] contains a Record cell such that cell.[[WeakRefTarget]] is empty, then an implementation may perform the following steps,
    1. Choose any such cell.
    2. Remove cell from finalizationRegistry.[[Cells]].
    3. Perform ? Call(callback, undefined, « cell.[[HeldValue]] »).
  4. Return NormalCompletion(undefined).
Editor's Note
When called from HostCleanupFinalizationRegistry, if calling the callback throws an error, this will be caught within the RunJobs algorithm and reported to the host. HTML does not apply the RunJobs algorithm, but will also report the error, which may call window.onerror.

ACopyright & Software License

Copyright Notice

© 2020 Dean Tribble, Till Schneidereit, Sathya Gunasekaran

Software License

All Software contained in this document ("Software") is protected by copyright and is being made available under the "BSD License", included below. This Software may be subject to third party rights (rights from parties other than Ecma International), including patent rights, and no licenses under such third party rights are granted under this license even if the third party concerned is a member of Ecma International. SEE THE ECMA CODE OF CONDUCT IN PATENT MATTERS AVAILABLE AT https://ecma-international.org/memento/codeofconduct.htm FOR INFORMATION REGARDING THE LICENSING OF PATENT CLAIMS THAT ARE REQUIRED TO IMPLEMENT ECMA INTERNATIONAL STANDARDS.

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

  1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
  2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
  3. Neither the name of the authors nor Ecma International may be used to endorse or promote products derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE ECMA INTERNATIONAL "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ECMA INTERNATIONAL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.