From 1fd7dfcbab469084876848855fb7cb4eb157e877 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20Overg=C3=A5rd=20Nielsen?= Date: Mon, 15 Aug 2022 12:38:50 +0200 Subject: [PATCH] Classes with Finalizable fields implements Finalizable Classes that implements Finalizable: * App, * FlexibleSyncConfiguration, * HandleBase, * Realm, * RealmEntity, * RealmList, * RealmResults, * Subscription, * SubscriptionSet, and * User, The corresponding XInternal extension classes implements a keepAlive function to ensure finalizable fields always live as long as their finalizable parent. This is workaround for: https://github.com/dart-lang/sdk/issues/49643 Resolves: #581, resolves: #582 --- lib/src/app.dart | 8 +++++++- lib/src/configuration.dart | 9 ++++++++- lib/src/credentials.dart | 15 +++++++++++++-- lib/src/list.dart | 8 ++++++++ lib/src/native/realm_core.dart | 11 +++++++---- lib/src/realm_class.dart | 23 +++++++++++++++++++---- lib/src/realm_object.dart | 8 +++++++- lib/src/results.dart | 8 +++++++- lib/src/subscription.dart | 16 ++++++++++++++-- lib/src/user.dart | 10 ++++++++-- 10 files changed, 98 insertions(+), 18 deletions(-) diff --git a/lib/src/app.dart b/lib/src/app.dart index fba68ebccc..7eb3822ca8 100644 --- a/lib/src/app.dart +++ b/lib/src/app.dart @@ -15,6 +15,7 @@ // limitations under the License. // //////////////////////////////////////////////////////////////////////////////// +import 'dart:ffi'; import 'dart:io'; import 'package:meta/meta.dart'; @@ -108,7 +109,7 @@ class AppConfiguration { /// * Register uses and perform various user-related operations through authentication providers /// * Synchronize data between the local device and a remote Realm App with Synchronized Realms /// {@category Application} -class App { +class App implements Finalizable { final AppHandle _handle; /// The id of this application. This is the same as the appId in the [AppConfiguration] used to @@ -179,6 +180,11 @@ enum MetadataPersistenceMode { /// @nodoc extension AppInternal on App { + @pragma('vm:never-inline') + void keepAlive() { + _handle.keepAlive(); + } + AppHandle get handle => _handle; static App create(AppHandle handle) => App._(handle); diff --git a/lib/src/configuration.dart b/lib/src/configuration.dart index 5f53d8b09a..b17edf289c 100644 --- a/lib/src/configuration.dart +++ b/lib/src/configuration.dart @@ -16,6 +16,7 @@ // //////////////////////////////////////////////////////////////////////////////// +import 'dart:ffi'; import 'dart:io'; import 'package:path/path.dart' as _path; @@ -23,6 +24,7 @@ import 'package:path/path.dart' as _path; import 'native/realm_core.dart'; import 'realm_class.dart'; import 'init.dart'; +import 'user.dart'; /// The signature of a callback used to determine if compaction /// should be attempted. @@ -47,7 +49,7 @@ typedef InitialDataCallback = void Function(Realm realm); /// Configuration used to create a [Realm] instance /// {@category Configuration} -abstract class Configuration { +abstract class Configuration implements Finalizable { /// The default realm filename to be used. static String get defaultRealmName => _path.basename(defaultRealmPath); static set defaultRealmName(String name) => defaultRealmPath = _path.join(_path.dirname(defaultRealmPath), _path.basename(name)); @@ -278,6 +280,11 @@ class FlexibleSyncConfiguration extends Configuration { } extension FlexibleSyncConfigurationInternal on FlexibleSyncConfiguration { + @pragma('vm:never-inline') + void keepAlive() { + user.keepAlive(); + } + SessionStopPolicy get sessionStopPolicy => _sessionStopPolicy; set sessionStopPolicy(SessionStopPolicy value) => _sessionStopPolicy = value; } diff --git a/lib/src/credentials.dart b/lib/src/credentials.dart index 8643b2f20b..12d734e991 100644 --- a/lib/src/credentials.dart +++ b/lib/src/credentials.dart @@ -17,6 +17,7 @@ //////////////////////////////////////////////////////////////////////////////// import 'dart:convert'; +import 'dart:ffi'; import 'native/realm_core.dart'; import 'app.dart'; @@ -55,7 +56,7 @@ enum AuthProviderType { /// A class, representing the credentials used for authenticating a [User] /// {@category Application} -class Credentials { +class Credentials implements Finalizable { final RealmAppCredentialsHandle _handle; final AuthProviderType provider; @@ -109,13 +110,18 @@ class Credentials { /// @nodoc extension CredentialsInternal on Credentials { + @pragma('vm:never-inline') + void keepAlive() { + _handle.keepAlive(); + } + RealmAppCredentialsHandle get handle => _handle; } /// A class, encapsulating functionality for users, logged in with [Credentials.emailPassword()]. /// It is always scoped to a particular app. /// {@category Application} -class EmailPasswordAuthProvider { +class EmailPasswordAuthProvider implements Finalizable { final App app; /// Create a new EmailPasswordAuthProvider for the [app] @@ -164,5 +170,10 @@ class EmailPasswordAuthProvider { } extension EmailPasswordAuthProviderInternal on EmailPasswordAuthProvider { + @pragma('vm:never-inline') + void keepAlive() { + app.keepAlive(); + } + static EmailPasswordAuthProvider create(App app) => EmailPasswordAuthProvider(app); } diff --git a/lib/src/list.dart b/lib/src/list.dart index 0555e4e95a..9e1315896c 100644 --- a/lib/src/list.dart +++ b/lib/src/list.dart @@ -145,6 +145,14 @@ extension RealmListOfObject on RealmList { /// @nodoc extension RealmListInternal on RealmList { + @pragma('vm:never-inline') + void keepAlive() { + final self = this; + if (self is ManagedRealmList) { + self._handle.keepAlive(); + } + } + ManagedRealmList asManaged() => this is ManagedRealmList ? this as ManagedRealmList : throw RealmStateError('$this is not managed'); RealmListHandle get handle => asManaged()._handle; diff --git a/lib/src/native/realm_core.dart b/lib/src/native/realm_core.dart index a2b90e2ce8..8a4f91b2f5 100644 --- a/lib/src/native/realm_core.dart +++ b/lib/src/native/realm_core.dart @@ -1681,7 +1681,7 @@ class LastError { } void _traceFinalization(Object o) { - Realm.logger.log(RealmLogLevel.info, 'Finalizing: $o'); + Realm.logger.log(RealmLogLevel.trace, 'Finalizing: $o'); } final _debugFinalizer = Finalizer(_traceFinalization); @@ -1691,6 +1691,9 @@ final _nativeFinalizer = NativeFinalizer(_realmLib.addresses.realm_release); abstract class HandleBase implements Finalizable { final Pointer _pointer; + @pragma('vm:never-inline') + void keepAlive() {} + HandleBase(this._pointer, int size) { _nativeFinalizer.attach(this, _pointer.cast(), detach: this, externalSize: size); assert(() { @@ -1755,13 +1758,13 @@ class ReleasableHandle extends HandleBase { return; } _nativeFinalizer.detach(this); + _realmLib.realm_release(_pointer.cast()); + released = true; assert(() { - _traceFinalization(_pointer); _debugFinalizer.detach(this); + _traceFinalization(_pointer); return true; }()); - _realmLib.realm_release(_pointer.cast()); - released = true; } } diff --git a/lib/src/realm_class.dart b/lib/src/realm_class.dart index e1ccf13a55..7e39fcb76c 100644 --- a/lib/src/realm_class.dart +++ b/lib/src/realm_class.dart @@ -17,6 +17,7 @@ //////////////////////////////////////////////////////////////////////////////// import 'dart:async'; +import 'dart:ffi'; import 'dart:io'; import 'package:logging/logging.dart'; @@ -84,7 +85,7 @@ export 'session.dart' show Session, SessionState, ConnectionState, ProgressDirec /// A [Realm] instance represents a `Realm` database. /// /// {@category Realm} -class Realm { +class Realm implements Finalizable { final Map _metadata = {}; final RealmHandle _handle; @@ -237,6 +238,11 @@ class Realm { _subscriptions?.handle.release(); _subscriptions = null; + final c = config; + if (c is FlexibleSyncConfiguration) { + final i = c.user.app.id; + } + realmCore.closeRealm(this); } @@ -327,9 +333,9 @@ class Realm { ..level = RealmLogLevel.info ..onRecord.listen((event) => print(event)); - /// Used to shutdown Realm and allow the process to correctly release native resources and exit. - /// - /// Disclaimer: This method is mostly needed on Dart standalone and if not called the Dart probram will hang and not exit. + /// Used to shutdown Realm and allow the process to correctly release native resources and exit. + /// + /// Disclaimer: This method is mostly needed on Dart standalone and if not called the Dart probram will hang and not exit. /// This is a workaround of a Dart VM bug and will be removed in a future version of the SDK. static void shutdown() => scheduler.stop(); } @@ -364,6 +370,15 @@ class Transaction { /// @nodoc extension RealmInternal on Realm { + @pragma('vm:never-inline') + void keepAlive() { + _handle.keepAlive(); + final c = config; + if (c is FlexibleSyncConfiguration) { + c.keepAlive(); + } + } + RealmHandle get handle => _handle; static Realm getUnowned(Configuration config, RealmHandle handle) { diff --git a/lib/src/realm_object.dart b/lib/src/realm_object.dart index a6acf3fa38..40b853c9ae 100644 --- a/lib/src/realm_object.dart +++ b/lib/src/realm_object.dart @@ -17,6 +17,7 @@ //////////////////////////////////////////////////////////////////////////////// import 'dart:async'; +import 'dart:ffi'; import 'list.dart'; import 'native/realm_core.dart'; @@ -178,7 +179,7 @@ class RealmCoreAccessor implements RealmAccessor { } } -mixin RealmEntity { +mixin RealmEntity implements Finalizable { Realm? _realm; /// The [Realm] instance this object belongs to. @@ -189,6 +190,11 @@ mixin RealmEntity { } extension RealmEntityInternal on RealmEntity { + @pragma('vm:never-inline') + void keepAlive() { + _realm?.keepAlive(); + } + void setRealm(Realm value) => _realm = value; } diff --git a/lib/src/results.dart b/lib/src/results.dart index 1cd2f6e326..5c6c770fd2 100644 --- a/lib/src/results.dart +++ b/lib/src/results.dart @@ -18,6 +18,7 @@ import 'dart:async'; import 'dart:collection' as collection; +import 'dart:ffi'; import 'collections.dart'; import 'native/realm_core.dart'; @@ -27,7 +28,7 @@ import 'realm_class.dart'; /// added to or deleted from the Realm that match the underlying query. /// /// {@category Realm} -class RealmResults extends collection.IterableBase { +class RealmResults extends collection.IterableBase implements Finalizable { final RealmResultsHandle _handle; /// The Realm instance this collection belongs to. @@ -81,6 +82,11 @@ class RealmResults extends collection.IterableBase { /// @nodoc //RealmResults package internal members extension RealmResultsInternal on RealmResults { + @pragma('vm:never-inline') + void keepAlive() { + _handle.keepAlive(); + } + RealmResultsHandle get handle => _handle; static RealmResults create(RealmResultsHandle handle, Realm realm) { diff --git a/lib/src/subscription.dart b/lib/src/subscription.dart index e63da7e960..3e21855dea 100644 --- a/lib/src/subscription.dart +++ b/lib/src/subscription.dart @@ -18,6 +18,7 @@ import 'dart:core'; import 'dart:collection'; +import 'dart:ffi'; import 'native/realm_core.dart'; import 'realm_class.dart'; @@ -26,7 +27,7 @@ import 'realm_class.dart'; /// evaluate the query that the app subscribed to and will send data /// that matches it as well as remove data that no longer does. /// {@category Sync} -class Subscription { +class Subscription implements Finalizable { final SubscriptionHandle _handle; Subscription._(this._handle); @@ -64,6 +65,11 @@ class Subscription { } extension SubscriptionInternal on Subscription { + @pragma('vm:never-inline') + void keepAlive() { + _handle.keepAlive(); + } + SubscriptionHandle get handle => _handle; ObjectId get id => _id; } @@ -121,7 +127,7 @@ enum SubscriptionSetState { /// Realm is an expensive operation serverside, even if there's very little data that needs /// downloading. /// {@category Sync} -abstract class SubscriptionSet with IterableMixin { +abstract class SubscriptionSet with IterableMixin implements Finalizable { final Realm _realm; SubscriptionSetHandle _handle; @@ -204,6 +210,12 @@ abstract class SubscriptionSet with IterableMixin { } extension SubscriptionSetInternal on SubscriptionSet { + @pragma('vm:never-inline') + void keepAlive() { + _realm.keepAlive(); + _handle.keepAlive(); + } + Realm get realm => _realm; SubscriptionSetHandle get handle => _handle; diff --git a/lib/src/user.dart b/lib/src/user.dart index ba7a92f3e8..2fbf2f1736 100644 --- a/lib/src/user.dart +++ b/lib/src/user.dart @@ -29,14 +29,14 @@ import './app.dart'; /// locally on the device, and should be treated as sensitive data. /// {@category Application} class User { - final App? _app; + App? _app; final UserHandle _handle; /// The [App] with which the [User] is associated with. App get app { // The _app field may be null when we're retrieving a user from the session // rather than from the app. - return _app ?? AppInternal.create(realmCore.userGetApp(_handle)); + return _app ??= AppInternal.create(realmCore.userGetApp(_handle)); } User._(this._handle, this._app); @@ -190,6 +190,12 @@ extension UserIdentityInternal on UserIdentity { /// @nodoc extension UserInternal on User { + @pragma('vm:never-inline') + void keepAlive() { + _handle.keepAlive(); + _app?.keepAlive(); + } + UserHandle get handle => _handle; static User create(UserHandle handle, [App? app]) => User._(handle, app);