1
- # Proposal for adding ` FinalizationRegistry ` and ` WeakRef ` to core libraries.
1
+ # Proposal for adding ` Finalizer ` and ` WeakReference ` to core libraries.
2
2
3
3
Author:
[email protected] <
br >Version: 0.1
4
4
5
5
## Background
6
6
7
7
This proposal includes two interconnected pieces of functionality:
8
8
9
- - an ability to create _ weak references_ (` WeakRef ` );
10
- - an ability to associate finalization actions with objects
11
- (` FinalizationRegistry ` ).
9
+ - an ability to create _ weak references_ (` WeakReference ` );
10
+ - an ability to associate finalization actions with objects (` Finalizer ` ).
12
11
13
12
### Weak references
14
13
@@ -132,75 +131,80 @@ would release the native resource.
132
131
133
132
We split the proposed finalization API into two parts:
134
133
135
- - ` FinalizationRegistry ` which:
134
+ - ` Finalizer ` which:
136
135
- does not provide strong guarantees around promptness of finalization,
137
136
- does not impose any restrictions on objects you could associate a
138
137
finalization action with,
139
138
- does not impose any restrictions on finalization actions and invokes them
140
139
asynchronously,
141
140
- is implementable on the Web.
142
- - ` NativeFinalizationRegistry ` which:
141
+ - ` NativeFinalizer ` which:
143
142
- provides stronger guarantees around promptness of finalization,
144
143
- guarantees that finalization actions are invoked when isolate is shutting
145
144
down,
146
145
- is limited to objects which implement ` Finalizable ` interface,
147
146
- imposes restrictions on finalization actions to allow calling these actions
148
147
outside of Dart universe.
149
148
150
- The following classes are added to the new ` dart:weakref ` library
149
+ The following classes are added to the ` dart:core ` library
151
150
152
151
``` dart
153
- /// A registry of objects which may invoke a callback when those objects
154
- /// become inaccessible.
152
+ /// A finalizer which can be attached to Dart objects.
155
153
///
156
- /// The registry allows objects to be registered,
157
- /// and when those objects become inaccessible to the program,
158
- /// the callback passed to the register's constructor *may* be called
159
- /// with the registration token associated with the object.
154
+ /// When [attach]ed to a Dart object, this finalizer's callback *may* be called
155
+ /// some time after the Dart object becomes inaccessible to the program.
160
156
///
161
157
/// No promises are made that the callback will ever be called,
162
158
/// only that *if* it is called with a finalization token as argument,
163
159
/// at least one object registered in the registry with that finalization token
164
160
/// is no longer accessible to the program.
165
161
///
166
- /// If the same object is registered in multiple finalization registries,
167
- /// or registered multiple times in a single registry,
168
- /// and the object becomes inaccessible to the program,
169
- /// then any number of those registrations may trigger their associated
170
- /// callback. It will not necessarily be all or none of them.
162
+ /// If multiple finalizers has been attached to an object or the same
163
+ /// finalizer has been attached multiple times to an object and that object
164
+ /// becomes inaccessible to the program, then any number of those finalizers
165
+ /// may trigger their associated callback. It will not necessarily be all or
166
+ /// none of them.
171
167
///
172
168
/// Finalization callbacks will happen as *events*, not during execution of
173
169
/// other code and not as a microtask, but as high-level events similar to
174
170
/// timer events.
175
- abstract class FinalizationRegistry<FT> {
176
- /// Creates a finalization registry with the given finalization callback.
177
- external factory FinalizationRegistry(
178
- void Function(FT finalizationToken) callback);
171
+ ///
172
+ /// Finalization callbacks should not throw.
173
+ abstract class Finalizer<T> {
174
+ /// Creates a finalizer with the given finalization callback.
175
+ ///
176
+ /// The [callback] is bound to the [current zone](Zone.current)
177
+ /// when the [Finalizer] is created, and will run in that zone when called.
178
+ external factory Finalizer(void Function(T) callback);
179
179
180
- /// Registers [value] for a finalization callback .
180
+ /// Attaches this finalizer to the given [value] .
181
181
///
182
182
/// When [value] is no longer accessible to the program,
183
183
/// the registry *may* call its callback function with [finalizationToken]
184
184
/// as argument.
185
185
///
186
- /// The [value] and [unregisterToken ] arguments do not count towards those
186
+ /// The [value] and [detachKey ] arguments do not count towards those
187
187
/// objects being accessible to the program. Both must be objects supported
188
188
/// as an [Expando] key.
189
189
///
190
- /// Multiple objects may be registered with the same finalization token,
191
- /// and the same object may be registered multiple times with different,
192
- /// or the same, finalization token.
190
+ /// Multiple objects may be using the same finalization token,
191
+ /// and the finalizer can be attached multiple times to the same object
192
+ /// with different, or the same, finalization token.
193
193
///
194
- /// The callback may be called at most once per registration , and not
195
- /// for registrations which have been unregistered since they were registered .
196
- void register (Object value, FT finalizationToken , {Object? unregisterToken });
194
+ /// The callback may be called at most once per attachment , and not
195
+ /// for registrations which have been detached since they were attached .
196
+ void attach (Object value, T token , {Object? detachKey });
197
197
198
- /// Unregisters any finalization callbacks registered with [unregisterToken]
199
- /// as unregister-token.
198
+ /// Detaches the finalizer from any objects that used [detachKey] when
199
+ /// attaching the finalizer to them.
200
+ ///
201
+ /// If the finalizer was attached multiple times to the same object with different
202
+ /// detachment keys, only those attachments which used [detachKey] are
203
+ /// removed.
200
204
///
201
- /// After unregistering, those callbacks will not happen even if the
202
- /// registered object becomes inaccessible.
203
- void unregister (Object unregisterToken );
205
+ /// After detaching, an attachment won't cause any callbacks to happen if the
206
+ /// object become inaccessible.
207
+ void detach (Object detachKey );
204
208
}
205
209
206
210
/// A weak reference to another object.
@@ -212,23 +216,23 @@ abstract class FinalizationRegistry<FT> {
212
216
/// _The referenced object may be garbage collected when the only reachable
213
217
/// references to it are weak._
214
218
///
215
- /// Not all objects are supported as targets for weak references.
216
- /// The [WeakRef ] constructor will reject any object that is not
219
+ /// Not all objects are supported as targets for weak references.
220
+ /// The [WeakReference ] constructor will reject any object that is not
217
221
/// supported as an [Expando] key.
218
- abstract class WeakRef <T extends Object> {
219
- /// Create a [WeakRef ] pointing to the given [target].
220
- ///
222
+ abstract class WeakReference <T extends Object> {
223
+ /// Create a [WeakReference ] pointing to the given [target].
224
+ ///
221
225
/// The [target] must be an object supported as an [Expando] key.
222
- external factory WeakRef(T target);
226
+ /// Which means [target] can not be a number, a string, a boolean, or
227
+ /// the `null` value.
228
+ external factory WeakReference(T target);
223
229
224
230
/// The current object weakly referenced by [this], if any.
225
- ///
231
+ ///
226
232
/// The value os either the object supplied in the constructor,
227
233
/// or `null` if the weak reference has been cleared.
228
234
T? get target;
229
235
}
230
-
231
- typedef WeakMap = Expando;
232
236
```
233
237
234
238
The following classes are added to ` dart:ffi ` library:
@@ -247,55 +251,92 @@ abstract class Finalizable {
247
251
typedef NativeFinalizer = Void Function(Pointer<Void>);
248
252
typedef NativeFinalizerPtr = Pointer<NativeFunction<NativeFinalizer>>
249
253
250
- /// [FinalizationRegistry] which will execute its finalizers as early as
251
- /// possible without waiting for control to return to the event loop.
254
+ /// A native finalizer which can be attached to Dart objects.
255
+ ///
256
+ /// When [attach]ed to a Dart object, this finalizer's native callback is called
257
+ /// after the Dart object is garbage collected or becomes inaccessible for other
258
+ /// reasons.
259
+ ///
260
+ /// Callbacks will happen as early as possible, when the object becomes
261
+ /// inaccessible to the program, and may happen at any moment during execution
262
+ /// of the program. At the latest, when an isolate group shuts down,
263
+ /// this callback is guaranteed to be called for each object in that isolate
264
+ /// group that the finalizer is still attached to.
252
265
///
253
- /// Will also invoke finalization callbacks when the isolate which created
254
- /// this finalization registry is shutting down.
255
- abstract class NativeFinalizationRegistry<F extends Finalizable>
256
- extends FinalizationRegistry<Pointer> {
257
- /// Creates a finalization registry with the given finalization
258
- /// callback.
266
+ /// Compared to the [Finalizer] from `dart:core`, which makes no promises to
267
+ /// ever call an attached callback, this native finalizer promises that all
268
+ /// attached finalizers are definitely called at least once before the program
269
+ /// ends, and the callbacks are called as soon as possible after an object
270
+ /// is recognized as inaccessible.
271
+ abstract class NativeFinalizer<T> {
272
+ /// Creates a finalizer with the given finalization callback.
259
273
///
260
- /// Note: [callback] is expected to be a native function which can be
274
+ /// Note: the [callback] is expected to be a native function which can be
261
275
/// executed outside of a Dart isolate. This means that passing an FFI
262
276
/// trampoline (a function pointer obtained via [Pointer.fromFunction]) is
263
277
/// not supported for arbitrary Dart functions. This constructor will throw
264
278
/// if an unsupported [callback] is passed to it.
265
279
///
266
- /// [callback] might be invoked on an arbitrary thread and not necessary
280
+ /// The [callback] might be invoked on an arbitrary thread and not necessary
267
281
/// on the same thread that created [FinalizationRegistry].
268
- external factory NativeFinalizationRegistry(NativeFinalizerPtr callback);
282
+ external factory NativeFinalizer(NativeFinalizerPtr callback);
283
+
284
+ /// Attaches this finalizer to the given [value].
285
+ ///
286
+ /// When [value] is no longer accessible to the program,
287
+ /// the registry will call its callback function with [finalizationToken]
288
+ /// as argument.
289
+ ///
290
+ /// The [value] and [detachKey] arguments do not count towards those
291
+ /// objects being accessible to the program. Both must be objects supported
292
+ /// as an [Expando] key.
293
+ ///
294
+ /// Multiple objects may be using the same finalization token,
295
+ /// and the finalizer can be attached multiple times to the same object
296
+ /// with different, or the same, finalization token.
297
+ ///
298
+ /// The callback may be called at most once per attachment, and not
299
+ /// for registrations which have been detached since they were attached.
300
+ ///
301
+ /// [externalSize] is an amount of native (non-Dart) memory owned by the
302
+ /// given [value]. This information is used to drive garbage collection
303
+ /// scheduling heuristics.
304
+ void attach(Object value, T token, {Object? detachKey, int externalSize}});
269
305
270
- /// Same as [super.register] but allows to specify an [externalSize] to
271
- /// guide GC heuristics.
272
- void register(covariant F value,
273
- Pointer finalizationToken,
274
- {Object? unregisterToken, int externalSize});
306
+ /// Detaches the finalizer from any objects that used [detachKey] when
307
+ /// attaching the finalizer to them.
308
+ ///
309
+ /// If the finalizer was attached multiple times to the same object with different
310
+ /// detachment keys, only those attachments which used [detachKey] are
311
+ /// removed.
312
+ ///
313
+ /// After detaching, an attachment won't cause any callbacks to happen if the
314
+ /// object become inaccessible.
315
+ void detach(Object detachKey);
275
316
}
276
317
```
277
318
278
- ` FinalizationRegistry ` was directly modeled after its
319
+ ` Finalizer ` was directly modeled after its
279
320
[ JavaScript counterpart] [ MDN FinalizationRegistry ] and only supports
280
- asynchronous finalization, while ` NativeFinalizationRegistry ` is added to
321
+ asynchronous finalization, while ` NativeFinalizer ` is added to
281
322
` dart:ffi ` to allow eager synchronous finalization.
282
323
283
- Note differences in API between ` FinalizationRegistry ` and
284
- ` NativeFinalizationRegistry ` :
324
+ Note differences in API between ` Finalizer ` and
325
+ ` NativeFinalizer ` :
285
326
286
- - ` NativeFinalizationRegistry ` requires objects which are registered with it
327
+ - ` NativeFinalizer ` requires objects which are registered with it
287
328
to implement ` Finalizable ` interface, which serves as a marker instructing
288
329
optimizing compiler to provide stronger liveness guarantees for an object. This
289
330
interface is our solution to the problem of _ premature finalization_ .
290
- - ` NativeFinalizationRegistry ` is constructed with a _ native function_ as a
331
+ - ` NativeFinalizer ` is constructed with a _ native function_ as a
291
332
callback rather than a Dart function. This is done to guarantee that eager
292
333
synchronous execution of a finalization callback is not going to produce any
293
334
side-effects observable from the pure Dart code.
294
335
295
336
Unfortunately the second restriction has far reaching implications: in many
296
337
commonly used native APIs destruction method does not adhere to a single
297
338
argument signature that we expect from a finalization callback. This makes
298
- ` NativeFinalizationRegistry ` API unusable without writing additional trampoline
339
+ ` NativeFinalizer ` API unusable without writing additional trampoline
299
340
code in native programming language (e.g. C), which we consider highly
300
341
undesirable: as we want ` dart:ffi ` to be expressive enough to enable developers
301
342
to create bindings in pure Dart, without requiring them to write and compile
@@ -319,7 +360,7 @@ Such isolate-dependent function can't be used as a finalization callback because
319
360
finalization callbacks should be callable in contexts when there is no current
320
361
isolate at all or isolates are not allowing entering into Dart code.
321
362
322
- It seems thus reasonable to restrict ` NativeFinalizationRegistry ` constructor
363
+ It seems thus reasonable to restrict ` NativeFinalizer ` constructor
323
364
in a way that would reject function pointers which are pointing to
324
365
native-to-Dart FFI trampolines.
325
366
@@ -380,7 +421,7 @@ class Mapping extends Finalizable {
380
421
return wrapper;
381
422
}
382
423
383
- static final _registry = NativeFinalizationRegistry (finalizeMapping);
424
+ static final _registry = NativeFinalizer (finalizeMapping);
384
425
}
385
426
```
386
427
0 commit comments