From b5a126f2e7de2a27675a411a65728d9c9e3f5a09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Wed, 1 Nov 2023 14:13:35 +0100 Subject: [PATCH 01/23] Fix crashes when posting to released scheduler --- CHANGELOG.md | 1 + .../kotlin/internal/interop/RealmInterop.kt | 16 ++++++++-- .../kotlin/internal/interop/RealmInterop.kt | 27 ++++++++++++----- .../io/realm/kotlin/test/CinteropTest.kt | 2 +- packages/external/core | 2 +- .../src/main/jni/realm_api_helpers.cpp | 30 +++++++++++-------- .../kotlin/io/realm/kotlin/Realm.kt | 2 +- .../kotlin/internal/ConfigurationImpl.kt | 2 +- .../util/CoroutineDispatcherFactory.kt | 1 - .../test/StandaloneDynamicMutableRealm.kt | 1 - 10 files changed, 55 insertions(+), 29 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2ca238e662..e6e951da3b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ This release upgrades the Sync metadata in a way that is not compatible with old ### Fixed * `Realm.close()` is now idempotent. * Fix error in `RealmAny.equals` that would sometimes return `true` when comparing RealmAnys wrapping same type but different values. (Issue [#1523](https://github.com/realm/realm-kotlin/pull/1523)) +* Fix craches caused by posting to a released scheduler. (Issue ...) * [Sync] If calling a function on App Services that resulted in a redirect, it would only redirect for GET requests. (Issue [#1517](https://github.com/realm/realm-kotlin/pull/1517)) * [Sync] Manual client reset on Windows would not trigger correctly when run inside `onManualResetFallback`. (Issue [#1515](https://github.com/realm/realm-kotlin/pull/1515)) * [Sync] `ClientResetRequiredException.executeClientReset()` now returns a boolean indicating if the manual reset fully succeded or not. (Issue [#1515](https://github.com/realm/realm-kotlin/pull/1515)) diff --git a/packages/cinterop/src/jvm/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt b/packages/cinterop/src/jvm/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt index cd48ae8854..6fae667eac 100644 --- a/packages/cinterop/src/jvm/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt +++ b/packages/cinterop/src/jvm/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt @@ -32,7 +32,9 @@ import io.realm.kotlin.internal.interop.sync.SyncUserIdentity import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch +import kotlinx.coroutines.suspendCancellableCoroutine import org.mongodb.kbson.ObjectId +import java.util.concurrent.atomic.AtomicBoolean // FIXME API-CLEANUP Rename io.realm.interop. to something with platform? // https://github.com/realm/realm-kotlin/issues/56 @@ -182,10 +184,10 @@ actual object RealmInterop { } actual fun realm_create_scheduler(): RealmSchedulerPointer = - LongPointerWrapper(realmc.realm_create_generic_scheduler()) + LongPointerWrapper(realmc.realm_create_generic_scheduler(), managed = false) actual fun realm_create_scheduler(dispatcher: CoroutineDispatcher): RealmSchedulerPointer = - LongPointerWrapper(realmc.realm_create_scheduler(JVMScheduler(dispatcher))) + LongPointerWrapper(realmc.realm_create_scheduler(JVMScheduler(dispatcher)), managed = false) actual fun realm_open( config: RealmConfigurationPointer, @@ -199,6 +201,7 @@ actual object RealmInterop { realm_config_set_data_initialization_function(config, callback) realmc.realm_config_set_scheduler(config.cptr(), scheduler.cptr()) + scheduler.release() val realmPtr = LongPointerWrapper(realmc.realm_open(config.cptr())) // Ensure that we can read version information, etc. @@ -2095,10 +2098,17 @@ fun ObjectId.asRealmObjectIdT(): realm_object_id_t { private class JVMScheduler(dispatcher: CoroutineDispatcher) { val scope: CoroutineScope = CoroutineScope(dispatcher) + val cancelled = AtomicBoolean(false) fun notifyCore(schedulerPointer: Long) { scope.launch { - realmc.invoke_core_notify_callback(schedulerPointer) + if (!cancelled.get()) { + realmc.invoke_core_notify_callback(schedulerPointer) + } } } + + fun cancel() { + cancelled.set(true) + } } diff --git a/packages/cinterop/src/nativeDarwin/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt b/packages/cinterop/src/nativeDarwin/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt index 54feccf235..46bcc0ca1c 100644 --- a/packages/cinterop/src/nativeDarwin/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt +++ b/packages/cinterop/src/nativeDarwin/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt @@ -125,6 +125,7 @@ import realm_wrapper.realm_user_t import realm_wrapper.realm_value_t import realm_wrapper.realm_value_type import realm_wrapper.realm_version_id_t +import realm_wrapper.realm_work_queue_t import kotlin.collections.set import kotlin.native.internal.createCleaner @@ -519,6 +520,7 @@ actual object RealmInterop { // but requires opting in for @ExperimentalStdlibApi, and have really gotten it to play // for default cases. realm_wrapper.realm_config_set_scheduler(config.cptr(), scheduler.cptr()) + scheduler.release() val realmPtr = CPointerWrapper(realm_wrapper.realm_open(config.cptr())) // Ensure that we can read version information, etc. @@ -530,7 +532,7 @@ actual object RealmInterop { // If there is no notification dispatcher use the default scheduler. // Re-verify if this is actually needed when notification scheduler is fully in place. val scheduler = checkedPointerResult(realm_wrapper.realm_scheduler_make_default()) - return CPointerWrapper(scheduler) + return CPointerWrapper(scheduler, managed = false) } actual fun realm_create_scheduler(dispatcher: CoroutineDispatcher): RealmSchedulerPointer { @@ -545,17 +547,19 @@ actual object RealmInterop { // free: realm_wrapper.realm_free_userdata_func_t? /* = kotlinx.cinterop.CPointer? */) -> kotlin.Unit>>? */, staticCFunction { userdata -> printlntid("free") - userdata?.asStableRef()?.dispose() + val asStableRef: StableRef? = userdata?.asStableRef() + asStableRef?.get()?.cancel() + asStableRef?.dispose() }, // notify: realm_wrapper.realm_scheduler_notify_func_t? /* = kotlinx.cinterop.CPointer? */) -> kotlin.Unit>>? */, - staticCFunction { userdata -> + staticCFunction?, Unit> { userdata, work_queue -> // Must be thread safe val scheduler = userdata!!.asStableRef().get() printlntid("$scheduler notify") try { - scheduler.notify() + scheduler.notify(work_queue) } catch (e: Exception) { // Should never happen, but is included for development to get some indicators // on errors instead of silent crashes. @@ -584,7 +588,7 @@ actual object RealmInterop { scheduler.set_scheduler(capi_scheduler) - return CPointerWrapper(capi_scheduler) + return CPointerWrapper(capi_scheduler, managed = false) } actual fun realm_open_synchronized(config: RealmConfigurationPointer): RealmAsyncOpenTaskPointer { @@ -3382,7 +3386,7 @@ actual object RealmInterop { ) interface Scheduler { - fun notify() + fun notify(work_queue: CPointer?) } class SingleThreadDispatcherScheduler( @@ -3392,6 +3396,7 @@ actual object RealmInterop { val scope: CoroutineScope = CoroutineScope(dispatcher) val ref: CPointer lateinit var scheduler: CPointer + private val cancelled: AtomicBoolean = atomic(false) init { ref = StableRef.create(this).asCPointer() @@ -3401,11 +3406,13 @@ actual object RealmInterop { this.scheduler = scheduler } - override fun notify() { + override fun notify(work_queue: CPointer?) { scope.launch { try { printlntid("on dispatcher") - realm_wrapper.realm_scheduler_perform_work(scheduler) + if (!cancelled.value) { + realm_wrapper.realm_scheduler_perform_work(work_queue) + } } catch (e: Exception) { // Should never happen, but is included for development to get some indicators // on errors instead of silent crashes. @@ -3413,6 +3420,10 @@ actual object RealmInterop { } } } + + fun cancel() { + cancelled.value = true + } } } diff --git a/packages/cinterop/src/nativeDarwinTest/kotlin/io/realm/kotlin/test/CinteropTest.kt b/packages/cinterop/src/nativeDarwinTest/kotlin/io/realm/kotlin/test/CinteropTest.kt index 01fd6fec06..eea9bf231b 100644 --- a/packages/cinterop/src/nativeDarwinTest/kotlin/io/realm/kotlin/test/CinteropTest.kt +++ b/packages/cinterop/src/nativeDarwinTest/kotlin/io/realm/kotlin/test/CinteropTest.kt @@ -180,7 +180,7 @@ class CinteropTest { ) RealmInterop.realm_config_set_schema_version(nativeConfig, 1) RealmInterop.realm_create_scheduler() - .use { scheduler -> + .let { scheduler -> val (realm, fileCreated) = RealmInterop.realm_open(nativeConfig, scheduler) assertEquals(1L, RealmInterop.realm_get_num_classes(realm)) RealmInterop.realm_close(realm) diff --git a/packages/external/core b/packages/external/core index e6271d7230..7fe6e27891 160000 --- a/packages/external/core +++ b/packages/external/core @@ -1 +1 @@ -Subproject commit e6271d72308b40399890060f58a88cf568c2ee22 +Subproject commit 7fe6e27891985130134fe9815fa651a02e42087f diff --git a/packages/jni-swig-stub/src/main/jni/realm_api_helpers.cpp b/packages/jni-swig-stub/src/main/jni/realm_api_helpers.cpp index 98e52f6890..c105b7a1c8 100644 --- a/packages/jni-swig-stub/src/main/jni/realm_api_helpers.cpp +++ b/packages/jni-swig-stub/src/main/jni/realm_api_helpers.cpp @@ -264,6 +264,7 @@ class CustomJVMScheduler { JNIEnv *jenv = get_env(); jclass jvm_scheduler_class = jenv->FindClass("io/realm/kotlin/internal/interop/JVMScheduler"); m_notify_method = jenv->GetMethodID(jvm_scheduler_class, "notifyCore", "(J)V"); + m_cancel_method = jenv->GetMethodID(jvm_scheduler_class, "cancel", "()V"); m_jvm_dispatch_scheduler = jenv->NewGlobalRef(dispatchScheduler); } @@ -271,18 +272,14 @@ class CustomJVMScheduler { get_env(true)->DeleteGlobalRef(m_jvm_dispatch_scheduler); } - void set_scheduler(realm_scheduler_t* scheduler) { - m_scheduler = scheduler; - } - - void notify() { + void notify(realm_work_queue_t* work_queue) { // There is currently no signaling of creation/tear down of the core notifier thread, so we // just attach it as a daemon thread here on first notification to allow the JVM to // shutdown propertly. See https://github.com/realm/realm-core/issues/6429 auto jenv = get_env(true, true, "core-notifier"); jni_check_exception(jenv); jenv->CallVoidMethod(m_jvm_dispatch_scheduler, m_notify_method, - reinterpret_cast(m_scheduler)); + reinterpret_cast(work_queue)); } bool is_on_thread() const noexcept { @@ -293,12 +290,18 @@ class CustomJVMScheduler { return true; } + void cancel() { + auto jenv = get_env(true, true, "core-notifier"); + jenv->CallVoidMethod(m_jvm_dispatch_scheduler, m_cancel_method); + jni_check_exception(jenv); + } + private: std::thread::id m_id; jmethodID m_notify_method; + jmethodID m_cancel_method; jobject m_jvm_dispatch_scheduler; - realm_scheduler_t *m_scheduler; }; // Note: using jlong here will create a linker issue @@ -309,8 +312,8 @@ class CustomJVMScheduler { // // I suspect this could be related to the fact that jni.h defines jlong differently between Android (typedef int64_t) // and JVM which is a (typedef long long) resulting in a different signature of the method that could be found by the linker. -void invoke_core_notify_callback(int64_t scheduler) { - realm_scheduler_perform_work(reinterpret_cast(scheduler)); +void invoke_core_notify_callback(int64_t work_queue) { + realm_scheduler_perform_work(reinterpret_cast(work_queue)); } realm_scheduler_t* @@ -319,13 +322,16 @@ realm_create_scheduler(jobject dispatchScheduler) { auto jvmScheduler = new CustomJVMScheduler(dispatchScheduler); auto scheduler = realm_scheduler_new( jvmScheduler, - [](void *userdata) { delete(static_cast(userdata)); }, - [](void *userdata) { static_cast(userdata)->notify(); }, + [](void *userdata) { + auto jvmScheduler = static_cast(userdata); + jvmScheduler->cancel(); + delete(jvmScheduler); + }, + [](void *userdata, realm_work_queue_t* work_queue) { static_cast(userdata)->notify(work_queue); }, [](void *userdata) { return static_cast(userdata)->is_on_thread(); }, [](const void *userdata, const void *userdata_other) { return userdata == userdata_other; }, [](void *userdata) { return static_cast(userdata)->can_invoke(); } ); - jvmScheduler->set_scheduler(scheduler); return scheduler; } throw std::runtime_error("Null dispatchScheduler"); diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/Realm.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/Realm.kt index ca26a0f362..5c60bdfe99 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/Realm.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/Realm.kt @@ -121,7 +121,7 @@ public interface Realm : TypedRealm { val config = (configuration as InternalConfiguration) return RealmInterop.realm_create_scheduler() - .use { scheduler -> + .let { scheduler -> val (dbPointer, _) = RealmInterop.realm_open( config = config.createNativeConfiguration(), scheduler = scheduler diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/ConfigurationImpl.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/ConfigurationImpl.kt index 3463f478c0..70ceca2e65 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/ConfigurationImpl.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/ConfigurationImpl.kt @@ -110,7 +110,7 @@ public open class ConfigurationImpl( override suspend fun openRealm(realm: RealmImpl): Pair { val configPtr = realm.configuration.createNativeConfiguration() return RealmInterop.realm_create_scheduler() - .use { scheduler -> + .let { scheduler -> val (dbPointer, fileCreated) = RealmInterop.realm_open(configPtr, scheduler) val liveRealmReference = LiveRealmReference(realm, dbPointer) val frozenReference = liveRealmReference.snapshot(realm) diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/util/CoroutineDispatcherFactory.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/util/CoroutineDispatcherFactory.kt index 370af4d31f..bba0b3849e 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/util/CoroutineDispatcherFactory.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/util/CoroutineDispatcherFactory.kt @@ -120,7 +120,6 @@ public class LiveRealmContext( } override fun close() { - scheduler.release() dispatcherHolder.close() } } diff --git a/packages/test-base/src/commonMain/kotlin/io/realm/kotlin/test/StandaloneDynamicMutableRealm.kt b/packages/test-base/src/commonMain/kotlin/io/realm/kotlin/test/StandaloneDynamicMutableRealm.kt index 5784b90573..c5274e5ad7 100644 --- a/packages/test-base/src/commonMain/kotlin/io/realm/kotlin/test/StandaloneDynamicMutableRealm.kt +++ b/packages/test-base/src/commonMain/kotlin/io/realm/kotlin/test/StandaloneDynamicMutableRealm.kt @@ -39,7 +39,6 @@ internal class StandaloneDynamicMutableRealm private constructor( try { RealmInterop.realm_open(configuration.createNativeConfiguration(), scheduler) } catch (exception: Exception) { - scheduler.release() throw exception } ) { From f3e4ce362d56231c77bbf6dffcf8240bf5756fd2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Thu, 2 Nov 2023 09:30:44 +0100 Subject: [PATCH 02/23] Rework scheduler life cycle --- CHANGELOG.md | 2 +- .../kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt | 5 ++--- .../src/commonMain/kotlin/io/realm/kotlin/Realm.kt | 2 +- .../kotlin/io/realm/kotlin/internal/ConfigurationImpl.kt | 2 +- .../realm/kotlin/internal/util/CoroutineDispatcherFactory.kt | 1 + .../io/realm/kotlin/test/StandaloneDynamicMutableRealm.kt | 1 + .../test/common/notifications/SystemNotificationTests.kt | 1 + 7 files changed, 8 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e6e951da3b..5d2be13f12 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,7 +12,7 @@ This release upgrades the Sync metadata in a way that is not compatible with old ### Fixed * `Realm.close()` is now idempotent. * Fix error in `RealmAny.equals` that would sometimes return `true` when comparing RealmAnys wrapping same type but different values. (Issue [#1523](https://github.com/realm/realm-kotlin/pull/1523)) -* Fix craches caused by posting to a released scheduler. (Issue ...) +* Fix craches caused by posting to a released scheduler. (Issue [#1543](https://github.com/realm/realm-kotlin/issues/1543)) * [Sync] If calling a function on App Services that resulted in a redirect, it would only redirect for GET requests. (Issue [#1517](https://github.com/realm/realm-kotlin/pull/1517)) * [Sync] Manual client reset on Windows would not trigger correctly when run inside `onManualResetFallback`. (Issue [#1515](https://github.com/realm/realm-kotlin/pull/1515)) * [Sync] `ClientResetRequiredException.executeClientReset()` now returns a boolean indicating if the manual reset fully succeded or not. (Issue [#1515](https://github.com/realm/realm-kotlin/pull/1515)) diff --git a/packages/cinterop/src/jvm/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt b/packages/cinterop/src/jvm/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt index 6fae667eac..cb11ea0b74 100644 --- a/packages/cinterop/src/jvm/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt +++ b/packages/cinterop/src/jvm/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt @@ -184,10 +184,10 @@ actual object RealmInterop { } actual fun realm_create_scheduler(): RealmSchedulerPointer = - LongPointerWrapper(realmc.realm_create_generic_scheduler(), managed = false) + LongPointerWrapper(realmc.realm_create_generic_scheduler()) actual fun realm_create_scheduler(dispatcher: CoroutineDispatcher): RealmSchedulerPointer = - LongPointerWrapper(realmc.realm_create_scheduler(JVMScheduler(dispatcher)), managed = false) + LongPointerWrapper(realmc.realm_create_scheduler(JVMScheduler(dispatcher))) actual fun realm_open( config: RealmConfigurationPointer, @@ -201,7 +201,6 @@ actual object RealmInterop { realm_config_set_data_initialization_function(config, callback) realmc.realm_config_set_scheduler(config.cptr(), scheduler.cptr()) - scheduler.release() val realmPtr = LongPointerWrapper(realmc.realm_open(config.cptr())) // Ensure that we can read version information, etc. diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/Realm.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/Realm.kt index 5c60bdfe99..ca26a0f362 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/Realm.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/Realm.kt @@ -121,7 +121,7 @@ public interface Realm : TypedRealm { val config = (configuration as InternalConfiguration) return RealmInterop.realm_create_scheduler() - .let { scheduler -> + .use { scheduler -> val (dbPointer, _) = RealmInterop.realm_open( config = config.createNativeConfiguration(), scheduler = scheduler diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/ConfigurationImpl.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/ConfigurationImpl.kt index 70ceca2e65..3463f478c0 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/ConfigurationImpl.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/ConfigurationImpl.kt @@ -110,7 +110,7 @@ public open class ConfigurationImpl( override suspend fun openRealm(realm: RealmImpl): Pair { val configPtr = realm.configuration.createNativeConfiguration() return RealmInterop.realm_create_scheduler() - .let { scheduler -> + .use { scheduler -> val (dbPointer, fileCreated) = RealmInterop.realm_open(configPtr, scheduler) val liveRealmReference = LiveRealmReference(realm, dbPointer) val frozenReference = liveRealmReference.snapshot(realm) diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/util/CoroutineDispatcherFactory.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/util/CoroutineDispatcherFactory.kt index bba0b3849e..370af4d31f 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/util/CoroutineDispatcherFactory.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/util/CoroutineDispatcherFactory.kt @@ -120,6 +120,7 @@ public class LiveRealmContext( } override fun close() { + scheduler.release() dispatcherHolder.close() } } diff --git a/packages/test-base/src/commonMain/kotlin/io/realm/kotlin/test/StandaloneDynamicMutableRealm.kt b/packages/test-base/src/commonMain/kotlin/io/realm/kotlin/test/StandaloneDynamicMutableRealm.kt index c5274e5ad7..5784b90573 100644 --- a/packages/test-base/src/commonMain/kotlin/io/realm/kotlin/test/StandaloneDynamicMutableRealm.kt +++ b/packages/test-base/src/commonMain/kotlin/io/realm/kotlin/test/StandaloneDynamicMutableRealm.kt @@ -39,6 +39,7 @@ internal class StandaloneDynamicMutableRealm private constructor( try { RealmInterop.realm_open(configuration.createNativeConfiguration(), scheduler) } catch (exception: Exception) { + scheduler.release() throw exception } ) { diff --git a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/notifications/SystemNotificationTests.kt b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/notifications/SystemNotificationTests.kt index b43ba6c9a6..97d265bc4d 100644 --- a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/notifications/SystemNotificationTests.kt +++ b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/notifications/SystemNotificationTests.kt @@ -76,5 +76,6 @@ class SystemNotificationTests { writer1.write { copyToRealm(Sample()) } writer2.write { copyToRealm(Sample()) } } + liveRealmContext.close() } } From f44046a169ed438eb2996cde8c361104f0d92126 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Thu, 2 Nov 2023 09:36:20 +0100 Subject: [PATCH 03/23] Clean up --- .../kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt | 5 ++--- .../kotlin/io/realm/kotlin/test/CinteropTest.kt | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/cinterop/src/nativeDarwin/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt b/packages/cinterop/src/nativeDarwin/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt index 46bcc0ca1c..91d7cdbc14 100644 --- a/packages/cinterop/src/nativeDarwin/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt +++ b/packages/cinterop/src/nativeDarwin/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt @@ -520,7 +520,6 @@ actual object RealmInterop { // but requires opting in for @ExperimentalStdlibApi, and have really gotten it to play // for default cases. realm_wrapper.realm_config_set_scheduler(config.cptr(), scheduler.cptr()) - scheduler.release() val realmPtr = CPointerWrapper(realm_wrapper.realm_open(config.cptr())) // Ensure that we can read version information, etc. @@ -532,7 +531,7 @@ actual object RealmInterop { // If there is no notification dispatcher use the default scheduler. // Re-verify if this is actually needed when notification scheduler is fully in place. val scheduler = checkedPointerResult(realm_wrapper.realm_scheduler_make_default()) - return CPointerWrapper(scheduler, managed = false) + return CPointerWrapper(scheduler) } actual fun realm_create_scheduler(dispatcher: CoroutineDispatcher): RealmSchedulerPointer { @@ -588,7 +587,7 @@ actual object RealmInterop { scheduler.set_scheduler(capi_scheduler) - return CPointerWrapper(capi_scheduler, managed = false) + return CPointerWrapper(capi_scheduler) } actual fun realm_open_synchronized(config: RealmConfigurationPointer): RealmAsyncOpenTaskPointer { diff --git a/packages/cinterop/src/nativeDarwinTest/kotlin/io/realm/kotlin/test/CinteropTest.kt b/packages/cinterop/src/nativeDarwinTest/kotlin/io/realm/kotlin/test/CinteropTest.kt index eea9bf231b..d200017463 100644 --- a/packages/cinterop/src/nativeDarwinTest/kotlin/io/realm/kotlin/test/CinteropTest.kt +++ b/packages/cinterop/src/nativeDarwinTest/kotlin/io/realm/kotlin/test/CinteropTest.kt @@ -180,7 +180,7 @@ class CinteropTest { ) RealmInterop.realm_config_set_schema_version(nativeConfig, 1) RealmInterop.realm_create_scheduler() - .let { scheduler -> + .run { scheduler -> val (realm, fileCreated) = RealmInterop.realm_open(nativeConfig, scheduler) assertEquals(1L, RealmInterop.realm_get_num_classes(realm)) RealmInterop.realm_close(realm) From 6d55eb6d72e4b2f2d5ef4528f8816421b9214331 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Thu, 2 Nov 2023 09:37:44 +0100 Subject: [PATCH 04/23] Naming --- .../kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/cinterop/src/nativeDarwin/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt b/packages/cinterop/src/nativeDarwin/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt index 91d7cdbc14..99185a5280 100644 --- a/packages/cinterop/src/nativeDarwin/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt +++ b/packages/cinterop/src/nativeDarwin/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt @@ -546,9 +546,9 @@ actual object RealmInterop { // free: realm_wrapper.realm_free_userdata_func_t? /* = kotlinx.cinterop.CPointer? */) -> kotlin.Unit>>? */, staticCFunction { userdata -> printlntid("free") - val asStableRef: StableRef? = userdata?.asStableRef() - asStableRef?.get()?.cancel() - asStableRef?.dispose() + val stableSchedulerRef: StableRef? = userdata?.asStableRef() + stableSchedulerRef?.get()?.cancel() + stableSchedulerRef?.dispose() }, // notify: realm_wrapper.realm_scheduler_notify_func_t? /* = kotlinx.cinterop.CPointer? */) -> kotlin.Unit>>? */, From 93be8fb57fed9b0635d59d7d6f7b7d9c09fa7aae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Thu, 2 Nov 2023 10:26:44 +0100 Subject: [PATCH 05/23] Fix test build errors --- .../jvm/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt | 1 - .../kotlin/io/realm/kotlin/test/CinteropTest.kt | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/cinterop/src/jvm/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt b/packages/cinterop/src/jvm/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt index cb11ea0b74..da5d7b6429 100644 --- a/packages/cinterop/src/jvm/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt +++ b/packages/cinterop/src/jvm/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt @@ -32,7 +32,6 @@ import io.realm.kotlin.internal.interop.sync.SyncUserIdentity import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch -import kotlinx.coroutines.suspendCancellableCoroutine import org.mongodb.kbson.ObjectId import java.util.concurrent.atomic.AtomicBoolean diff --git a/packages/cinterop/src/nativeDarwinTest/kotlin/io/realm/kotlin/test/CinteropTest.kt b/packages/cinterop/src/nativeDarwinTest/kotlin/io/realm/kotlin/test/CinteropTest.kt index d200017463..01fd6fec06 100644 --- a/packages/cinterop/src/nativeDarwinTest/kotlin/io/realm/kotlin/test/CinteropTest.kt +++ b/packages/cinterop/src/nativeDarwinTest/kotlin/io/realm/kotlin/test/CinteropTest.kt @@ -180,7 +180,7 @@ class CinteropTest { ) RealmInterop.realm_config_set_schema_version(nativeConfig, 1) RealmInterop.realm_create_scheduler() - .run { scheduler -> + .use { scheduler -> val (realm, fileCreated) = RealmInterop.realm_open(nativeConfig, scheduler) assertEquals(1L, RealmInterop.realm_get_num_classes(realm)) RealmInterop.realm_close(realm) From fc7b91ea3997a096c3c43e57f67de4ad8c5b31ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Thu, 2 Nov 2023 10:49:58 +0100 Subject: [PATCH 06/23] Bump to latest core --- CHANGELOG.md | 2 +- packages/external/core | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5d2be13f12..b1326a75b3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,7 +35,7 @@ GET requests. (Issue [#1517](https://github.com/realm/realm-kotlin/pull/1517)) * Minimum Android SDK: 16. ### Internal -* Updated to Realm Core 13.23.2, commit e6271d72308b40399890060f58a88cf568c2ee22. +* Updated to Realm Core 13.23.2, commit ba96288b5c88096998591f94a64b0e3a00d4c92f. ## 1.11.1 (2023-09-07) diff --git a/packages/external/core b/packages/external/core index 7fe6e27891..ba96288b5c 160000 --- a/packages/external/core +++ b/packages/external/core @@ -1 +1 @@ -Subproject commit 7fe6e27891985130134fe9815fa651a02e42087f +Subproject commit ba96288b5c88096998591f94a64b0e3a00d4c92f From 02459045fa639a8ac15e75a1d13844fa9a24decf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Thu, 2 Nov 2023 16:03:22 +0100 Subject: [PATCH 07/23] Bump to latest BAAS --- dependencies.list | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dependencies.list b/dependencies.list index abe16e6f95..2016dbb168 100644 --- a/dependencies.list +++ b/dependencies.list @@ -1,6 +1,6 @@ # Version of MongoDB Realm used by integration tests # See https://github.com/realm/ci/packages/147854 for available versions -MONGODB_REALM_SERVER=2023-10-10 +MONGODB_REALM_SERVER=2023-11-02 # `BAAS` and `BAAS-UI` projects commit hashes matching MONGODB_REALM_SERVER image version # note that the MONGODB_REALM_SERVER image is a nightly build, find the matching commits From 1363ca7394f85f7a719a5cb7413b3bb1f3d3d8b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Fri, 3 Nov 2023 11:56:59 +0100 Subject: [PATCH 08/23] Proper locking around posting to freed scheduler --- .../realm/kotlin/internal/interop/RealmInterop.kt | 14 +++++++++----- .../realm/kotlin/internal/interop/RealmInterop.kt | 13 +++++++++---- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/packages/cinterop/src/jvm/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt b/packages/cinterop/src/jvm/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt index da5d7b6429..b1525e24c1 100644 --- a/packages/cinterop/src/jvm/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt +++ b/packages/cinterop/src/jvm/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt @@ -33,7 +33,6 @@ import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch import org.mongodb.kbson.ObjectId -import java.util.concurrent.atomic.AtomicBoolean // FIXME API-CLEANUP Rename io.realm.interop. to something with platform? // https://github.com/realm/realm-kotlin/issues/56 @@ -2096,17 +2095,22 @@ fun ObjectId.asRealmObjectIdT(): realm_object_id_t { private class JVMScheduler(dispatcher: CoroutineDispatcher) { val scope: CoroutineScope = CoroutineScope(dispatcher) - val cancelled = AtomicBoolean(false) + val lock = SynchronizableObject() + var cancelled = false fun notifyCore(schedulerPointer: Long) { scope.launch { - if (!cancelled.get()) { - realmc.invoke_core_notify_callback(schedulerPointer) + lock.withLock { + if (!cancelled) { + realmc.invoke_core_notify_callback(schedulerPointer) + } } } } fun cancel() { - cancelled.set(true) + lock.withLock { + cancelled = true + } } } diff --git a/packages/cinterop/src/nativeDarwin/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt b/packages/cinterop/src/nativeDarwin/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt index 99185a5280..46d3a0f74a 100644 --- a/packages/cinterop/src/nativeDarwin/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt +++ b/packages/cinterop/src/nativeDarwin/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt @@ -3395,7 +3395,8 @@ actual object RealmInterop { val scope: CoroutineScope = CoroutineScope(dispatcher) val ref: CPointer lateinit var scheduler: CPointer - private val cancelled: AtomicBoolean = atomic(false) + private val lock = SynchronizableObject() + private var cancelled = false init { ref = StableRef.create(this).asCPointer() @@ -3409,8 +3410,10 @@ actual object RealmInterop { scope.launch { try { printlntid("on dispatcher") - if (!cancelled.value) { - realm_wrapper.realm_scheduler_perform_work(work_queue) + lock.withLock { + if (!cancelled) { + realm_wrapper.realm_scheduler_perform_work(work_queue) + } } } catch (e: Exception) { // Should never happen, but is included for development to get some indicators @@ -3421,7 +3424,9 @@ actual object RealmInterop { } fun cancel() { - cancelled.value = true + lock.withLock { + cancelled = true + } } } } From 2f470cdb1b5931a33a45da0a1e2a2b1c1b7f9a96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Fri, 3 Nov 2023 11:58:18 +0100 Subject: [PATCH 09/23] Remove usage of runTest --- .../kotlin/io/realm/kotlin/test/darwin/CoroutineTests.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/test-base/src/nativeDarwinTest/kotlin/io/realm/kotlin/test/darwin/CoroutineTests.kt b/packages/test-base/src/nativeDarwinTest/kotlin/io/realm/kotlin/test/darwin/CoroutineTests.kt index 2b7dfd654b..0218a0689d 100644 --- a/packages/test-base/src/nativeDarwinTest/kotlin/io/realm/kotlin/test/darwin/CoroutineTests.kt +++ b/packages/test-base/src/nativeDarwinTest/kotlin/io/realm/kotlin/test/darwin/CoroutineTests.kt @@ -26,7 +26,6 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.async import kotlinx.coroutines.newSingleThreadContext import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.test.runTest import platform.CoreFoundation.CFRunLoopRun import platform.Foundation.NSNumber import platform.darwin.DISPATCH_QUEUE_PRIORITY_BACKGROUND @@ -43,7 +42,7 @@ import kotlin.test.assertEquals class CoroutineTests { @Test - fun dispatchBetweenThreads() = runTest { + fun dispatchBetweenThreads() = runBlocking { val dispatcher1 = newSingleThreadContext("test-dispatcher-1") val dispatcher2 = newSingleThreadContext("test-disptacher-2") val tid2 = runBlocking(dispatcher2) { From 92c25d0b074adeea0cdd5f3d85a9dd80b7f76000 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Fri, 3 Nov 2023 14:36:40 +0100 Subject: [PATCH 10/23] Fix macos tests --- .../kotlin/test/common/VersionTrackingTests.kt | 4 +++- .../notifications/SystemNotificationTests.kt | 1 - .../io/realm/kotlin/test/darwin/CoroutineTests.kt | 3 ++- .../io/realm/kotlin/test/darwin/MemoryTests.kt | 14 +++++++++----- 4 files changed, 14 insertions(+), 8 deletions(-) diff --git a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/VersionTrackingTests.kt b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/VersionTrackingTests.kt index 6a28982495..e51f286ce7 100644 --- a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/VersionTrackingTests.kt +++ b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/VersionTrackingTests.kt @@ -45,6 +45,7 @@ import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertNotNull import kotlin.test.assertNull +import kotlin.test.assertTrue class VersionTrackingTests { private lateinit var initialLogLevel: LogLevel @@ -150,7 +151,8 @@ class VersionTrackingTests { realm.write { copyToRealm(Sample()) } realm.write { copyToRealm(Sample()) } realm.activeVersions().run { - assertEquals(1, allTracked.size, toString()) + // Initially tracked version from user facing realm might have been released by now + assertTrue(allTracked.size <= 1, toString()) assertNotNull(notifier, toString()) assertEquals(0, notifier?.active?.size, toString()) assertNotNull(writer, toString()) diff --git a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/notifications/SystemNotificationTests.kt b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/notifications/SystemNotificationTests.kt index 97d265bc4d..b43ba6c9a6 100644 --- a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/notifications/SystemNotificationTests.kt +++ b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/notifications/SystemNotificationTests.kt @@ -76,6 +76,5 @@ class SystemNotificationTests { writer1.write { copyToRealm(Sample()) } writer2.write { copyToRealm(Sample()) } } - liveRealmContext.close() } } diff --git a/packages/test-base/src/nativeDarwinTest/kotlin/io/realm/kotlin/test/darwin/CoroutineTests.kt b/packages/test-base/src/nativeDarwinTest/kotlin/io/realm/kotlin/test/darwin/CoroutineTests.kt index 0218a0689d..2b7dfd654b 100644 --- a/packages/test-base/src/nativeDarwinTest/kotlin/io/realm/kotlin/test/darwin/CoroutineTests.kt +++ b/packages/test-base/src/nativeDarwinTest/kotlin/io/realm/kotlin/test/darwin/CoroutineTests.kt @@ -26,6 +26,7 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.async import kotlinx.coroutines.newSingleThreadContext import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.test.runTest import platform.CoreFoundation.CFRunLoopRun import platform.Foundation.NSNumber import platform.darwin.DISPATCH_QUEUE_PRIORITY_BACKGROUND @@ -42,7 +43,7 @@ import kotlin.test.assertEquals class CoroutineTests { @Test - fun dispatchBetweenThreads() = runBlocking { + fun dispatchBetweenThreads() = runTest { val dispatcher1 = newSingleThreadContext("test-dispatcher-1") val dispatcher2 = newSingleThreadContext("test-disptacher-2") val tid2 = runBlocking(dispatcher2) { diff --git a/packages/test-base/src/nativeDarwinTest/kotlin/io/realm/kotlin/test/darwin/MemoryTests.kt b/packages/test-base/src/nativeDarwinTest/kotlin/io/realm/kotlin/test/darwin/MemoryTests.kt index 3c111a3891..ff29ef5503 100644 --- a/packages/test-base/src/nativeDarwinTest/kotlin/io/realm/kotlin/test/darwin/MemoryTests.kt +++ b/packages/test-base/src/nativeDarwinTest/kotlin/io/realm/kotlin/test/darwin/MemoryTests.kt @@ -61,6 +61,15 @@ class MemoryTests { @OptIn(ExperimentalStdlibApi::class) println("NEW_MEMORY_MODEL: " + isExperimentalMM()) + // Referencing things like + // NSProcessInfo.Companion.processInfo().operatingSystemVersionString + // platform.Foundation.NSFileManager.defaultManager + // as done in Darwin SystemUtils.kt and initialized lazily, so do a full realm-lifecycle + // to only measure increases over the actual test + // - Ensure that we clean up any released memory to get a nice baseline + platform.posix.sleep(1 * 5) // give chance to the Collector Thread to process out of scope references + triggerGC() + // - Record the baseline val initialAllocation = parseSizeString(runSystemCommand(amountOfMemoryMappedInProcessCMD)) val referenceHolder = mutableListOf(); @@ -91,11 +100,6 @@ class MemoryTests { triggerGC() platform.posix.sleep(1 * 5) // give chance to the Collector Thread to process out of scope references - // Referencing things like - // NSProcessInfo.Companion.processInfo().operatingSystemVersionString - // platform.Foundation.NSFileManager.defaultManager - // as done in Darwin SystemUtils.kt cause allocations so we just assert the increase over - // the test val allocation = parseSizeString(runSystemCommand(amountOfMemoryMappedInProcessCMD)) assertEquals(initialAllocation, allocation, "mmap allocation exceeds expectations: initial=$initialAllocation current=$allocation") } From 9a4fc03ab038c731f2d0a7bbd47a52c20616ecfe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Fri, 3 Nov 2023 15:35:56 +0100 Subject: [PATCH 11/23] Add FIXME for bumping local BaaS SHA --- dependencies.list | 3 +++ 1 file changed, 3 insertions(+) diff --git a/dependencies.list b/dependencies.list index 2016dbb168..9a81d09be4 100644 --- a/dependencies.list +++ b/dependencies.list @@ -7,5 +7,8 @@ MONGODB_REALM_SERVER=2023-11-02 # for that date within the following repositories: # https://github.com/10gen/baas/ # https://github.com/10gen/baas-ui/ +# FIXME This is not updated according to MONGODB_REALM_SERVER=2023-11-02 as core's build +# script does not use Go 1.21.3 which is required for newer versions +# https://github.com/10gen/baas/blob/master/etc/docs/onboarding.md#go REALM_BAAS_GIT_HASH=8246fc548763eb908b8090df864e9924e3330a0d REALM_BAAS_UI_GIT_HASH=8a1843be2bf24f2faa705c5470a5bdd8d954f7ea From 8782f9e42647b1cdeec29600068e4f622e09046b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Tue, 7 Nov 2023 16:31:06 +0100 Subject: [PATCH 12/23] Bump core and SHA1 for local BaaS builds --- CHANGELOG.md | 2 +- dependencies.list | 9 +++------ packages/external/core | 2 +- 3 files changed, 5 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f2ccf8180d..f8a2bb01ab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,7 +24,7 @@ * Minimum Android SDK: 16. ### Internal -* Updated to Realm Core 13.23.2, commit ba96288b5c88096998591f94a64b0e3a00d4c92f. +* Updated to Realm Core 13.23.3, commit 7556b535aa7b27d49c13444894f7e9db778b3203. ## 1.12.0 (2023-11-02) diff --git a/dependencies.list b/dependencies.list index 9a81d09be4..15db1c3303 100644 --- a/dependencies.list +++ b/dependencies.list @@ -1,14 +1,11 @@ # Version of MongoDB Realm used by integration tests # See https://github.com/realm/ci/packages/147854 for available versions -MONGODB_REALM_SERVER=2023-11-02 +MONGODB_REALM_SERVER=2023-11-07 # `BAAS` and `BAAS-UI` projects commit hashes matching MONGODB_REALM_SERVER image version # note that the MONGODB_REALM_SERVER image is a nightly build, find the matching commits # for that date within the following repositories: # https://github.com/10gen/baas/ # https://github.com/10gen/baas-ui/ -# FIXME This is not updated according to MONGODB_REALM_SERVER=2023-11-02 as core's build -# script does not use Go 1.21.3 which is required for newer versions -# https://github.com/10gen/baas/blob/master/etc/docs/onboarding.md#go -REALM_BAAS_GIT_HASH=8246fc548763eb908b8090df864e9924e3330a0d -REALM_BAAS_UI_GIT_HASH=8a1843be2bf24f2faa705c5470a5bdd8d954f7ea +REALM_BAAS_GIT_HASH=41fa6cdbca47826c20a64f756e21b2c184393e90 +REALM_BAAS_UI_GIT_HASH=b97a27ac858e0e8126aeb63f6ff9734d11029a91 diff --git a/packages/external/core b/packages/external/core index ba96288b5c..7556b535aa 160000 --- a/packages/external/core +++ b/packages/external/core @@ -1 +1 @@ -Subproject commit ba96288b5c88096998591f94a64b0e3a00d4c92f +Subproject commit 7556b535aa7b27d49c13444894f7e9db778b3203 From 8dbcf5d26ed903c6fb601258b1d20ac4c51f8e55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Wed, 8 Nov 2023 13:36:12 +0100 Subject: [PATCH 13/23] Change http timeout for debugging --- .../kotlin/io/realm/kotlin/test/mongodb/util/HttpClient.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/util/HttpClient.kt b/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/util/HttpClient.kt index da9e02c6e7..8010f92ce5 100644 --- a/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/util/HttpClient.kt +++ b/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/util/HttpClient.kt @@ -32,7 +32,7 @@ import kotlin.time.Duration.Companion.seconds // TODO Consider moving it to util package? fun defaultClient(name: String, debug: Boolean, block: HttpClientConfig<*>.() -> Unit = {}): HttpClient { - val timeout = 60.seconds.inWholeMilliseconds + val timeout = 55.seconds.inWholeMilliseconds return createPlatformClient { // Charset defaults to UTF-8 (https://ktor.io/docs/http-plain-text.html#configuration) install(HttpTimeout) { From 867db652a05c6bf27339ab2cae133cd96d437c02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Wed, 8 Nov 2023 13:38:32 +0100 Subject: [PATCH 14/23] Enable debugging info --- .../test/mongodb/common/SyncedRealmTests.kt | 144 +++++++++--------- 1 file changed, 75 insertions(+), 69 deletions(-) diff --git a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/SyncedRealmTests.kt b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/SyncedRealmTests.kt index fe2851414b..428bf643e5 100644 --- a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/SyncedRealmTests.kt +++ b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/SyncedRealmTests.kt @@ -32,6 +32,7 @@ import io.realm.kotlin.internal.platform.fileExists import io.realm.kotlin.internal.platform.pathOf import io.realm.kotlin.internal.platform.runBlocking import io.realm.kotlin.log.LogLevel +import io.realm.kotlin.log.RealmLog import io.realm.kotlin.mongodb.App import io.realm.kotlin.mongodb.User import io.realm.kotlin.mongodb.exceptions.DownloadingRealmTimeOutException @@ -695,87 +696,92 @@ class SyncedRealmTests { @Suppress("LongMethod") @Test fun mutableRealmInt_convergesAcrossClients() = runBlocking { - // Updates and initial data upload are carried out using this config - val config0 = createPartitionSyncConfig( - user = app.createUserAndLogIn(randomEmail(), "password1234"), - partitionValue = partitionValue, - name = "db1", - ) - - // Config for update 1 - val config1 = createPartitionSyncConfig( - user = app.createUserAndLogIn(randomEmail(), "password1234"), - partitionValue = partitionValue, - name = "db2", - ) - - // Config for update 2 - val config2 = createPartitionSyncConfig( - user = app.createUserAndLogIn(randomEmail(), "password1234"), - partitionValue = partitionValue, - name = "db3", - ) + val oldLevel = RealmLog.level + RealmLog.level = LogLevel.ALL + try {// Updates and initial data upload are carried out using this config + val config0 = createPartitionSyncConfig( + user = app.createUserAndLogIn(randomEmail(), "password1234"), + partitionValue = partitionValue, + name = "db1", + ) - val counterValue = Channel(1) + // Config for update 1 + val config1 = createPartitionSyncConfig( + user = app.createUserAndLogIn(randomEmail(), "password1234"), + partitionValue = partitionValue, + name = "db2", + ) - // Asynchronously receive updates - val updates = async { - Realm.open(config0).use { realm -> - realm.query() - .first() - .asFlow() - .collect { - if (it.obj != null) { - val counter = it.obj!!.mutableRealmIntField - counterValue.trySend(counter.get()) - } - } - } - } + // Config for update 2 + val config2 = createPartitionSyncConfig( + user = app.createUserAndLogIn(randomEmail(), "password1234"), + partitionValue = partitionValue, + name = "db3", + ) - // Upload initial data - blocking to ensure the two clients find an object to update - val masterObject = SyncObjectWithAllTypes().apply { _id = "id-${Random.nextLong()}" } - Realm.open(config0).use { realm -> - realm.writeBlocking { copyToRealm(masterObject) } - realm.syncSession.uploadAllLocalChanges() - } - assertEquals(42, counterValue.receiveOrFail(), "Failed to receive 42") + val counterValue = Channel(1) - // Increment counter asynchronously after download initial data (1) - val increment1 = async { - Realm.open(config1).use { realm -> - realm.syncSession.downloadAllServerChanges(30.seconds) - realm.write { + // Asynchronously receive updates + val updates = async { + Realm.open(config0).use { realm -> realm.query() .first() - .find() - .let { assertNotNull(findLatest(assertNotNull(it))) } - .mutableRealmIntField - .increment(1) + .asFlow() + .collect { + if (it.obj != null) { + val counter = it.obj!!.mutableRealmIntField + counterValue.trySend(counter.get()) + } + } } } - } - assertEquals(43, counterValue.receiveOrFail(), "Failed to receive 43") - // Increment counter asynchronously after download initial data (2) - val increment2 = async { - Realm.open(config2).use { realm -> - realm.syncSession.downloadAllServerChanges(30.seconds) - realm.write { - realm.query() - .first() - .find() - .let { assertNotNull(findLatest(assertNotNull(it))) } - .mutableRealmIntField - .increment(1) + // Upload initial data - blocking to ensure the two clients find an object to update + val masterObject = SyncObjectWithAllTypes().apply { _id = "id-${Random.nextLong()}" } + Realm.open(config0).use { realm -> + realm.writeBlocking { copyToRealm(masterObject) } + realm.syncSession.uploadAllLocalChanges() + } + assertEquals(42, counterValue.receiveOrFail(), "Failed to receive 42") + + // Increment counter asynchronously after download initial data (1) + val increment1 = async { + Realm.open(config1).use { realm -> + realm.syncSession.downloadAllServerChanges(30.seconds) + realm.write { + realm.query() + .first() + .find() + .let { assertNotNull(findLatest(assertNotNull(it))) } + .mutableRealmIntField + .increment(1) + } } } - } - assertEquals(44, counterValue.receiveOrFail(), "Failed to receive 44") + assertEquals(43, counterValue.receiveOrFail(), "Failed to receive 43") + + // Increment counter asynchronously after download initial data (2) + val increment2 = async { + Realm.open(config2).use { realm -> + realm.syncSession.downloadAllServerChanges(30.seconds) + realm.write { + realm.query() + .first() + .find() + .let { assertNotNull(findLatest(assertNotNull(it))) } + .mutableRealmIntField + .increment(1) + } + } + } + assertEquals(44, counterValue.receiveOrFail(), "Failed to receive 44") - increment1.cancel() - increment2.cancel() - updates.cancel() + increment1.cancel() + increment2.cancel() + updates.cancel() + } finally { + RealmLog.level = oldLevel + } } private fun createWriteCopyLocalConfig( From e243e36be21609b7868ff3f0f9f86f652a12c9f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Wed, 8 Nov 2023 13:43:49 +0100 Subject: [PATCH 15/23] Another round for linting --- .../io/realm/kotlin/test/mongodb/common/SyncedRealmTests.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/SyncedRealmTests.kt b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/SyncedRealmTests.kt index 428bf643e5..9e8e853831 100644 --- a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/SyncedRealmTests.kt +++ b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/SyncedRealmTests.kt @@ -698,7 +698,8 @@ class SyncedRealmTests { fun mutableRealmInt_convergesAcrossClients() = runBlocking { val oldLevel = RealmLog.level RealmLog.level = LogLevel.ALL - try {// Updates and initial data upload are carried out using this config + try { + // Updates and initial data upload are carried out using this config val config0 = createPartitionSyncConfig( user = app.createUserAndLogIn(randomEmail(), "password1234"), partitionValue = partitionValue, From bc2df5da326709b0b8a04e4981edbd8ab80bd0aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Wed, 8 Nov 2023 14:54:07 +0100 Subject: [PATCH 16/23] Additional debug statements --- .../kotlin/io/realm/kotlin/test/util/Utils.kt | 14 ++++++++++---- .../kotlin/test/mongodb/common/SyncedRealmTests.kt | 6 +++--- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/packages/test-base/src/commonMain/kotlin/io/realm/kotlin/test/util/Utils.kt b/packages/test-base/src/commonMain/kotlin/io/realm/kotlin/test/util/Utils.kt index 5e72a273dd..0380d7dfc7 100644 --- a/packages/test-base/src/commonMain/kotlin/io/realm/kotlin/test/util/Utils.kt +++ b/packages/test-base/src/commonMain/kotlin/io/realm/kotlin/test/util/Utils.kt @@ -21,11 +21,12 @@ import io.realm.kotlin.Realm import io.realm.kotlin.test.platform.PlatformUtils import io.realm.kotlin.types.RealmInstant import io.realm.kotlin.types.RealmObject +import kotlinx.coroutines.CancellationException import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.withTimeout import kotlinx.datetime.Instant import kotlin.time.Duration -import kotlin.time.Duration.Companion.minutes +import kotlin.time.Duration.Companion.seconds // Platform independent helper methods object Utils { @@ -92,8 +93,13 @@ fun Instant.toRealmInstant(): RealmInstant { } // Variant of `Channel.receiveOrFail()` that will will throw if a timeout is hit. -suspend fun Channel.receiveOrFail(timeout: Duration = 1.minutes): T { - return withTimeout(timeout) { - receive() +suspend fun Channel.receiveOrFail(timeout: Duration = 1.seconds, message: String? = null): T { + return try { + withTimeout(timeout) { + receive() + } + } catch (e: CancellationException) { + @Suppress("TooGenericExceptionThrown") + throw RuntimeException("$message", e) } } diff --git a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/SyncedRealmTests.kt b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/SyncedRealmTests.kt index 9e8e853831..f6c2e380f2 100644 --- a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/SyncedRealmTests.kt +++ b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/SyncedRealmTests.kt @@ -743,7 +743,7 @@ class SyncedRealmTests { realm.writeBlocking { copyToRealm(masterObject) } realm.syncSession.uploadAllLocalChanges() } - assertEquals(42, counterValue.receiveOrFail(), "Failed to receive 42") + assertEquals(42, counterValue.receiveOrFail(message = "Failed to receive 42")) // Increment counter asynchronously after download initial data (1) val increment1 = async { @@ -759,7 +759,7 @@ class SyncedRealmTests { } } } - assertEquals(43, counterValue.receiveOrFail(), "Failed to receive 43") + assertEquals(43, counterValue.receiveOrFail(message = "Failed to receive 43")) // Increment counter asynchronously after download initial data (2) val increment2 = async { @@ -775,7 +775,7 @@ class SyncedRealmTests { } } } - assertEquals(44, counterValue.receiveOrFail(), "Failed to receive 44") + assertEquals(44, counterValue.receiveOrFail(message = "Failed to receive 44")) increment1.cancel() increment2.cancel() From 43fae5df9efa646484d90a0518728bf638b0aee8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Wed, 8 Nov 2023 15:27:26 +0100 Subject: [PATCH 17/23] Changed timeout. --- .../src/commonMain/kotlin/io/realm/kotlin/test/util/Utils.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/test-base/src/commonMain/kotlin/io/realm/kotlin/test/util/Utils.kt b/packages/test-base/src/commonMain/kotlin/io/realm/kotlin/test/util/Utils.kt index 0380d7dfc7..113c37c5f0 100644 --- a/packages/test-base/src/commonMain/kotlin/io/realm/kotlin/test/util/Utils.kt +++ b/packages/test-base/src/commonMain/kotlin/io/realm/kotlin/test/util/Utils.kt @@ -26,6 +26,7 @@ import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.withTimeout import kotlinx.datetime.Instant import kotlin.time.Duration +import kotlin.time.Duration.Companion.minutes import kotlin.time.Duration.Companion.seconds // Platform independent helper methods @@ -93,7 +94,7 @@ fun Instant.toRealmInstant(): RealmInstant { } // Variant of `Channel.receiveOrFail()` that will will throw if a timeout is hit. -suspend fun Channel.receiveOrFail(timeout: Duration = 1.seconds, message: String? = null): T { +suspend fun Channel.receiveOrFail(timeout: Duration = 1.minutes, message: String? = null): T { return try { withTimeout(timeout) { receive() From be6842c79cc7404f6869ab79ec7eab815f8a45bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Wed, 8 Nov 2023 15:47:14 +0100 Subject: [PATCH 18/23] Another round for linting --- .../src/commonMain/kotlin/io/realm/kotlin/test/util/Utils.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/test-base/src/commonMain/kotlin/io/realm/kotlin/test/util/Utils.kt b/packages/test-base/src/commonMain/kotlin/io/realm/kotlin/test/util/Utils.kt index 113c37c5f0..9c27c6832b 100644 --- a/packages/test-base/src/commonMain/kotlin/io/realm/kotlin/test/util/Utils.kt +++ b/packages/test-base/src/commonMain/kotlin/io/realm/kotlin/test/util/Utils.kt @@ -27,7 +27,6 @@ import kotlinx.coroutines.withTimeout import kotlinx.datetime.Instant import kotlin.time.Duration import kotlin.time.Duration.Companion.minutes -import kotlin.time.Duration.Companion.seconds // Platform independent helper methods object Utils { From 367bbea79e4555dbd8216a0a4a10e2e9cc70fb81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Wed, 8 Nov 2023 16:28:33 +0100 Subject: [PATCH 19/23] More debug output --- .../io/realm/kotlin/test/mongodb/common/SyncedRealmTests.kt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/SyncedRealmTests.kt b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/SyncedRealmTests.kt index f6c2e380f2..12a863c3be 100644 --- a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/SyncedRealmTests.kt +++ b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/SyncedRealmTests.kt @@ -764,7 +764,10 @@ class SyncedRealmTests { // Increment counter asynchronously after download initial data (2) val increment2 = async { Realm.open(config2).use { realm -> + println("realm2 open") realm.syncSession.downloadAllServerChanges(30.seconds) + + println("realm2 remote data downloaded") realm.write { realm.query() .first() @@ -772,7 +775,10 @@ class SyncedRealmTests { .let { assertNotNull(findLatest(assertNotNull(it))) } .mutableRealmIntField .increment(1) + println("realm2 incremented") } + realm.syncSession.uploadAllLocalChanges(10.seconds) + println("realm2 increment uploaded") } } assertEquals(44, counterValue.receiveOrFail(message = "Failed to receive 44")) From 5623989633e55f0ecd866a399d56ead4eba8a7ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Wed, 8 Nov 2023 20:30:23 +0100 Subject: [PATCH 20/23] Fix timeout exceptions for debugging --- .../src/commonMain/kotlin/io/realm/kotlin/test/util/Utils.kt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/test-base/src/commonMain/kotlin/io/realm/kotlin/test/util/Utils.kt b/packages/test-base/src/commonMain/kotlin/io/realm/kotlin/test/util/Utils.kt index 9c27c6832b..29a14cafe9 100644 --- a/packages/test-base/src/commonMain/kotlin/io/realm/kotlin/test/util/Utils.kt +++ b/packages/test-base/src/commonMain/kotlin/io/realm/kotlin/test/util/Utils.kt @@ -22,6 +22,7 @@ import io.realm.kotlin.test.platform.PlatformUtils import io.realm.kotlin.types.RealmInstant import io.realm.kotlin.types.RealmObject import kotlinx.coroutines.CancellationException +import kotlinx.coroutines.TimeoutCancellationException import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.withTimeout import kotlinx.datetime.Instant @@ -99,7 +100,7 @@ suspend fun Channel.receiveOrFail(timeout: Duration = 1.minutes, m receive() } } catch (e: CancellationException) { - @Suppress("TooGenericExceptionThrown") - throw RuntimeException("$message", e) + @Suppress("TooGenericExceptionThrown", "invisible_member") + throw TimeoutCancellationException("$message") } } From 6b6c8a86a84d5cf4f76d9fc9c78b7c9e277ad194 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Wed, 8 Nov 2023 21:00:52 +0100 Subject: [PATCH 21/23] Clean up --- .../kotlin/io/realm/kotlin/test/util/Utils.kt | 15 +- .../test/mongodb/common/SyncedRealmTests.kt | 151 ++++++++---------- 2 files changed, 76 insertions(+), 90 deletions(-) diff --git a/packages/test-base/src/commonMain/kotlin/io/realm/kotlin/test/util/Utils.kt b/packages/test-base/src/commonMain/kotlin/io/realm/kotlin/test/util/Utils.kt index 29a14cafe9..f07e4676bf 100644 --- a/packages/test-base/src/commonMain/kotlin/io/realm/kotlin/test/util/Utils.kt +++ b/packages/test-base/src/commonMain/kotlin/io/realm/kotlin/test/util/Utils.kt @@ -21,10 +21,10 @@ import io.realm.kotlin.Realm import io.realm.kotlin.test.platform.PlatformUtils import io.realm.kotlin.types.RealmInstant import io.realm.kotlin.types.RealmObject -import kotlinx.coroutines.CancellationException import kotlinx.coroutines.TimeoutCancellationException import kotlinx.coroutines.channels.Channel -import kotlinx.coroutines.withTimeout +import kotlinx.coroutines.selects.onTimeout +import kotlinx.coroutines.selects.select import kotlinx.datetime.Instant import kotlin.time.Duration import kotlin.time.Duration.Companion.minutes @@ -95,12 +95,11 @@ fun Instant.toRealmInstant(): RealmInstant { // Variant of `Channel.receiveOrFail()` that will will throw if a timeout is hit. suspend fun Channel.receiveOrFail(timeout: Duration = 1.minutes, message: String? = null): T { - return try { - withTimeout(timeout) { - receive() + return select { + this@receiveOrFail.onReceive { it } + onTimeout(timeout) { + @Suppress("invisible_member") + throw TimeoutCancellationException("Timeout: $message") } - } catch (e: CancellationException) { - @Suppress("TooGenericExceptionThrown", "invisible_member") - throw TimeoutCancellationException("$message") } } diff --git a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/SyncedRealmTests.kt b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/SyncedRealmTests.kt index 12a863c3be..7a1b7b6072 100644 --- a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/SyncedRealmTests.kt +++ b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/SyncedRealmTests.kt @@ -32,7 +32,6 @@ import io.realm.kotlin.internal.platform.fileExists import io.realm.kotlin.internal.platform.pathOf import io.realm.kotlin.internal.platform.runBlocking import io.realm.kotlin.log.LogLevel -import io.realm.kotlin.log.RealmLog import io.realm.kotlin.mongodb.App import io.realm.kotlin.mongodb.User import io.realm.kotlin.mongodb.exceptions.DownloadingRealmTimeOutException @@ -696,99 +695,87 @@ class SyncedRealmTests { @Suppress("LongMethod") @Test fun mutableRealmInt_convergesAcrossClients() = runBlocking { - val oldLevel = RealmLog.level - RealmLog.level = LogLevel.ALL - try { - // Updates and initial data upload are carried out using this config - val config0 = createPartitionSyncConfig( - user = app.createUserAndLogIn(randomEmail(), "password1234"), - partitionValue = partitionValue, - name = "db1", - ) + // Updates and initial data upload are carried out using this config + val config0 = createPartitionSyncConfig( + user = app.createUserAndLogIn(randomEmail(), "password1234"), + partitionValue = partitionValue, + name = "db1", + ) - // Config for update 1 - val config1 = createPartitionSyncConfig( - user = app.createUserAndLogIn(randomEmail(), "password1234"), - partitionValue = partitionValue, - name = "db2", - ) + // Config for update 1 + val config1 = createPartitionSyncConfig( + user = app.createUserAndLogIn(randomEmail(), "password1234"), + partitionValue = partitionValue, + name = "db2", + ) - // Config for update 2 - val config2 = createPartitionSyncConfig( - user = app.createUserAndLogIn(randomEmail(), "password1234"), - partitionValue = partitionValue, - name = "db3", - ) + // Config for update 2 + val config2 = createPartitionSyncConfig( + user = app.createUserAndLogIn(randomEmail(), "password1234"), + partitionValue = partitionValue, + name = "db3", + ) - val counterValue = Channel(1) + val counterValue = Channel(1) - // Asynchronously receive updates - val updates = async { - Realm.open(config0).use { realm -> - realm.query() - .first() - .asFlow() - .collect { - if (it.obj != null) { - val counter = it.obj!!.mutableRealmIntField - counterValue.trySend(counter.get()) - } + // Asynchronously receive updates + val updates = async { + Realm.open(config0).use { realm -> + realm.query() + .first() + .asFlow() + .collect { + if (it.obj != null) { + val counter = it.obj!!.mutableRealmIntField + counterValue.trySend(counter.get()) } - } + } } + } - // Upload initial data - blocking to ensure the two clients find an object to update - val masterObject = SyncObjectWithAllTypes().apply { _id = "id-${Random.nextLong()}" } - Realm.open(config0).use { realm -> - realm.writeBlocking { copyToRealm(masterObject) } - realm.syncSession.uploadAllLocalChanges() - } - assertEquals(42, counterValue.receiveOrFail(message = "Failed to receive 42")) - - // Increment counter asynchronously after download initial data (1) - val increment1 = async { - Realm.open(config1).use { realm -> - realm.syncSession.downloadAllServerChanges(30.seconds) - realm.write { - realm.query() - .first() - .find() - .let { assertNotNull(findLatest(assertNotNull(it))) } - .mutableRealmIntField - .increment(1) - } + // Upload initial data - blocking to ensure the two clients find an object to update + val masterObject = SyncObjectWithAllTypes().apply { _id = "id-${Random.nextLong()}" } + Realm.open(config0).use { realm -> + realm.writeBlocking { copyToRealm(masterObject) } + realm.syncSession.uploadAllLocalChanges() + } + assertEquals(42, counterValue.receiveOrFail(message = "Failed to receive 42")) + + // Increment counter asynchronously after download initial data (1) + val increment1 = async { + Realm.open(config1).use { realm -> + realm.write { + realm.query() + .first() + .find() + .let { assertNotNull(findLatest(assertNotNull(it))) } + .mutableRealmIntField + .increment(1) } + realm.syncSession.uploadAllLocalChanges(10.seconds) } - assertEquals(43, counterValue.receiveOrFail(message = "Failed to receive 43")) - - // Increment counter asynchronously after download initial data (2) - val increment2 = async { - Realm.open(config2).use { realm -> - println("realm2 open") - realm.syncSession.downloadAllServerChanges(30.seconds) - - println("realm2 remote data downloaded") - realm.write { - realm.query() - .first() - .find() - .let { assertNotNull(findLatest(assertNotNull(it))) } - .mutableRealmIntField - .increment(1) - println("realm2 incremented") - } - realm.syncSession.uploadAllLocalChanges(10.seconds) - println("realm2 increment uploaded") + } + assertEquals(43, counterValue.receiveOrFail(message = "Failed to receive 43")) + + // Increment counter asynchronously after download initial data (2) + val increment2 = async { + Realm.open(config2).use { realm -> + realm.write { + realm.query() + .first() + .find() + .let { assertNotNull(findLatest(assertNotNull(it))) } + .mutableRealmIntField + .increment(1) } + realm.syncSession.uploadAllLocalChanges(10.seconds) } - assertEquals(44, counterValue.receiveOrFail(message = "Failed to receive 44")) - - increment1.cancel() - increment2.cancel() - updates.cancel() - } finally { - RealmLog.level = oldLevel } + assertEquals(44, counterValue.receiveOrFail(message = "Failed to receive 44")) + + increment1.cancel() + increment2.cancel() + updates.cancel() } private fun createWriteCopyLocalConfig( From cff6470b9a180ee1d8434532edf903aa94e71be9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Wed, 8 Nov 2023 21:02:02 +0100 Subject: [PATCH 22/23] Revert http timeout --- .../kotlin/io/realm/kotlin/test/mongodb/util/HttpClient.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/util/HttpClient.kt b/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/util/HttpClient.kt index 8010f92ce5..da9e02c6e7 100644 --- a/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/util/HttpClient.kt +++ b/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/util/HttpClient.kt @@ -32,7 +32,7 @@ import kotlin.time.Duration.Companion.seconds // TODO Consider moving it to util package? fun defaultClient(name: String, debug: Boolean, block: HttpClientConfig<*>.() -> Unit = {}): HttpClient { - val timeout = 55.seconds.inWholeMilliseconds + val timeout = 60.seconds.inWholeMilliseconds return createPlatformClient { // Charset defaults to UTF-8 (https://ktor.io/docs/http-plain-text.html#configuration) install(HttpTimeout) { From 7746087e40732228cb6b843c6548e3bedd3d8b66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Wed, 8 Nov 2023 21:27:18 +0100 Subject: [PATCH 23/23] Reinsert download guard --- .../io/realm/kotlin/test/mongodb/common/SyncedRealmTests.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/SyncedRealmTests.kt b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/SyncedRealmTests.kt index 7a1b7b6072..d79f7f157a 100644 --- a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/SyncedRealmTests.kt +++ b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/SyncedRealmTests.kt @@ -744,6 +744,7 @@ class SyncedRealmTests { // Increment counter asynchronously after download initial data (1) val increment1 = async { Realm.open(config1).use { realm -> + realm.syncSession.downloadAllServerChanges(30.seconds) realm.write { realm.query() .first() @@ -760,6 +761,7 @@ class SyncedRealmTests { // Increment counter asynchronously after download initial data (2) val increment2 = async { Realm.open(config2).use { realm -> + realm.syncSession.downloadAllServerChanges(30.seconds) realm.write { realm.query() .first()