// 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` 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 myFunction() async { /// final finalizable = MyFinalizable(); /// await someAsyncCall(); /// } /// /// Future 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 resource; /// /// MyFinalizable(this.resource); /// /// Future useAsync1() async { /// return await useResource(resource); /// } /// /// Future useAsync2() async { /// return useResource(resource); /// } /// /// Future useAsync3() { /// return useResource(resource); /// } /// } /// /// /// Does not use [resource] after the returned future completes. /// Future useResource(Pointer 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 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 interface 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 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 isolate /// group shuts down, and the callbacks are called as soon as possible after /// an object is recognized as inaccessible. /// /// Note that an isolate group is not necessarily guaranteed to shutdown /// normally as the whole process might crash or be abruptly terminated /// by a function like `exit`. This means `NativeFinalizer` can not be /// relied upon for running actions on the programs exit. /// /// 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)>>( /// 'CloseDatabase'); /// late final closeDatabase = closeDatabaseAddress /// .asFunction)>(); /// } /// /// final class _NativeDatabase extends Opaque {} /// ``` @Since('2.17') abstract final 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 also means that passing an FFI trampoline (a function /// a function pointer obtained via [Pointer.fromFunction]) is not supported. /// /// The callback is not allowed to re-enter the Dart VM via Dart C APIs, with /// two exceptions: it is allowed to call `Dart_DeletePersistentHandle` and /// `Dart_DeleteWeakPersistentHandle`. Calling any other Dart C API function /// results in undefined behavior, which means it can cause anything from /// crashes and deadlocks to silent memory corruptions. /// /// The [callback] might be invoked on an arbitrary thread. It will have a /// current isolate group but will not have a current isolate. external factory NativeFinalizer(Pointer 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 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); } // To make dart2wasm compile without patch file. external void _attachAsTypedListFinalizer( Pointer callback, Object typedList, Pointer pointer, int? externalSize, );