Skip to content

Commit

Permalink
Classes with Finalizable fields implements Finalizable
Browse files Browse the repository at this point in the history
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:
dart-lang/sdk#49643
  • Loading branch information
nielsenko committed Aug 15, 2022
1 parent 6c1b5d9 commit 26769d1
Show file tree
Hide file tree
Showing 10 changed files with 98 additions and 18 deletions.
8 changes: 7 additions & 1 deletion lib/src/app.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////////
import 'dart:ffi';
import 'dart:io';

import 'package:meta/meta.dart';
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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);
Expand Down
9 changes: 8 additions & 1 deletion lib/src/configuration.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,15 @@
//
////////////////////////////////////////////////////////////////////////////////
import 'dart:ffi';
import 'dart:io';

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.
Expand All @@ -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));
Expand Down Expand Up @@ -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;
}
Expand Down
15 changes: 13 additions & 2 deletions lib/src/credentials.dart
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
////////////////////////////////////////////////////////////////////////////////
import 'dart:convert';
import 'dart:ffi';

import 'native/realm_core.dart';
import 'app.dart';
Expand Down Expand Up @@ -53,7 +54,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;

Expand Down Expand Up @@ -101,13 +102,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]
Expand Down Expand Up @@ -156,5 +162,10 @@ class EmailPasswordAuthProvider {
}

extension EmailPasswordAuthProviderInternal on EmailPasswordAuthProvider {
@pragma('vm:never-inline')
void keepAlive() {
app.keepAlive();
}

static EmailPasswordAuthProvider create(App app) => EmailPasswordAuthProvider(app);
}
8 changes: 8 additions & 0 deletions lib/src/list.dart
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,14 @@ extension RealmListOfObject<T extends RealmObject> on RealmList<T> {

/// @nodoc
extension RealmListInternal<T extends Object> on RealmList<T> {
@pragma('vm:never-inline')
void keepAlive() {
final self = this;
if (self is ManagedRealmList<T>) {
self._handle.keepAlive();
}
}

ManagedRealmList<T> asManaged() => this is ManagedRealmList<T> ? this as ManagedRealmList<T> : throw RealmStateError('$this is not managed');

RealmListHandle get handle => asManaged()._handle;
Expand Down
11 changes: 7 additions & 4 deletions lib/src/native/realm_core.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1673,7 +1673,7 @@ class LastError {
}

void _traceFinalization(Object o) {
Realm.logger.log(RealmLogLevel.info, 'Finalizing: $o');
Realm.logger.log(RealmLogLevel.trace, 'Finalizing: $o');
}

final _debugFinalizer = Finalizer<Object>(_traceFinalization);
Expand All @@ -1683,6 +1683,9 @@ final _nativeFinalizer = NativeFinalizer(_realmLib.addresses.realm_release);
abstract class HandleBase<T extends NativeType> implements Finalizable {
final Pointer<T> _pointer;

@pragma('vm:never-inline')
void keepAlive() {}

HandleBase(this._pointer, int size) {
_nativeFinalizer.attach(this, _pointer.cast(), detach: this, externalSize: size);
assert(() {
Expand Down Expand Up @@ -1747,13 +1750,13 @@ class ReleasableHandle<T extends NativeType> extends HandleBase<T> {
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;
}
}

Expand Down
23 changes: 19 additions & 4 deletions lib/src/realm_class.dart
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
////////////////////////////////////////////////////////////////////////////////
import 'dart:async';
import 'dart:ffi';
import 'dart:io';

import 'package:logging/logging.dart';
Expand Down Expand Up @@ -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<Type, RealmMetadata> _metadata = <Type, RealmMetadata>{};
final RealmHandle _handle;

Expand Down Expand Up @@ -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);
}

Expand Down Expand Up @@ -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();
}
Expand Down Expand Up @@ -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) {
Expand Down
8 changes: 7 additions & 1 deletion lib/src/realm_object.dart
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
////////////////////////////////////////////////////////////////////////////////
import 'dart:async';
import 'dart:ffi';

import 'list.dart';
import 'native/realm_core.dart';
Expand Down Expand Up @@ -178,7 +179,7 @@ class RealmCoreAccessor implements RealmAccessor {
}
}

mixin RealmEntity {
mixin RealmEntity implements Finalizable {
Realm? _realm;

/// The [Realm] instance this object belongs to.
Expand All @@ -189,6 +190,11 @@ mixin RealmEntity {
}

extension RealmEntityInternal on RealmEntity {
@pragma('vm:never-inline')
void keepAlive() {
_realm?.keepAlive();
}

void setRealm(Realm value) => _realm = value;
}

Expand Down
8 changes: 7 additions & 1 deletion lib/src/results.dart
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import 'dart:async';
import 'dart:collection' as collection;
import 'dart:ffi';

import 'collections.dart';
import 'native/realm_core.dart';
Expand All @@ -27,7 +28,7 @@ import 'realm_class.dart';
/// added to or deleted from the Realm that match the underlying query.
///
/// {@category Realm}
class RealmResults<T extends RealmObject> extends collection.IterableBase<T> {
class RealmResults<T extends RealmObject> extends collection.IterableBase<T> implements Finalizable {
final RealmResultsHandle _handle;

/// The Realm instance this collection belongs to.
Expand Down Expand Up @@ -81,6 +82,11 @@ class RealmResults<T extends RealmObject> extends collection.IterableBase<T> {
/// @nodoc
//RealmResults package internal members
extension RealmResultsInternal on RealmResults {
@pragma('vm:never-inline')
void keepAlive() {
_handle.keepAlive();
}

RealmResultsHandle get handle => _handle;

static RealmResults<T> create<T extends RealmObject>(RealmResultsHandle handle, Realm realm) {
Expand Down
16 changes: 14 additions & 2 deletions lib/src/subscription.dart
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import 'dart:core';
import 'dart:collection';
import 'dart:ffi';

import 'native/realm_core.dart';
import 'realm_class.dart';
Expand All @@ -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);
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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<Subscription> {
abstract class SubscriptionSet with IterableMixin<Subscription> implements Finalizable {
final Realm _realm;
SubscriptionSetHandle _handle;

Expand Down Expand Up @@ -204,6 +210,12 @@ abstract class SubscriptionSet with IterableMixin<Subscription> {
}

extension SubscriptionSetInternal on SubscriptionSet {
@pragma('vm:never-inline')
void keepAlive() {
_realm.keepAlive();
_handle.keepAlive();
}

Realm get realm => _realm;
SubscriptionSetHandle get handle => _handle;

Expand Down
10 changes: 8 additions & 2 deletions lib/src/user.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down

0 comments on commit 26769d1

Please sign in to comment.