Stage 3 Draft / September 11, 2019

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%", « [[Target]] »).
  4. Perfom ! KeepDuringJob(target).
  5. Set weakRef.[[Target]] 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 [[Target]] 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. If Type(weakRef) is not Object, throw a TypeError exception.
  3. If weakRef does not have a [[Target]] internal slot, throw a TypeError exception.
  4. Let target be the value of weakRef.[[Target]].
  5. If target is not empty,
    1. Perform ! KeepDuringJob(target).
    2. Return target.
  6. 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 KeepDuringJob 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 [[Target]] 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.

Open questions remain under discussion about the relationship between when elements are collected in WeakMaps, WeakSets and WeakRefs, e.g., what guarantees are made about timing, c.f. #121 (comment).

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 FinalizationGroup Objects

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

3.1The FinalizationGroup Constructor

The FinalizationGroup constructor:

  • is the intrinsic object %FinalizationGroup%.
  • is the initial value of the FinalizationGroup property of the global object.
  • creates and initializes a new FinalizationGroup 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 FinalizationGroup behaviour must include a super call to the FinalizationGroup constructor to create and initialize the subclass instance with the internal state necessary to support the FinalizationGroup.prototype built-in methods.

3.1.1 FinalizationGroup ( cleanupCallback )

When the FinalizationGroup 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 finalizationGroup be ? OrdinaryCreateFromConstructor(NewTarget, "%FinalizationGroupPrototype%", « [[Realm]], [[CleanupCallback]], [[Cells]], [[IsFinalizationGroupCleanupJobActive]] »).
  4. Let fn be the active function object.
  5. Set finalizationGroup.[[Realm]] to fn.[[Realm]].
  6. Set finalizationGroup.[[CleanupCallback]] to cleanupCallback.
  7. Set finalizationGroup.[[Cells]] to be an empty List.
  8. Set finalizationGroup.[[IsFinalizationGroupCleanupJobActive]] to false.
  9. Return finalizationGroup.

3.2Properties of the FinalizationGroup Constructor

The FinalizationGroup constructor:

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

3.2.1FinalizationGroup.prototype

The initial value of FinalizationGroup.prototype is the intrinsic %FinalizationGroupPrototype% object.

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

3.3Properties of the FinalizationGroup Prototype Object

The FinalizationGroup prototype object:

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

3.3.1FinalizationGroup.prototype.constructor

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

3.3.2FinalizationGroup.prototype.register ( target , holdings [, unregisterToken ] )

The following steps are taken:

  1. Let finalizationGroup be the this value.
  2. If Type(finalizationGroup) is not Object, throw a TypeError exception.
  3. If Type(target) is not Object, throw a TypeError exception.
  4. If finalizationGroup does not have a [[Cells]] internal slot, 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 { [[Target]] : target, [[Holdings]]: holdings, [[UnregisterToken]]: unregisterToken }.
  7. Append cell to finalizationGroup.[[Cells]].
  8. Return undefined.

3.3.3FinalizationGroup.prototype.unregister ( unregisterToken )

The following steps are taken:

  1. Let finalizationGroup be the this value.
  2. If Type(finalizationGroup) is not Object, throw a TypeError exception.
  3. If finalizationGroup does not have a [[Cells]] internal slot, throw a TypeError exception.
  4. If Type(unregisterToken) is not Object, throw a TypeError exception.
  5. Let removed be false.
  6. For each Record { [[Target]], [[Holdings]], [[UnregisterToken]] } cell that is an element of finalizationGroup.[[Cells]], do
    1. If SameValue(cell.[[UnregisterToken]], unregisterToken) is true, then
      1. Remove cell from finalizationGroup.[[Cells]].
      2. Set removed to true.
  7. Return removed.

3.3.4FinalizationGroup.prototype.cleanupSome ( [ callback ] )

The following steps are taken:

  1. Let finalizationGroup be the this value.
  2. If Type(finalizationGroup) is not Object, throw a TypeError exception.
  3. If finalizationGroup does not have a [[Cells]] internal slot, throw a TypeError exception.
  4. If callback is not undefined and IsCallable(callback) is false, throw a TypeError exception.
  5. Perform ? CleanupFinalizationGroup(finalizationGroup, callback).
  6. Return undefined.

3.3.5FinalizationGroup.prototype [ @@toStringTag ]

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

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

3.4Properties of FinalizationGroup Instances

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

3.5FinalizationGroup Cleanup Iterator Objects

A FinalizationGroup Cleanup Iterator is an ordinary object, with the structure defined below, that represents a specific iteration over some specific FinalizationGroup instance object. There is not a named constructor for FinalizationGroup Cleanup Iterator objects. Instead, these iterator objects are created when the cleanupCallback of the FinalizationGroup is called.

3.5.1CreateFinalizationGroupCleanupIterator ( finalizationGroup )

The following steps are taken:

  1. Assert: Type(finalizationGroup) is Object.
  2. Assert: finalizationGroup has a [[Cells]] internal slot.
  3. Assert: finalizationGroup.[[Realm]].[[Intrinsics]].[[%FinalizationGroupCleanupIteratorPrototype%]] exists and has been initialized.
  4. Let prototype be finalizationGroup.[[Realm]].[[Intrinsics]].[[%FinalizationGroupCleanupIteratorPrototype%]].
  5. Let iterator be ObjectCreate(prototype, « [[FinalizationGroup]] »).
  6. Set iterator.[[FinalizationGroup]] to finalizationGroup.
  7. Return iterator.

3.5.2The %FinalizationGroupCleanupIteratorPrototype% Object

The %FinalizationGroupCleanupIteratorPrototype% object:

  • has properties that are inherited by all FinalizationGroup Cleanup Iterator Objects.
  • is an ordinary object.
  • has a [[Prototype]] internal slot whose value is the intrinsic object %IteratorPrototype%.
  • has the following properties:

3.5.2.1%FinalizationGroupCleanupIteratorPrototype%.next ( )

  1. Let iterator be the this value.
  2. If Type(iterator) is not Object, throw a TypeError exception.
  3. If iterator does not have a [[FinalizationGroup]] internal slot, throw a TypeError exception.
  4. Let finalizationGroup be iterator.[[FinalizationGroup]].
  5. Assert: Type(finalizationGroup) is Object.
  6. Assert: finalizationGroup has [[Cells]] and [[IsFinalizationGroupCleanupJobActive]] internal slots.
  7. If finalizationGroup.[[IsFinalizationGroupCleanupJobActive]] is false, throw a TypeError exception.
  8. If finalizationGroup.[[Cells]] contains a Record cell such that cell.[[Target]] is empty,
    1. Choose any such cell.
    2. Remove cell from finalizationGroup.[[Cells]].
    3. Return CreateIterResultObject(cell.[[Holdings]], false).
  9. Otherwise, return CreateIterResultObject(undefined, true).

3.5.2.2%FinalizationGroupCleanupIteratorPrototype% [ @@toStringTag ]

The initial value of the @@toStringTag property is the String value "FinalizationGroup Cleanup Iterator".

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

3.5.3Properties of FinalizationGroup Cleanup Iterator Instances

FinalizationGroup Cleanup Iterator instances are ordinary objects that inherit properties from the %FinalizationGroupCleanupIteratorPrototype% intrinsic object. FinalizationGroup Cleanup Iterator instances are initially created with a [[FinalizationGroup]] internal slot.

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 WeakRefs and FinalizationGroups

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 WeakRefs and FinalizationGroups 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 FinalizationGroup becomes unreachable, a call of the FinalizationGroup's cleanup callback may eventually be made, after synchronous ECMAScript execution completes. The FinalizationGroup cleanup is performed with the CleanupFinalizationGroup abstract operation.

Neither of these actions (ClearKeptObjects or CleanupFinalizationGroup) may interrupt synchronous ECMAScript execution. Because embedding environments may assemble longer, synchronous ECMAScript execution runs, this specification defers the scheduling of ClearKeptObjects and CleanupFinalizationGroup 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 CleanupFinalizationGroup allows it to resume ECMAScript execution in order to run finalizer work, which may free up holdings, reducing overall memory usage.

4.1.2Liveness

For some object obj, a hypothetical WeakRef-oblivious execution with respect to obj is an execution whereby WeakRef.prototype.deref being called on a WeakRef whose referent is obj 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.

At any point during evaluation, an object obj is considered live if either of the following conditions is met:

  • obj is included in any agent's [[KeptAlive]] List.
  • There exists a valid future hypothetical WeakRef-oblivious execution with respect to obj that observes the Object value of obj.
Note 2
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 3
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 [[Target]] and [[UnregisterToken]] fields of a FinalizationGroup 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 4
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.
Editor's Note
The exact definition of liveness remains under discussion in #115.

4.1.3Execution

At any time, if an object obj is not live, an ECMAScript implementation may perform the following steps atomically:

  1. For each WeakRef ref such that ref.[[Target]] is obj,
    1. Set ref.[[Target]] to empty.
  2. For each FinalizationGroup fg such that fg.[[Cells]] contains cell, such that cell.[[Target]] is obj,
    1. Set cell.[[Target]] to empty.
    2. Optionally, perform ! HostCleanupFinalizationGroup(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
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 [[Target]] internal slot of a WeakRef, optimizations such as rematerialization that observably empty the WeakRef are prohibited.

4.1.4Host Hooks

4.1.4.1HostCleanupFinalizationGroup(fg)

HostCleanupFinalizationGroup is an implementation-defined abstract operation that is expected to call CleanupFinalizationGroup(fg) 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.3KeepDuringJob ( object )

  1. Let agent be the surrounding agent.
  2. Append object to agent.[[KeptAlive]].
Note
When the abstract operation KeepDuringJob is called with a target object reference, it adds the target to an identity Set that will point strongly at the target until the end of the current Job. This may be abstractly implemented as a Set in the current Job that becomes unreachable when the Job ends because the Job becomes unreachable, or as a Set in the current Agent that gets cleared at the end of each Job.

4.4 CheckForEmptyCells ( finalizationGroup )

The following steps are performed:

  1. Assert: finalizationGroup has an [[Cells]] internal slot.
  2. For each cell in finalizationGroup.[[Cells]], do
    1. If cell.[[Target]] is empty, then
      1. Return true.
  3. Return false.

4.5 CleanupFinalizationGroup ( finalizationGroup [ , callback ] )

The following steps are performed:

  1. Assert: finalizationGroup has [[Cells]], [[CleanupCallback]], and [[IsFinalizationGroupCleanupJobActive]] internal slots.
  2. If CheckForEmptyCells(finalizationGroup) is false, return.
  3. Let iterator be ! CreateFinalizationGroupCleanupIterator(finalizationGroup).
  4. If callback is undefined, set callback to finalizationGroup.[[CleanupCallback]].
  5. Set finalizationGroup.[[IsFinalizationGroupCleanupJobActive]] to true.
  6. Let result be Call(callback, undefined, « iterator »).
  7. Set finalizationGroup.[[IsFinalizationGroupCleanupJobActive]] to false.
  8. If result is an abrupt completion, return result.
  9. Else, return NormalCompletion(undefined).
Editor's Note
When called from HostCleanupFinalizationGroup, 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

© 2019 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.