| // 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 dart.ffi; |
| |
| /// Marker interface for objects which should not be finalized too soon. |
| /// |
| /// Any local variable with a static type that _includes `Finalizable`_ |
| /// is guaranteed to be alive until execution exits the code block where |
| /// the variable is in scope. |
| /// |
| /// A type _includes `Finalizable`_ if either |
| /// * the type is a non-`Never` subtype of `Finalizable`, or |
| /// * the type is `T?` or `FutureOr<T>` where `T` includes `Finalizable`. |
| /// |
| /// In other words, while an object is referenced by such a variable, |
| /// it is guaranteed to *not* be considered unreachable, |
| /// and the variable itself is considered alive for the entire duration |
| /// of its scope, even after it is last referenced. |
| /// |
| /// _Without this marker interface on the variable's type, a variable's |
| /// value might be garbage collected before the surrounding scope has |
| /// been completely executed, as long as the variable is definitely not |
| /// referenced again. That can, in turn, trigger a `NativeFinalizer` |
| /// to perform a callback. When the variable's type includes [Finalizable], |
| /// The `NativeFinalizer` callback is prevented from running until |
| /// the current code using that variable is complete._ |
| /// |
| /// For example, `finalizable` is kept alive during the execution of |
| /// `someNativeCall`: |
| /// |
| /// ```dart |
| /// void myFunction() { |
| /// final finalizable = MyFinalizable(Pointer.fromAddress(0)); |
| /// someNativeCall(finalizable.nativeResource); |
| /// } |
| /// |
| /// void someNativeCall(Pointer nativeResource) { |
| /// // .. |
| /// } |
| /// |
| /// class MyFinalizable implements Finalizable { |
| /// final Pointer nativeResource; |
| /// |
| /// MyFinalizable(this.nativeResource); |
| /// } |
| /// ``` |
| /// |
| /// Methods on a class implementing `Finalizable` keep the `this` object alive |
| /// for the duration of the method execution. _The `this` value is treated |
| /// like a local variable._ |
| /// |
| /// For example, `this` is kept alive during the execution of `someNativeCall` |
| /// in `myFunction`: |
| /// |
| /// ```dart |
| /// class MyFinalizable implements Finalizable { |
| /// final Pointer nativeResource; |
| /// |
| /// MyFinalizable(this.nativeResource); |
| /// |
| /// void myFunction() { |
| /// someNativeCall(nativeResource); |
| /// } |
| /// } |
| /// |
| /// void someNativeCall(Pointer nativeResource) { |
| /// // .. |
| /// } |
| /// ``` |
| /// |
| /// It is good practise to implement logic involving finalizables as methods |
| /// on the class that implements [Finalizable]. |
| /// |
| /// If a closure is created inside the block scope declaring the variable, and |
| /// that closure contains any reference to the variable, the variable stays |
| /// alive as long as the closure object does, or as long as the body of such a |
| /// closure is executing. |
| /// |
| /// For example, `finalizable` is kept alive by the closure object and until the |
| /// end of the closure body: |
| /// |
| /// ```dart |
| /// void doSomething() { |
| /// final resourceAction = myFunction(); |
| /// resourceAction(); // `finalizable` is alive until this call returns. |
| /// } |
| /// |
| /// void Function() myFunction() { |
| /// final finalizable = MyFinalizable(Pointer.fromAddress(0)); |
| /// return () { |
| /// someNativeCall(finalizable.nativeResource); |
| /// }; |
| /// } |
| /// |
| /// void someNativeCall(Pointer nativeResource) { |
| /// // .. |
| /// } |
| /// |
| /// class MyFinalizable implements Finalizable { |
| /// final Pointer nativeResource; |
| /// |
| /// MyFinalizable(this.nativeResource); |
| /// } |
| /// ``` |
| /// |
| /// Only captured variables are kept alive by closures, not all variables. |
| /// |
| /// For example, `finalizable` is not kept alive by the returned closure object: |
| /// |
| /// ```dart |
| /// void Function() myFunction() { |
| /// final finalizable = MyFinalizable(Pointer.fromAddress(0)); |
| /// final nativeResource = finalizable.nativeResource; |
| /// return () { |
| /// someNativeCall(nativeResource); |
| /// }; |
| /// } |
| /// |
| /// void someNativeCall(Pointer nativeResource) { |
| /// // .. |
| /// } |
| /// |
| /// class MyFinalizable implements Finalizable { |
| /// final Pointer nativeResource; |
| /// |
| /// MyFinalizable(this.nativeResource); |
| /// } |
| /// ``` |
| /// |
| /// It's likely an error if a resource extracted from a finalizable object |
| /// escapes the scope of the finalizable variable it's taken from. |
| /// |
| /// The behavior of `Finalizable` variables applies to asynchronous |
| /// functions too. Such variables are kept alive as long as any |
| /// code may still execute inside the scope that declared the variable, |
| /// or in a closure capturing the variable, |
| /// even if there are asynchronous delays during that execution. |
| /// |
| /// For example, `finalizable` is kept alive during the `await someAsyncCall()`: |
| /// |
| /// ```dart |
| /// Future<void> myFunction() async { |
| /// final finalizable = MyFinalizable(); |
| /// await someAsyncCall(); |
| /// } |
| /// |
| /// Future<void> someAsyncCall() async { |
| /// // .. |
| /// } |
| /// |
| /// class MyFinalizable implements Finalizable { |
| /// // .. |
| /// } |
| /// ``` |
| /// |
| /// Also in asynchronous code it's likely an error if a resource extracted from |
| /// a finalizable object escapes the scope of the finalizable variable it's |
| /// taken from. If you have to extract a resource from a `Finalizable`, you |
| /// should ensure the scope in which Finalizable is defined outlives the |
| /// resource by `await`ing any asynchronous code that uses the resource. |
| /// |
| /// For example, `this` is kept alive until `resource` is not used anymore in |
| /// `useAsync1`, but not in `useAsync2` and `useAsync3`: |
| /// |
| /// ```dart |
| /// class MyFinalizable { |
| /// final Pointer<Int8> resource; |
| /// |
| /// MyFinalizable(this.resource); |
| /// |
| /// Future<int> useAsync1() async { |
| /// return await useResource(resource); |
| /// } |
| /// |
| /// Future<int> useAsync2() async { |
| /// return useResource(resource); |
| /// } |
| /// |
| /// Future<int> useAsync3() { |
| /// return useResource(resource); |
| /// } |
| /// } |
| /// |
| /// /// Does not use [resource] after the returned future completes. |
| /// Future<int> useResource(Pointer<Int8> resource) async { |
| /// return resource.value; |
| /// } |
| /// ``` |
| /// |
| /// _It is possible for an asynchronous function to *stall* at an |
| /// `await`, such that the runtime system can see that there is no possible |
| /// way for that `await` to complete. In that case, no code after the |
| /// `await` will ever execute, including `finally` blocks, and the |
| /// variable may be considered dead along with everything else._ |
| /// |
| /// If you're not going to keep a variable alive yourself, make sure to pass the |
| /// finalizable object to other functions instead of just its resource. |
| /// |
| /// For example, `finalizable` is not kept alive by `myFunction` after it has |
| /// run to the end of its scope, while `someAsyncCall` could still continue |
| /// execution. However, `finalizable` is kept alive by `someAsyncCall` itself: |
| /// |
| /// ```dart |
| /// void myFunction() { |
| /// final finalizable = MyFinalizable(); |
| /// someAsyncCall(finalizable); |
| /// } |
| /// |
| /// Future<void> someAsyncCall(MyFinalizable finalizable) async { |
| /// // .. |
| /// } |
| /// |
| /// class MyFinalizable implements Finalizable { |
| /// // .. |
| /// } |
| /// ``` |
| // TODO(http://dartbug.com/44395): Add implicit await to Dart implementation. |
| // This will fix `useAsync2` above. |
| @Since('2.17') |
| abstract class Finalizable { |
| factory Finalizable._() => throw UnsupportedError(""); |
| } |
| |
| /// The native function type for [NativeFinalizer]s. |
| /// |
| /// A [NativeFinalizer]'s `callback` should have the C |
| /// `void nativeFinalizer(void* token)` type. |
| typedef NativeFinalizerFunction |
| = NativeFunction<Void Function(Pointer<Void> token)>; |
| |
| /// A native finalizer which can be attached to Dart objects. |
| /// |
| /// When [attach]ed to a Dart object, this finalizer's native callback is called |
| /// after the Dart object is garbage collected or becomes inaccessible for other |
| /// reasons. |
| /// |
| /// Callbacks will happen as early as possible, when the object becomes |
| /// inaccessible to the program, and may happen at any moment during execution |
| /// of the program. At the latest, when an isolate group shuts down, |
| /// this callback is guaranteed to be called for each object in that isolate |
| /// group that the finalizer is still attached to. |
| /// |
| /// Compared to the [Finalizer] from `dart:core`, which makes no promises to |
| /// ever call an attached callback, this native finalizer promises that all |
| /// attached finalizers are definitely called at least once before the program |
| /// ends, and the callbacks are called as soon as possible after an object |
| /// is recognized as inaccessible. |
| /// |
| /// When the callback is a Dart function rather than a native function, use |
| /// [Finalizer] instead. |
| /// |
| /// A native finalizer can be used to close native resources. See the following |
| /// example. |
| /// |
| /// ```dart |
| /// /// [Database] enables interacting with the native database. |
| /// /// |
| /// /// After [close] is called, cannot be used to [query]. |
| /// /// |
| /// /// If a [Database] is garbage collected, it is automatically closed by |
| /// /// means of a native finalizer. Prefer closing manually for timely |
| /// /// release of native resources. |
| /// /// |
| /// /// Note this class is incomplete and for illustration purposes only. |
| /// class Database implements Finalizable { |
| /// /// The native finalizer runs [_closeDatabasePointer] on [_nativeDatabase] |
| /// /// if the object is garbage collected. |
| /// /// |
| /// /// Keeps the finalizer itself reachable, otherwise it might be disposed |
| /// /// before the finalizer callback gets a chance to run. |
| /// static final _finalizer = |
| /// NativeFinalizer(_nativeDatabaseBindings.closeDatabaseAddress.cast()); |
| /// |
| /// /// The native resource. |
| /// /// |
| /// /// Should be closed exactly once with [_closeDatabase] or |
| /// /// [_closeDatabasePointer]. |
| /// Pointer<_NativeDatabase> _nativeDatabase; |
| /// |
| /// /// Used to prevent double close and usage after close. |
| /// bool _closed = false; |
| /// |
| /// Database._(this._nativeDatabase); |
| /// |
| /// /// Open a database. |
| /// factory Database.open() { |
| /// final nativeDatabase = _nativeDatabaseBindings.openDatabase(); |
| /// final database = Database._(nativeDatabase); |
| /// _finalizer.attach(database, nativeDatabase.cast(), detach: database); |
| /// return database; |
| /// } |
| /// |
| /// /// Closes this database. |
| /// /// |
| /// /// This database cannot be used anymore after it is closed. |
| /// void close() { |
| /// if (_closed) { |
| /// return; |
| /// } |
| /// _closed = true; |
| /// _finalizer.detach(this); |
| /// _nativeDatabaseBindings.closeDatabase(_nativeDatabase); |
| /// } |
| /// |
| /// /// Query the database. |
| /// /// |
| /// /// The database should not have been closed. |
| /// void query() { |
| /// if (_closed) { |
| /// throw StateError('The database has been closed.'); |
| /// } |
| /// |
| /// // Query the database. |
| /// } |
| /// } |
| /// |
| /// final _nativeDatabaseBindings = _NativeDatabaseLib(DynamicLibrary.process()); |
| /// |
| /// // The following classes are typically generated with `package:ffigen`. |
| /// // Use `symbol-address` to expose the address of the close function. |
| /// class _NativeDatabaseLib { |
| /// final DynamicLibrary _library; |
| /// |
| /// _NativeDatabaseLib(this._library); |
| /// |
| /// late final openDatabase = _library.lookupFunction< |
| /// Pointer<_NativeDatabase> Function(), |
| /// Pointer<_NativeDatabase> Function()>('OpenDatabase'); |
| /// late final closeDatabaseAddress = |
| /// _library.lookup<NativeFunction<Void Function(Pointer<_NativeDatabase>)>>( |
| /// 'CloseDatabase'); |
| /// late final closeDatabase = closeDatabaseAddress |
| /// .asFunction<void Function(Pointer<_NativeDatabase>)>(); |
| /// } |
| /// |
| /// class _NativeDatabase extends Opaque {} |
| /// ``` |
| @Since('2.17') |
| abstract class NativeFinalizer { |
| /// Creates a finalizer with the given finalization callback. |
| /// |
| /// The [callback] must be a native function which can be executed outside of |
| /// a Dart isolate. This means that passing an FFI trampoline (a function |
| /// pointer obtained via [Pointer.fromFunction]) is not supported. |
| /// |
| /// The [callback] might be invoked on an arbitrary thread and not necessary |
| /// on the same thread that created [NativeFinalizer]. |
| // TODO(https://dartbug.com/47778): Implement isolate independent code and |
| // update the above comment. |
| external factory NativeFinalizer(Pointer<NativeFinalizerFunction> callback); |
| |
| /// Attaches this finalizer to [value]. |
| /// |
| /// When [value] is no longer accessible to the program, |
| /// the finalizer will call its callback function with [token] |
| /// as argument. |
| /// |
| /// If a non-`null` [detach] value is provided, that object can be |
| /// passed to [Finalizer.detach] to remove the attachment again. |
| /// |
| /// The [value] and [detach] arguments do not count towards those |
| /// objects being accessible to the program. Both must be objects supported |
| /// as an [Expando] key. They may be the *same* object. |
| /// |
| /// Multiple objects may be using the same finalization token, |
| /// and the finalizer can be attached multiple times to the same object |
| /// with different, or the same, finalization token. |
| /// |
| /// The callback will be called exactly once per attachment, except for |
| /// registrations which have been detached since they were attached. |
| /// |
| /// The [externalSize] should represent the amount of native (non-Dart) memory |
| /// owned by the given [value]. This information is used for garbage |
| /// collection scheduling heuristics. |
| void attach(Finalizable value, Pointer<Void> token, |
| {Object? detach, int? externalSize}); |
| |
| /// Detaches this finalizer from values attached with [detach]. |
| /// |
| /// If this finalizer was attached multiple times to the same object with |
| /// different detachment keys, only those attachments which used [detach] |
| /// are removed. |
| /// |
| /// After detaching, an attachment won't cause any callbacks to happen if the |
| /// object become inaccessible. |
| void detach(Object detach); |
| } |