Skip to content

Commit 422d9cb

Browse files
authored
[RKOTLIN-1096] Map is_fatal to UnrecoverableSyncException (#1768)
1 parent 651991a commit 422d9cb

File tree

3 files changed

+110
-21
lines changed

3 files changed

+110
-21
lines changed

CHANGELOG.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,30 @@
1+
## 2.0.1 (YYYY-MM-DD)
2+
3+
### Breaking changes
4+
* None.
5+
6+
### Enhancements
7+
* None.
8+
9+
### Fixed
10+
* [Sync] Fatal sync exceptions are now thrown as `UnrecoverableSyncException`. (Issue [#1767](https://github.com/realm/realm-kotlin/issues/1767) [RKOTLIN-1096](https://jira.mongodb.org/browse/RKOTLIN-1096)).
11+
12+
### Compatibility
13+
* File format: Generates Realms with file format v24 (reads and upgrades file format v10 or later).
14+
* Realm Studio 15.0.0 or above is required to open Realms created by this version.
15+
* This release is compatible with the following Kotlin releases:
16+
* Kotlin 2.0.0 and above. Support for experimental K2-compilation with `kotlin.experimental.tryK2=true`.
17+
* Ktor 2.1.2 and above.
18+
* Coroutines 1.7.0 and above.
19+
* AtomicFu 0.18.3 and above.
20+
* The new memory model only. See https://github.com/realm/realm-kotlin#kotlin-memory-model-and-coroutine-compatibility
21+
* Minimum Kbson 0.4.0.
22+
* Minimum Gradle version: 7.2.
23+
* Minimum Android Gradle Plugin version: 7.1.3.
24+
* Minimum Android SDK: 16.
25+
* Minimum R8: 8.0.34.
26+
27+
128
## 2.0.0 (2024-06-03)
229

330
> [!NOTE]

packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/RealmSyncUtils.kt

Lines changed: 26 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -78,26 +78,34 @@ internal fun <T, R> channelResultCallback(
7878
internal fun convertSyncError(syncError: SyncError): SyncException {
7979
val errorCode = syncError.errorCode
8080
val message = createMessageFromSyncError(errorCode)
81-
return when (errorCode.errorCode) {
82-
ErrorCode.RLM_ERR_WRONG_SYNC_TYPE -> WrongSyncTypeException(message)
81+
return if (syncError.isFatal) {
82+
// An unrecoverable exception happened
83+
UnrecoverableSyncException(message)
84+
} else {
85+
when (errorCode.errorCode) {
86+
ErrorCode.RLM_ERR_WRONG_SYNC_TYPE -> WrongSyncTypeException(message)
8387

84-
ErrorCode.RLM_ERR_INVALID_SUBSCRIPTION_QUERY -> {
85-
// Flexible Sync Query was rejected by the server
86-
BadFlexibleSyncQueryException(message)
87-
}
88-
ErrorCode.RLM_ERR_SYNC_COMPENSATING_WRITE -> CompensatingWriteException(message, syncError.compensatingWrites)
88+
ErrorCode.RLM_ERR_INVALID_SUBSCRIPTION_QUERY -> {
89+
// Flexible Sync Query was rejected by the server
90+
BadFlexibleSyncQueryException(message)
91+
}
8992

90-
ErrorCode.RLM_ERR_SYNC_PROTOCOL_INVARIANT_FAILED,
91-
ErrorCode.RLM_ERR_SYNC_PROTOCOL_NEGOTIATION_FAILED,
92-
ErrorCode.RLM_ERR_SYNC_PERMISSION_DENIED -> {
93-
// Permission denied errors should be unrecoverable according to Core, i.e. the
94-
// client will disconnect sync and transition to the "inactive" state
95-
UnrecoverableSyncException(message)
96-
}
97-
else -> {
98-
// An error happened we are not sure how to handle. Just report as a generic
99-
// SyncException.
100-
SyncException(message)
93+
ErrorCode.RLM_ERR_SYNC_COMPENSATING_WRITE -> CompensatingWriteException(
94+
message,
95+
syncError.compensatingWrites
96+
)
97+
ErrorCode.RLM_ERR_SYNC_PROTOCOL_INVARIANT_FAILED,
98+
ErrorCode.RLM_ERR_SYNC_PROTOCOL_NEGOTIATION_FAILED,
99+
ErrorCode.RLM_ERR_SYNC_PERMISSION_DENIED -> {
100+
// Permission denied errors should be unrecoverable according to Core, i.e. the
101+
// client will disconnect sync and transition to the "inactive" state
102+
UnrecoverableSyncException(message)
103+
}
104+
else -> {
105+
// An error happened we are not sure how to handle. Just report as a generic
106+
// SyncException.
107+
SyncException(message)
108+
}
101109
}
102110
}
103111
}

packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/SyncedRealmTests.kt

Lines changed: 57 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
* See the License for the specific language governing permissions and
1414
* limitations under the License.
1515
*/
16-
@file:Suppress("invisible_reference", "invisible_member")
16+
@file:Suppress("invisible_member", "invisible_reference")
1717

1818
package io.realm.kotlin.test.mongodb.common
1919

@@ -28,15 +28,19 @@ import io.realm.kotlin.entities.sync.flx.FlexChildObject
2828
import io.realm.kotlin.entities.sync.flx.FlexEmbeddedObject
2929
import io.realm.kotlin.entities.sync.flx.FlexParentObject
3030
import io.realm.kotlin.ext.query
31+
import io.realm.kotlin.internal.interop.ErrorCode
32+
import io.realm.kotlin.internal.interop.RealmInterop
3133
import io.realm.kotlin.internal.platform.fileExists
3234
import io.realm.kotlin.internal.platform.pathOf
3335
import io.realm.kotlin.internal.platform.runBlocking
3436
import io.realm.kotlin.log.RealmLog
3537
import io.realm.kotlin.mongodb.App
38+
import io.realm.kotlin.mongodb.Credentials
3639
import io.realm.kotlin.mongodb.User
3740
import io.realm.kotlin.mongodb.exceptions.DownloadingRealmTimeOutException
3841
import io.realm.kotlin.mongodb.exceptions.SyncException
3942
import io.realm.kotlin.mongodb.exceptions.UnrecoverableSyncException
43+
import io.realm.kotlin.mongodb.internal.SyncSessionImpl
4044
import io.realm.kotlin.mongodb.subscriptions
4145
import io.realm.kotlin.mongodb.sync.InitialSubscriptionsCallback
4246
import io.realm.kotlin.mongodb.sync.SubscriptionSetState
@@ -313,6 +317,56 @@ class SyncedRealmTests {
313317
realm3.close()
314318
}
315319

320+
@Test
321+
fun errorHandlerProcessFatalSyncErrors() {
322+
val channel = TestChannel<Throwable>()
323+
val user = runBlocking {
324+
app.login(Credentials.anonymous())
325+
}
326+
327+
val config = SyncConfiguration.Builder(
328+
schema = setOf(ParentPk::class, ChildPk::class),
329+
user = user,
330+
partitionValue = partitionValue
331+
).errorHandler { _, error ->
332+
channel.trySendOrFail(error)
333+
}.build()
334+
335+
runBlocking {
336+
val deferred = async {
337+
Realm.open(config).use { realm ->
338+
RealmInterop.realm_sync_session_handle_error_for_testing(
339+
syncSession = (realm.syncSession as SyncSessionImpl).nativePointer,
340+
error = ErrorCode.RLM_ERR_ACCOUNT_NAME_IN_USE,
341+
errorMessage = "Non fatal error",
342+
isFatal = true, // flipped https://jira.mongodb.org/browse/RCORE-2146
343+
)
344+
345+
RealmInterop.realm_sync_session_handle_error_for_testing(
346+
syncSession = (realm.syncSession as SyncSessionImpl).nativePointer,
347+
error = ErrorCode.RLM_ERR_INTERNAL_SERVER_ERROR,
348+
errorMessage = "Fatal error",
349+
isFatal = false, // flipped https://jira.mongodb.org/browse/RCORE-2146
350+
)
351+
}
352+
}
353+
354+
// First error
355+
channel.receiveOrFail().let { error ->
356+
assertNotNull(error.message)
357+
assertIs<SyncException>(error)
358+
}
359+
360+
// Second
361+
channel.receiveOrFail().let { error ->
362+
assertNotNull(error.message)
363+
assertIs<UnrecoverableSyncException>(error)
364+
}
365+
366+
deferred.cancel()
367+
}
368+
}
369+
316370
@Test
317371
fun errorHandlerReceivesPermissionDeniedSyncError() {
318372
val channel = TestChannel<Throwable>()
@@ -336,15 +390,15 @@ class SyncedRealmTests {
336390
Realm.open(config).use {
337391
// Make sure that the test eventually fail. Coroutines can cancel a delay
338392
// so this doesn't always block the test for 10 seconds.
339-
delay(10 * 1000)
393+
delay(10_000)
340394
channel.send(AssertionError("Realm was successfully opened"))
341395
}
342396
}
343397

344398
val error = channel.receiveOrFail()
345-
assertTrue(error is UnrecoverableSyncException, "Was $error")
346399
val message = error.message
347400
assertNotNull(message)
401+
assertTrue(error is UnrecoverableSyncException, "Was $error")
348402
assertTrue(
349403
message.lowercase().contains("permission denied"),
350404
"The error should be 'PermissionDenied' but it was: $message"

0 commit comments

Comments
 (0)