| // Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| |
| // part of "core_patch.dart"; |
| |
| @patch |
| @pragma("vm:entry-point") |
| abstract class Finalizer<T> { |
| @patch |
| factory Finalizer(void Function(T) callback) = _FinalizerImpl<T>; |
| } |
| |
| @pragma("vm:entry-point") |
| class _FinalizerImpl<T> extends FinalizerBase implements Finalizer<T> { |
| @pragma("vm:recognized", "other") |
| @pragma("vm:prefer-inline") |
| external void Function(T) get _callback; |
| @pragma("vm:recognized", "other") |
| @pragma("vm:prefer-inline") |
| external set _callback(void Function(T) value); |
| |
| /// Constructs a finalizer. |
| /// |
| /// This is fine as a non-atomic operation, because the GC only looks at |
| /// finalizer instances when it process their entries. By preventing inlining |
| /// we ensure the the finalizer to have been fully initialized by the time |
| /// any [attach] on it is called. |
| /// |
| /// Alternatively, we could make it a recognized method and add a reachability |
| /// fence on the relevant members. |
| @pragma('vm:never-inline') |
| _FinalizerImpl(void Function(T) callback) { |
| allEntries = <FinalizerEntry>{}; |
| _callback = Zone.current.bindUnaryCallbackGuarded(callback); |
| setIsolate(); |
| isolateRegisterFinalizer(); |
| } |
| |
| void attach(Object value, T token, {Object? detach}) { |
| checkValidWeakTarget(value, 'value'); |
| if (detach != null) { |
| checkValidWeakTarget(detach, 'detach'); |
| } |
| |
| // Initializing the entry in a non-atomic way should be fine. |
| // The only interesting step in the GC is when value is collected. |
| // If the entry gets processed before initializing value, it will be null, |
| // and this is fine. We will not consider it as being collected that GC. |
| final entry = FinalizerEntry() |
| ..value = value |
| ..token = token |
| ..detach = detach |
| ..finalizer = this; |
| allEntries.add(entry); |
| // Ensure value stays reachable until after having initialized the entry. |
| // This ensures the token and finalizer are set. |
| reachabilityFence(value); |
| |
| if (detach != null) { |
| (detachments[detach] ??= <FinalizerEntry>{}).add(entry); |
| } |
| } |
| |
| void _runFinalizers() { |
| FinalizerEntry? entry = exchangeEntriesCollectedWithNull(); |
| while (entry != null) { |
| final token = entry.token; |
| // Check token for identical, detach might have been called. |
| if (!identical(token, entry)) { |
| _callback(unsafeCast<T>(token)); |
| } |
| allEntries.remove(entry); |
| final detach = entry.detach; |
| if (detach != null) { |
| detachments[detach]?.remove(entry); |
| } |
| entry = entry.next; |
| } |
| } |
| |
| @pragma("vm:entry-point", "call") |
| static _handleFinalizerMessage(_FinalizerImpl finalizer) { |
| finalizer._runFinalizers(); |
| } |
| } |