Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix crashes on unmapped error codes #951

Merged
merged 23 commits into from
Aug 19, 2022
Merged

Conversation

clementetb
Copy link
Contributor

The SDK would crash if core throws an exception that is not mapped by the C-API.

This PR fixes this situation by handling these unmapped error codes into proper sync or app exceptions.

@clementetb clementetb marked this pull request as ready for review August 4, 2022 16:19
@cla-bot cla-bot bot added the cla: yes label Aug 4, 2022
@clementetb clementetb self-assigned this Aug 4, 2022
@clementetb clementetb requested review from cmelchior and rorbech August 4, 2022 16:19
Copy link
Contributor

@cmelchior cmelchior left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice, only minor comments + we also need a CHANGELOG update.

public constructor(message: String) : super(message)
public constructor(message: String, cause: Throwable) : super(message, cause)
public constructor(cause: Throwable) : super(cause)
public constructor(message: String?) : super(message)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was actually on purpose I made these non-null as we should always strive to have some kind of message. I can see you use null in the safeErrorCodeMapping method, but I would consider if we could just return "" there instead since it is kind of an edge case.

return "[$categoryDesc][$errorDesc] ${error.message}"
}

// Prevents exceptions caused by unmapped sync errors from propagating and causing a crash. The exception
// message describes the missing error code and its category.
internal fun <T> safeErrorCodeMapping(onException: (message: String?) -> T, block: () -> T): T =
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice way doing this 👍

ProtocolConnectionErrorCode.RLM_SYNC_ERR_CONNECTION_SWITCH_TO_PBS -> { // Connected with wrong wire protocol - should switch to PBS
WrongSyncTypeException(message)
return safeErrorCodeMapping(onException = { SyncException(it) }) {
when (error.category) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably not worth fixing in this PR, but we would still crash if we suddenly see a category we haven't seen before. It would crash here though, but inside RealmInterop. Maybe just create an issue for it for now?

Copy link
Contributor

@rorbech rorbech left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe I just don't get the overall layers of mapping, but the tests looks more like a unittest of the AppErrorCode/SyncErrorCode-constructor than a test verifying that we actually can handle unmapped error codes. Don't know if it possible for Sync errors, but for App-errors we could probably inject a mock HTTP-client that issued unknown error codes. Don't know if it is worth considering for this PR compared to getting the fix out though 🤔 ... has it been verified manually?

@clementetb clementetb force-pushed the ct/unmapped-error-codes branch from 3ddf15b to a92ca9c Compare August 8, 2022 10:10
@clementetb clementetb requested review from cmelchior and rorbech August 9, 2022 11:30
Copy link
Contributor

@edualonso edualonso left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only tiny things, otherwise looks good 💯

@@ -63,7 +61,7 @@ internal fun convertSyncErrorCode(error: SyncErrorCode): SyncException {
// See https://github.com/realm/realm-core/blob/master/src/realm/sync/protocol.hpp#L200
// Use https://docs.google.com/spreadsheets/d/1SmiRxhFpD1XojqCKC-xAjjV-LKa9azeeWHg-zgr07lE/edit
// as guide for how to categorize Connection type errors.
when (ProtocolConnectionErrorCode.fromInt(error.value)) {
when (error.error) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant naming?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not really, but we can rename the variable error: SyncErrorCode into syncErrorCode: SyncErrorCode for readability.

Comment on lines +229 to +230
SyncErrorCodeCategory.RLM_SYNC_ERROR_CATEGORY_UNKNOWN,
-> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Weird indenting?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Helps add new categories.

Comment on lines 242 to 245
if (errorCodeDesc == null)
error.errorCode.toString()
else
"$errorCodeDesc(${error.errorCode})"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Leave it as a one liner or write braces for the if

Copy link
Contributor

@cmelchior cmelchior left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only minor comments 🚀

@@ -16,18 +16,55 @@

package io.realm.kotlin.internal.interop.sync

import kotlin.jvm.JvmStatic

interface ErrorCode {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe add a description of what this interface is for? Especially since we also have an errorCode: Int in AppError

val category: AppErrorCategory,
data class AppError internal constructor(
val category: AppErrorCategory?,
val error: ErrorCode?,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is a bit confusing that we have both error: ErrorCode and errorCode: Int. Should we either combine them or rename one of them?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I got the same feeling, but they map core structures, I will try to get better naming.

return value
}
internal actual fun of(nativeValue: Int): AppErrorCategory? =
values().first { value ->
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this be firstOrNull otherwise I think it will throw?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

😅 yes

return value
}
internal actual fun of(nativeValue: Int): ClientErrorCode? =
values().first { value ->
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same

return value
}
internal actual fun of(nativeValue: Int): SyncErrorCodeCategory? =
values().first { value ->
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same

return value
}
internal actual fun of(nativeValue: Int): ProtocolConnectionErrorCode? =
values().first { value ->
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same

return value
}
internal actual fun of(nativeValue: Int): ProtocolSessionErrorCode? =
values().first { value ->
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same

return value
}
internal actual fun of(nativeValue: Int): ServiceErrorCode? =
values().first { value ->
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same

return value
}
internal actual fun of(nativeValue: Int): SyncErrorCodeCategory? =
values().first { value ->
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same

// server log.
val msg = error.message?.let { message: String ->
if (message.endsWith(".")) {
message
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't add a space before it so I suspect this will make the error a little more unreadable, e.g [Category][ErrorCode]Message. rather than [Category][ErrorCode] Message.

@clementetb clementetb merged commit 2391281 into releases Aug 19, 2022
@clementetb clementetb deleted the ct/unmapped-error-codes branch August 19, 2022 15:39
@github-actions github-actions bot locked as resolved and limited conversation to collaborators Mar 15, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants