Prior Art
V8 implements "idle tasks" which can be accessed through its C++ API. Idle tasks are optional (non-urgent) GC work that will run eventually. Blink uses idle tasks to schedule work between frames, after JavaScript execution is finished.
https://v8.dev/blog/trash-talk#idle-time-gc
JavaScriptCore uses "synthetic pauses" for its concurrent collector. They do not need to be run immediately and are based on engine heuristics.
SpiderMonkey has incremental marking which is done in small arbitrary pauses and incremental sweeping which is done in pauses proportional to the zone size.
https://searchfox.org/mozilla-central/source/js/src/gc/GC.cpp#104
Summary: Extant ECMAScript implementations have internal mechanisms for controlling scheduling and duration of short GC pauses.
Proposal
JavaScript could benefit from fine-grained control of garbage-collection timing. Realtime applications often need to deliver a frame otherwise the user experience will suffer.
I propose a global, Realtime
, with two static methods:
Realtime.avoidPausingFor(period)
- Hints the runtime to avoid long-pausing the current JS worker/thread between now and now plus period
milliseconds.
Realtime.canPauseFor(period)
alternate name idlePeriodFor(period)
- Hints the runtime that it can pause the current JS thread from now to now plus puration
milliseconds. The call signals that the user experience will not be degraded if the ECMAScript engine pauses for part or all of the duration.
Use cases for canPauseFor
- Preferring to run garbage collection pauses during low-action segments of a game, such as on an interstitial screen, or when there are no nearby enemies, instead of running it while the player is battling enemies
- Running GC when a user is not interacting with an article
- Running GC while the application is transitioning to a new state (and thus lots of memory is becoming garbage) and cannot respond to user input
- Running GC work of a Node.js game server that runs at 30FPS in between frames
Use cases for avoidPausingFor
- Signaling a web worker that the main thread offloads animation or graphics computation to should run GC only after the current frame is done, to allow normal web workers to have independent collections
- Avoiding collecting garbage while serving an HTTP request from Node.js to prevent end-user latency from increasing
Things to bikeshed on
canPauseFor does not mandate triggering a GC cycle the same way System.gc
in Java might. It signals an opportunity to do pauses. The spec text would not be normative upon the behavior of WeakRef
and FinalizationRegistry
.
avoidPausingFor for implementations should request new memory from the operating system if it is required to fulfill the request, up to the security restrictions of the host environment. However, GC may still run is necessary, runtimes are not asked to support a "never-fail allocator" or "emergency allocator."
A long-pause is an implementation-defined duration that varies between garbage collector implementations. However, it is understood to be less than 1 cumulative millisecond.
canPauseFor overrides any previous avoidPausingFor directive. This is to allow the developer to ask the implementation to avoid pausing for the rest of the frame, then when the frame is complete, allowing the implementation to pause for the remaining frame time. An avoidPausingFor call does not override an earlier canPauseFor call. This restriction may be lifted in a future spec if necessary
The host environment may restrict, ignore, set minimums, and/or set maximums on the Realtime calls. In other words, the host has ultimate control over GC scheduling and Realtime calls are an implementation hint.
This spec may not need avoidPausingFor
. The canPauseFor
hint to run some GC work may be enough. On the other hand, it may have meaning on single-threaded processors (common in cloud servers) or concurrent GCs: Avoid running a GC operating system thread in parallel, wait until the period has expired to do that. It may be dropped from the proposal as it moves towards Stage 3 and implementation experience is gained. It is suggested engines implement canPauseFor
and gather real-world performance data in origin trials and synthetic benchmarks before deciding whether avoidPausingFor
should be removed from the spec.
Future work
A future version of the spec may specify a way to define a Realm's maximum pause length. For instance, it may be possible to spawn a Realm with a 0 to activate the engine's lowest latency collector, such as reference-counting cycle collector.