Skip to content

Commit

Permalink
Use Finalizable to ensure lexical liveness (#754)
Browse files Browse the repository at this point in the history
* HandleBase implements Finalizable, and uses a common NativeFinalizer

* 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:
dart-lang/sdk#49643

Resolves: #581, resolves: #582

* Remove redundant code

* Implement Finalizable on more classes
* RealmObject,
* RealmObjectChanges, and
* RealmCollectionChanges.

Drop from RealmEntity

* Implements Finalizable on SyncSession

* Don't use `assert(() { ...; return true; }());` trick for finalization trace. Instead have const flag and depend on tree-shaking

* Update CHANGELOG
  • Loading branch information
nielsenko authored Aug 16, 2022
1 parent d67c67b commit 92d151e
Show file tree
Hide file tree
Showing 17 changed files with 182 additions and 100 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@
* Support `Credentials.function` for login user with Custom Function Authentication Provider. ([#742](https://github.com/realm/realm-dart/pull/742))
* Added `update` flag on `Realm.add` and `Realm.addAll` to support upserts. ([#668](https://github.com/realm/realm-dart/pull/668))

### Fixed
* Use Dart 2.17 `Finalizable` to ensure lexically scoped lifetime of finalizable resources (Realm, App, etc.). ([#754](https://github.com/realm/realm-dart/pull/754))

### Internal
* Added a command to `realm_dart` for deleting Atlas App Services applications. Usage: `dart run realm_dart delete-apps`. By default it will delete apps from `http://localhost:9090` which is the endpoint of the local docker image. If `--atlas-cluster` is provided, it will authenticate, delete the application from the provided cluster. (PR [#663](https://github.com/realm/realm-dart/pull/663))
* Uses Realm Core v12.5.0
Expand Down
1 change: 1 addition & 0 deletions ffigen/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ functions:
symbol-address:
include:
- 'realm_dart_.*'
- 'realm_release'
structs:
exclude:
- '_.*'
Expand Down
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
37 changes: 32 additions & 5 deletions lib/src/collections.dart
Original file line number Diff line number Diff line change
@@ -1,9 +1,29 @@
////////////////////////////////////////////////////////////////////////////////
//
// Copyright 2022 Realm Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////////
import 'dart:ffi';
import 'native/realm_core.dart';

/// Contains index information about objects that moved within the same collection.
class Move {
/// The index in the old version of the collection.
final int from;

/// The index in the new version of the collection.
final int to;

Expand All @@ -22,10 +42,10 @@ class CollectionChanges {
}

/// Describes the changes in a Realm collection since the last time the notification callback was invoked.
class RealmCollectionChanges {
class RealmCollectionChanges implements Finalizable {
final RealmCollectionChangesHandle _handle;
CollectionChanges? _values;

RealmCollectionChanges(this._handle);

CollectionChanges get _changes => _values ??= realmCore.getCollectionChanges(_handle);
Expand All @@ -38,11 +58,18 @@ class RealmCollectionChanges {

/// The indexes of the objects in the new collection which were modified in this version.
List<int> get modified => _changes.modifications;

/// The indexes of the objects in the collection which moved.
List<Move> get moved => _changes.moves;

/// The indexes in the new version of the collection which were modified. Conceptually, it contains the same entries as [modified] but after the
/// The indexes in the new version of the collection which were modified. Conceptually, it contains the same entries as [modified] but after the
/// insertions and deletions have been accounted for.
List<int> get newModified => _changes.modificationsAfter;
}
}

extension RealmCollectionChangesInternal on RealmCollectionChanges {
@pragma('vm:never-inline')
void keepAlive() {
_handle.keepAlive();
}
}
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
19 changes: 15 additions & 4 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 @@ -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;

Expand Down Expand Up @@ -98,8 +99,8 @@ class Credentials {
/// Returns a [Credentials] object that can be used to authenticate a user with a Google account using an id token.
Credentials.googleIdToken(String idToken)
: _handle = realmCore.createAppCredentialsGoogleIdToken(idToken),
provider = AuthProviderType.google;
provider = AuthProviderType.google;

/// Returns a [Credentials] object that can be used to authenticate a user with a custom Function.
/// [Custom Function Authentication Docs](https://www.mongodb.com/docs/atlas/app-services/authentication/custom-function/)
Credentials.function(String payload)
Expand All @@ -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]
Expand Down Expand Up @@ -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);
}
12 changes: 11 additions & 1 deletion lib/src/list.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 @@ -29,7 +30,7 @@ import 'results.dart';
/// added to or deleted from the collection or from the Realm.
///
/// {@category Realm}
abstract class RealmList<T extends Object> with RealmEntity implements List<T> {
abstract class RealmList<T extends Object> with RealmEntity implements List<T>, Finalizable {
/// Gets a value indicating whether this collection is still valid to use.
///
/// Indicates whether the [Realm] instance hasn't been closed,
Expand Down Expand Up @@ -145,6 +146,15 @@ 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>) {
realm.keepAlive();
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
55 changes: 2 additions & 53 deletions lib/src/native/realm_bindings.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2963,27 +2963,6 @@ class RealmLibrary {
ffi.Pointer<realm_thread_safe_reference_t> Function(
ffi.Pointer<ffi.Void>)>();

Dart_FinalizableHandle realm_dart_attach_finalizer(
Object handle,
ffi.Pointer<ffi.Void> realmPtr,
int size,
) {
return _realm_dart_attach_finalizer(
handle,
realmPtr,
size,
);
}

late final _realm_dart_attach_finalizerPtr = _lookup<
ffi.NativeFunction<
Dart_FinalizableHandle Function(ffi.Handle, ffi.Pointer<ffi.Void>,
ffi.Int)>>('realm_dart_attach_finalizer');
late final _realm_dart_attach_finalizer =
_realm_dart_attach_finalizerPtr.asFunction<
Dart_FinalizableHandle Function(
Object, ffi.Pointer<ffi.Void>, int)>();

ffi.Pointer<realm_scheduler_t> realm_dart_create_scheduler(
int isolateId,
int port,
Expand All @@ -3001,23 +2980,6 @@ class RealmLibrary {
late final _realm_dart_create_scheduler = _realm_dart_create_schedulerPtr
.asFunction<ffi.Pointer<realm_scheduler_t> Function(int, int)>();

void realm_dart_delete_finalizable(
Dart_FinalizableHandle finalizable_handle,
Object handle,
) {
return _realm_dart_delete_finalizable(
finalizable_handle,
handle,
);
}

late final _realm_dart_delete_finalizablePtr = _lookup<
ffi.NativeFunction<
ffi.Void Function(Dart_FinalizableHandle,
ffi.Handle)>>('realm_dart_delete_finalizable');
late final _realm_dart_delete_finalizable = _realm_dart_delete_finalizablePtr
.asFunction<void Function(Dart_FinalizableHandle, Object)>();

void realm_dart_delete_persistent_handle(
ffi.Pointer<ffi.Void> handle,
) {
Expand Down Expand Up @@ -9584,22 +9546,11 @@ class RealmLibrary {
class _SymbolAddresses {
final RealmLibrary _library;
_SymbolAddresses(this._library);
ffi.Pointer<
ffi.NativeFunction<
Dart_FinalizableHandle Function(
ffi.Handle, ffi.Pointer<ffi.Void>, ffi.Int)>>
get realm_dart_attach_finalizer =>
_library._realm_dart_attach_finalizerPtr;
ffi.Pointer<
ffi.NativeFunction<
ffi.Pointer<realm_scheduler_t> Function(ffi.Uint64, Dart_Port)>>
get realm_dart_create_scheduler =>
_library._realm_dart_create_schedulerPtr;
ffi.Pointer<
ffi.NativeFunction<
ffi.Void Function(Dart_FinalizableHandle, ffi.Handle)>>
get realm_dart_delete_finalizable =>
_library._realm_dart_delete_finalizablePtr;
ffi.Pointer<ffi.NativeFunction<ffi.Void Function(ffi.Pointer<ffi.Void>)>>
get realm_dart_delete_persistent_handle =>
_library._realm_dart_delete_persistent_handlePtr;
Expand Down Expand Up @@ -9674,10 +9625,10 @@ class _SymbolAddresses {
ffi.Pointer<ffi.NativeFunction<ffi.Handle Function(ffi.Pointer<ffi.Void>)>>
get realm_dart_weak_handle_to_object =>
_library._realm_dart_weak_handle_to_objectPtr;
ffi.Pointer<ffi.NativeFunction<ffi.Void Function(ffi.Pointer<ffi.Void>)>>
get realm_release => _library._realm_releasePtr;
}

typedef Dart_FinalizableHandle = ffi.Pointer<_Dart_FinalizableHandle>;

/// A port is used to send or receive inter-isolate messages
typedef Dart_Port = ffi.Int64;

Expand Down Expand Up @@ -9720,8 +9671,6 @@ class UnnamedUnion2 extends ffi.Union {
external int logic_error_kind;
}

class _Dart_FinalizableHandle extends ffi.Opaque {}

class realm_app extends ffi.Opaque {}

class realm_app_config extends ffi.Opaque {}
Expand Down
Loading

0 comments on commit 92d151e

Please sign in to comment.