When a background SDK task fails, react in the client (#6166)

- For initialization issues or errors, we just print and report them.
- For panics (unrecoverable errors) we also crash the app.
This commit is contained in:
Jorge Martin Espinosa 2026-02-10 12:28:24 +01:00 committed by GitHub
parent b271e06973
commit 5b5224eb58
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 88 additions and 3 deletions

View file

@ -10,11 +10,14 @@ package io.element.android.libraries.matrix.impl
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
import io.element.android.libraries.core.log.logger.LoggerTag
import io.element.android.libraries.matrix.impl.core.SdkBackgroundTaskError
import io.element.android.libraries.matrix.impl.mapper.toSessionData
import io.element.android.libraries.matrix.impl.paths.getSessionPaths
import io.element.android.libraries.matrix.impl.util.anonymizedTokens
import io.element.android.libraries.sessionstorage.api.SessionStore
import io.element.android.services.analytics.api.AnalyticsService
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import org.matrix.rustcomponents.sdk.ClientDelegate
import org.matrix.rustcomponents.sdk.ClientSessionDelegate
@ -23,6 +26,7 @@ import timber.log.Timber
import uniffi.matrix_sdk_common.BackgroundTaskFailureReason
import java.lang.ref.WeakReference
import java.util.concurrent.atomic.AtomicBoolean
import kotlin.time.Duration.Companion.milliseconds
private val loggerTag = LoggerTag("RustClientSessionDelegate")
@ -36,6 +40,7 @@ private val loggerTag = LoggerTag("RustClientSessionDelegate")
class RustClientSessionDelegate(
private val sessionStore: SessionStore,
private val appCoroutineScope: CoroutineScope,
private val analyticsService: AnalyticsService,
coroutineDispatchers: CoroutineDispatchers,
) : ClientSessionDelegate, ClientDelegate {
// Used to ensure several calls to `didReceiveAuthError` don't trigger multiple logouts
@ -122,8 +127,18 @@ class RustClientSessionDelegate(
}
override fun onBackgroundTaskErrorReport(taskName: String, error: BackgroundTaskFailureReason) {
// TODO actually implement the missing logic to report to sentry and crash the app
Timber.tag(loggerTag.value).e("onBackgroundTaskErrorReport(taskName=$taskName, error=$error)")
val backgroundTaskError = SdkBackgroundTaskError(taskName, error)
Timber.e(backgroundTaskError, "SDK background task failed")
analyticsService.trackError(backgroundTaskError)
if (error is BackgroundTaskFailureReason.Panic) {
appCoroutineScope.launch {
// The SDK failed in an unrecoverable way, so it will have indeterminate behaviour now.
// Crash the app instead after a small delay to send the error.
delay(500.milliseconds)
throw backgroundTaskError
}
}
}
override fun retrieveSessionFromKeychain(userId: String): Session {

View file

@ -66,7 +66,12 @@ class RustMatrixClientFactory(
private val sqliteStoreBuilderProvider: SqliteStoreBuilderProvider,
private val workManagerScheduler: WorkManagerScheduler,
) {
private val sessionDelegate = RustClientSessionDelegate(sessionStore, appCoroutineScope, coroutineDispatchers)
private val sessionDelegate = RustClientSessionDelegate(
sessionStore = sessionStore,
appCoroutineScope = appCoroutineScope,
analyticsService = analyticsService,
coroutineDispatchers = coroutineDispatchers
)
suspend fun create(sessionData: SessionData): RustMatrixClient = withContext(coroutineDispatchers.io) {
val client = getBaseClientBuilder(

View file

@ -0,0 +1,36 @@
/*
* Copyright (c) 2026 Element Creations Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.libraries.matrix.impl.core
import uniffi.matrix_sdk_common.BackgroundTaskFailureReason
/**
* Error thrown when a background SDK task panics and can't recover.
* @param task The name of the task that failed.
* @param reason The cause of this error.
*/
class SdkBackgroundTaskError(
task: String,
reason: BackgroundTaskFailureReason,
) : Error() {
override val message: String = run {
val message = when (reason) {
is BackgroundTaskFailureReason.EarlyTermination -> "Early termination"
is BackgroundTaskFailureReason.Error -> "Error: ${reason.error}"
is BackgroundTaskFailureReason.Panic -> buildString {
append("Panic (unrecoverable): ")
reason.message?.let { append(it) }
reason.panicBacktrace?.let {
append("\n")
append(it)
}
}
}
"SDK background task '$task' failure: \n$message"
}
}