#1740 Weak references in JS

Open
opened 2 months ago by Moonchild · 0 comments

Research into weak references (ES2021) beyond the first kneejerk response (“OMG why”) indicated that the response was wholly correct.
Regardless of what the TC9 working group in their “meetings” has “decided” (without input from us, mind you), I currently don’t have an intent to ship this at all.

TL;DR

  • The W3C TAG Design Principles recommend against APIs that expose access to the Garbage Collector (which is supposed to be an internal mechanism)
  • Garbage collectors are complicated; letting content scripts control (parts of) it is asking for trouble, will likely be detrimental to GC, and is no replacement for proper data handling design in programs.
  • This API is wholly unnecessary and extremely implementation-dependent. It makes zero guarantees and the interaction between DOM/html and weakref is very tightly bound to individual engines and cannot be relied on.
  • We already have WeakMap for large data structures if it’s absolutely necessary. This is not a replacement for it and has a whole bunch of “gotchas” to go along with it.

Important background

Background taken from the very TC39 proposal readme:

A note of caution

This proposal contains two advanced features, WeakRef and FinalizationRegistry. Their correct use takes careful thought, and they are best avoided if possible.

Garbage collectors are complicated. If an application or library depends on GC cleaning up a WeakRef or calling a finalizer in a timely, predictable manner, it’s likely to be disappointed: the cleanup may happen much later than expected, or not at all. Sources of variability include:

  • One object might be garbage-collected much sooner than another object, even if they become unreachable at the same time, e.g., due to generational collection.
  • Garbage collection work can be split up over time using incremental and concurrent techniques.
  • Various runtime heuristics can be used to balance memory usage, responsiveness.
  • The JavaScript engine may hold references to things which look like they are unreachable (e.g., in closures, or inline caches).
  • Different JavaScript engines may do these things differently, or the same engine may change its algorithms across versions.
  • Complex factors may lead to objects being held alive for unexpected amounts of time, such as use with certain APIs.

Important logic should not be placed in the code path of a finalizer. Doing so could create user-facing issues triggered by memory management bugs, or even differences between JavaScript garbage collector implementations. For example, if data is saved persistently solely from a finalizer, then a bug which accidentally keeps an additional reference around could lead to data loss.

For this reason, the W3C TAG Design Principles recommend against creating APIs that expose garbage collection. It’s best if WeakRef objects and FinalizationRegistry objects are used as a way to avoid excess memory usage, or as a backstop against certain bugs, rather than as a normal way to clean up external resources or observe what’s allocated.


Another note of caution

Finalizers are tricky business and it is best to avoid them. They can be invoked at unexpected times, or not at all---for example, they are not invoked when closing a browser tab or on process exit. They don’t help the garbage collector do its job; rather, they are a hindrance. Furthermore, they perturb the garbage collector’s internal accounting. The GC decides to scan the heap when it thinks that it is necessary, after some amount of allocation. Finalizable objects almost always represent an amount of allocation that is invisible to the garbage collector. The effect can be that the actual resource usage of a system with finalizable objects is higher than what the GC thinks it should be.

The proposed specification allows conforming implementations to skip calling finalization callbacks for any reason or no reason. Some reasons why many JS environments and implementations may omit finalization callbacks:

  • If the program shuts down (e.g., process exit, closing a tab, navigating away from a page), finalization callbacks typically don’t run on the way out.
  • If the FinalizationRegistry becomes “dead” (approximately, unreachable), then finalization callbacks registered against it might not run.

Remarks

It is extremely surprising from a developer’s point of view that this was actually agreed upon to specify in an ES standard. The internals of any JS implementation should never be exposed so directly. Doing so is risky, potentially security-vulnerable (giving programs access to an API to coerce/accurately detect GC happening is... a can of worms), potentially fingerprinting-capable (behavior in response to using the API in specific situations can provide timings of individual browser implementations, etc.) and any GCing language implementation is literally always better internally at performing GC than what a cross-browser script might think from version to version.

Research into weak references (ES2021) beyond the first kneejerk response ("OMG why") indicated that the response was wholly correct. Regardless of what the TC9 working group in their "meetings" has "decided" (without input from us, mind you), **I currently don't have an intent to ship this at all.** TL;DR - The W3C TAG Design Principles recommend against APIs that expose access to the Garbage Collector (which is supposed to be an internal mechanism) - Garbage collectors are complicated; letting content scripts control (parts of) it is asking for trouble, will likely be detrimental to GC, and is no replacement for proper data handling design in programs. - This API is wholly unnecessary and extremely implementation-dependent. It makes zero guarantees and the interaction between DOM/html and weakref is very tightly bound to individual engines and cannot be relied on. - We already have `WeakMap` for large data structures if it's absolutely necessary. This is not a replacement for it and has a whole bunch of "gotchas" to go along with it. ### Important background Background taken from the very TC39 proposal readme: #### A note of caution This proposal contains two advanced features, WeakRef and FinalizationRegistry. Their correct use takes careful thought, and they are best avoided if possible. Garbage collectors are complicated. If an application or library depends on GC cleaning up a WeakRef or calling a finalizer in a timely, predictable manner, it's likely to be disappointed: the cleanup may happen much later than expected, or not at all. Sources of variability include: - One object might be garbage-collected much sooner than another object, even if they become unreachable at the same time, e.g., due to generational collection. - Garbage collection work can be split up over time using incremental and concurrent techniques. - Various runtime heuristics can be used to balance memory usage, responsiveness. - The JavaScript engine may hold references to things which look like they are unreachable (e.g., in closures, or inline caches). - Different JavaScript engines may do these things differently, or the same engine may change its algorithms across versions. - Complex factors may lead to objects being held alive for unexpected amounts of time, such as use with certain APIs. Important logic should not be placed in the code path of a finalizer. Doing so could create user-facing issues triggered by memory management bugs, or even differences between JavaScript garbage collector implementations. For example, if data is saved persistently solely from a finalizer, then a bug which accidentally keeps an additional reference around could lead to data loss. For this reason, the W3C TAG Design Principles recommend against creating APIs that expose garbage collection. It's best if WeakRef objects and FinalizationRegistry objects are used as a way to avoid excess memory usage, or as a backstop against certain bugs, rather than as a normal way to clean up external resources or observe what's allocated. ----- #### Another note of caution Finalizers are tricky business and it is best to avoid them. They can be invoked at unexpected times, or not at all---for example, they are not invoked when closing a browser tab or on process exit. They don’t help the garbage collector do its job; rather, they are a hindrance. Furthermore, they perturb the garbage collector’s internal accounting. The GC decides to scan the heap when it thinks that it is necessary, after some amount of allocation. Finalizable objects almost always represent an amount of allocation that is invisible to the garbage collector. The effect can be that the actual resource usage of a system with finalizable objects is higher than what the GC thinks it should be. The proposed specification allows conforming implementations to skip calling finalization callbacks for any reason or no reason. Some reasons why many JS environments and implementations may omit finalization callbacks: - If the program shuts down (e.g., process exit, closing a tab, navigating away from a page), finalization callbacks typically don't run on the way out. - If the FinalizationRegistry becomes "dead" (approximately, unreachable), then finalization callbacks registered against it might not run. ---- ### Remarks It is extremely surprising from a developer's point of view that this was actually agreed upon to specify in an ES standard. The internals of any JS implementation should never be exposed so directly. Doing so is risky, potentially security-vulnerable (giving programs access to an API to coerce/accurately detect GC happening is... a can of worms), potentially fingerprinting-capable (behavior in response to using the API in specific situations can provide timings of individual browser implementations, etc.) and any GCing language implementation is literally always better internally at performing GC than what a cross-browser script might think from version to version.
Moonchild added the
C: Javascript
label 2 months ago
Moonchild added the
Standards Compliance
label 2 months ago
Moonchild added a new dependency 2 months ago
Moonchild added the
On Hold
label 2 months ago
Moonchild added the
Retarded
label 2 months ago
Sign in to join this conversation.
No Milestone
No Assignees
1 Participants
Notifications
Due Date

No due date set.

Blocks
#1735 Implement ES2021 proposals
MoonchildProductions/UXP
Loading…
There is no content yet.