From 15a01b97f6ccbc299b7e4c3d41585518466529a1 Mon Sep 17 00:00:00 2001 From: Clemente Date: Wed, 22 Nov 2023 13:08:11 +0100 Subject: [PATCH] Add error propagation on JVM --- .../kotlin/internal/interop/sync/SyncError.kt | 14 ++++--- packages/external/core | 2 +- .../src/main/jni/realm_api_helpers.cpp | 38 +++++-------------- .../ClientResetRequiredException.kt | 7 +++- .../common/SyncClientResetIntegrationTests.kt | 11 ++++++ 5 files changed, 36 insertions(+), 36 deletions(-) diff --git a/packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/sync/SyncError.kt b/packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/sync/SyncError.kt index dfe69d2ea3..f1d27b8b48 100644 --- a/packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/sync/SyncError.kt +++ b/packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/sync/SyncError.kt @@ -29,13 +29,14 @@ data class SyncError constructor( val isFatal: Boolean, val isUnrecognizedByClient: Boolean, val isClientResetRequested: Boolean, - val compensatingWrites: Array + val compensatingWrites: Array, + val userError: Throwable?, ) { // Constructs an SyncError out from a simple code. There are some situations (SyncSessionTransferCompletionCallback) // where we receive an error code rather than a full SyncErrorCode, wrapping the code // simplifies the error handling logic. constructor( - error: CoreError + error: CoreError, ) : this( errorCode = error, originalFilePath = null, @@ -43,7 +44,8 @@ data class SyncError constructor( isFatal = false, isUnrecognizedByClient = false, isClientResetRequested = false, - compensatingWrites = emptyArray() + compensatingWrites = emptyArray(), + userError = null, ) // Constructor used by JNI so we avoid creating too many objects on the JNI side. @@ -56,7 +58,8 @@ data class SyncError constructor( isFatal: Boolean, isUnrecognizedByClient: Boolean, isClientResetRequested: Boolean, - compensatingWrites: Array + compensatingWrites: Array, + userError: Throwable?, ) : this( CoreError(categoryFlags, value, message), originalFilePath, @@ -64,6 +67,7 @@ data class SyncError constructor( isFatal, isUnrecognizedByClient, isClientResetRequested, - compensatingWrites + compensatingWrites, + userError, ) } diff --git a/packages/external/core b/packages/external/core index 7556b535aa..424a49e25a 160000 --- a/packages/external/core +++ b/packages/external/core @@ -1 +1 @@ -Subproject commit 7556b535aa7b27d49c13444894f7e9db778b3203 +Subproject commit 424a49e25aaa2aa975e9076620a0504c914143b8 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 03783aaf80..ec6fdcd2ab 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 @@ -90,15 +90,6 @@ inline std::string get_exception_message(JNIEnv *env) { return env->GetStringUTFChars(message, NULL); } -inline void system_out_println(JNIEnv *env, std::string message) { - jclass system_class = env->FindClass("java/lang/System"); - jfieldID field_id = env->GetStaticFieldID(system_class, "out", "Ljava/io/PrintStream;"); - jobject system_out = env->GetStaticObjectField(system_class, field_id); - jclass print_stream_class = env->FindClass("java/io/PrintStream"); - jmethodID method_id = env->GetMethodID(print_stream_class, "println", "(Ljava/lang/String;)V"); - env->CallVoidMethod(system_out, method_id, to_jstring(env, message)); -} - void realm_changed_callback(void* userdata) { auto env = get_env(true); @@ -716,7 +707,7 @@ jobject convert_to_jvm_sync_error(JNIEnv* jenv, const realm_sync_error_t& error) static JavaMethod sync_error_constructor(jenv, JavaClassGlobalDef::sync_error(), "", - "(IILjava/lang/String;Ljava/lang/String;Ljava/lang/String;ZZZ[Lio/realm/kotlin/internal/interop/sync/CoreCompensatingWriteInfo;)V"); + "(IILjava/lang/String;Ljava/lang/String;Ljava/lang/String;ZZZ[Lio/realm/kotlin/internal/interop/sync/CoreCompensatingWriteInfo;Ljava/lang/Throwable;)V"); jint category = static_cast(error.status.categories); jint value = static_cast(error.status.error); @@ -788,7 +779,7 @@ jobject convert_to_jvm_sync_error(JNIEnv* jenv, const realm_sync_error_t& error) } } - return jenv->NewObject( + jobject result = jenv->NewObject( JavaClassGlobalDef::sync_error(), sync_error_constructor, category, @@ -799,8 +790,13 @@ jobject convert_to_jvm_sync_error(JNIEnv* jenv, const realm_sync_error_t& error) is_fatal, is_unrecognized_by_client, is_client_reset_requested, - j_compensating_write_info_array + j_compensating_write_info_array, + static_cast(error.usercode_error) ); + + jni_check_exception(jenv); + jenv->DeleteGlobalRef(static_cast(error.usercode_error)); + return result; } void sync_set_error_handler(realm_sync_config_t* sync_config, jobject error_handler) { @@ -887,14 +883,7 @@ before_client_reset(void* userdata, realm_t* before_realm) { "(Lio/realm/kotlin/internal/interop/NativePointer;)V"); auto before_pointer = wrap_pointer(env, reinterpret_cast(before_realm), false); env->CallVoidMethod(static_cast(userdata), java_before_callback_function, before_pointer); - if (env->ExceptionCheck()) { - std::string exception_message = get_exception_message(env); - std::string message_template = "An error has occurred in the 'onBefore' callback: "; - system_out_println(env, message_template.append(exception_message)); - return false; - } - - return true; + return jni_check_exception(env); } bool @@ -913,14 +902,7 @@ after_client_reset(void* userdata, realm_t* before_realm, auto after_pointer = wrap_pointer(env, reinterpret_cast(after_realm_ptr), false); env->CallVoidMethod(static_cast(userdata), java_after_callback_function, before_pointer, after_pointer, did_recover); realm_close(after_realm_ptr); - if (env->ExceptionCheck()) { - std::string exception_message = get_exception_message(env); - std::string message_template = "An error has occurred in the 'onAfter' callback: "; - system_out_println(env, message_template.append(exception_message)); - return false; - } - - return true; + return jni_check_exception(env); } void diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/exceptions/ClientResetRequiredException.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/exceptions/ClientResetRequiredException.kt index 26b2daa2e4..48a7fcfb72 100644 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/exceptions/ClientResetRequiredException.kt +++ b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/exceptions/ClientResetRequiredException.kt @@ -30,8 +30,11 @@ import io.realm.kotlin.mongodb.internal.createMessageFromSyncError */ public class ClientResetRequiredException constructor( private val appPointer: RealmAppPointer, - error: SyncError -) : Throwable(message = createMessageFromSyncError(error.errorCode)) { + error: SyncError, +) : Throwable( + message = createMessageFromSyncError(error.errorCode), + cause = error.userError, +) { /** * Path to the original (local) copy of the realm when the Client Reset event was triggered. diff --git a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/SyncClientResetIntegrationTests.kt b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/SyncClientResetIntegrationTests.kt index 01fd16cb75..62304d3d3e 100644 --- a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/SyncClientResetIntegrationTests.kt +++ b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/SyncClientResetIntegrationTests.kt @@ -62,6 +62,7 @@ import kotlin.test.BeforeTest import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFalse +import kotlin.test.assertIs import kotlin.test.assertNotNull import kotlin.test.assertTrue import kotlin.test.fail @@ -740,6 +741,11 @@ class SyncClientResetIntegrationTests { "[Sync][AutoClientResetFailed(1028)] A fatal error occurred during client reset: 'User-provided callback failed'.", exception.message ) + assertIs(exception.cause) + assertEquals( + "User exception", + exception.cause?.message + ) channel.trySend(ClientResetEvents.ON_MANUAL_RESET_FALLBACK) } @@ -752,6 +758,11 @@ class SyncClientResetIntegrationTests { "[Sync][AutoClientResetFailed(1028)] A fatal error occurred during client reset: 'User-provided callback failed'.", exception.message ) + assertIs(exception.cause) + assertEquals( + "User exception", + exception.cause?.message + ) channel.trySend(ClientResetEvents.ON_MANUAL_RESET_FALLBACK) } }).build()