/proposal-cleanup-some

Proposal to migrate cleanup some to its own proposal repository

Primary LanguageHTML

The FinalizationRegistry.prototype.cleanupSome Method

Champion group: Daniel Ehrenberg, Yulia Startsev

Stage 2

Introduction

The FinalizationRegistry.prototype.cleanupSome method was originally part of the TC39 Proposal for WeakRefs. This method provided a way for library authors to synchronously allow them to clean up without yielding to the event loop. We propose to move it into a separate proposal, to allow the core of WeakRefs to be used in the wild a bit before adding this additional functionality.

Motivation of cleanupSome

WeakRef and FinalizationRegistry allow JavaScript programs to observe garbage collection. This observability presents significant interoperability risks, which are contained somewhat by giving the host environment control over the granularity in time of when JS can see that an object has been garbage-collected.

In HTML, the granularity is based on tasks and microtasks: WeakRefs are only allowed to observably go from "filled with an object" to undefined at the end of a microtask checkpoint (i.e., after all the Promise jobs run), and FinalizationRegistry cleanup callbacks happen in a queued task, meaning also only when all the Promise jobs have run. This all adds up to: you need to yield to the event loop regularly to make WeakRef and FinalizationRegistry work.

With Workers, SharedArrayBuffer and/or WebAssembly threads/shared memory, it's possible to create a "long job" which does a bunch of computation and communicates with other agents through shared memory and atomics, without yielding to the event loop very frequently. WebAssembly facilitates this use case. If there is also use of JavaScript garbage-collected objects, there may be demand to get FinalizationRegistry cleanup callbacks without yielding to the event loop.

This use case is met by FinalizationRegistry.prototype.cleanupSome. It accepts a function as a parameter (or can fall back to the FinalizationRegistry's callback), and may call that callback with the heldValue of any registered, garbage-collected value, to synchronously perform cleanup actions.

Hesitation: When should cleanupSome be exposed on the Web?

The "long job" case doesn't quite make sense on the "window", the Web's main thread. It makes more sense for background Worker/Worklet threads. So, in browsers, it may make sense to exclude it from the window. There are several other Web Platform APIs which are only exposed in the context of certain global objects and not others, so this would follow a typical idiom.

Further, Apple has raised concerns about cleanupSome being exposed on the Web at all, due to concerns about whether we want to encourage the "long job" programming style in a context where objects are also being used.

Discussion about where and whether cleanupSome should be exposed in web browsers is ongoing in a WHATWG HTML issue.

Splitting out cleanupSome into a separate proposal would give everyone time to consider its presence in browsers at its own pace.

Design changes over time

Earlier drafts of cleanupSome had certain differences from the latest version:

  • Initially, cleanupSome returned a boolean to indicate whether anything is cleaned up. Now, cleanupSome always returns undefined.
  • Initially, cleanupSome called its callback with an iterator of held values. The JavaScript code had the option of not exhausting the iterator, leaving it to the engine to requeue the parts which were not used. Now, cleanupSome calls the callback with one value at a time, repeatedly.

These changes were made without the involvement of the people who made the previous design decisions. Splitting out cleanupSome into a separate proposal would give the committee time to reconsider these design decisions.

Ecosystem impact of a proposal split vs normative optional

Declaring FinalizationRegistry.prototype.cleanupSome has been a bit confusing from an ecosystem perspective. For example,

  • The TypeScript typing includes cleanupSome but marked as optional
  • The MDN documentation notes that the method is "optional", but it's not really clear how this should be interpreted by readers.

Splitting cleanupSome into a separate proposal would make it clear to the JavaScript community that the main parts of WeakRef are standard, and this part is still under discussion.

Committee status

FinalizationRegistry.prototype.cleanupSome has been off from the WeakRefs proposal and is at Stage 2.

History

This API was originally proposed in a form which would be WebAssembly-specific. See the historical document - Support for long wasm jobs for more details.