From 65af80655df845abbbd060887c06645d648ea2be Mon Sep 17 00:00:00 2001 From: SessionHero01 <180888785+SessionHero01@users.noreply.github.com> Date: Fri, 13 Feb 2026 10:55:34 +1100 Subject: [PATCH 1/2] Bump version to 1.31.1 --- app/build.gradle.kts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index b7a9a074b5..5a929a6d2b 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -26,8 +26,8 @@ configurations.configureEach { exclude(module = "commons-logging") } -val canonicalVersionCode = 437 -val canonicalVersionName = "1.31.0" +val canonicalVersionCode = 438 +val canonicalVersionName = "1.31.1" val postFixSize = 10 val abiPostFix = mapOf( From b59848ac1790f0ff17341062dc2e8d84cbece360 Mon Sep 17 00:00:00 2001 From: SessionHero01 <180888785+SessionHero01@users.noreply.github.com> Date: Fri, 13 Feb 2026 11:59:36 +1100 Subject: [PATCH 2/2] Fix a few crashes (#1933) * Add stack to API exception and catches a few exceptions * Try catch PathManager * Cancellation handling * Cancellation handling and comments * Tidy up cancellation handling in fetchSnodePoolFromSeedWithFallback --- .../OfficialCommunityRepository.kt | 2 ++ .../libsession/network/onion/PathManager.kt | 8 +++++- .../network/snode/SnodeDirectory.kt | 19 ++++++++++---- .../securesms/api/AutoRetryApiExecutor.kt | 13 +++++++++- .../securesms/configs/ConfigToDatabaseSync.kt | 18 ++++++++++--- .../securesms/groups/GroupManagerV2Impl.kt | 8 +++++- .../notifications/MarkReadProcessor.kt | 25 ++++++++++++------- 7 files changed, 72 insertions(+), 21 deletions(-) diff --git a/app/src/main/java/org/session/libsession/messaging/open_groups/OfficialCommunityRepository.kt b/app/src/main/java/org/session/libsession/messaging/open_groups/OfficialCommunityRepository.kt index 8f17a7a2e1..7c56d02bcf 100644 --- a/app/src/main/java/org/session/libsession/messaging/open_groups/OfficialCommunityRepository.kt +++ b/app/src/main/java/org/session/libsession/messaging/open_groups/OfficialCommunityRepository.kt @@ -100,6 +100,8 @@ class OfficialCommunityRepository @Inject constructor( ) } } + }.onFailure { + if (it is CancellationException) throw it }) } } diff --git a/app/src/main/java/org/session/libsession/network/onion/PathManager.kt b/app/src/main/java/org/session/libsession/network/onion/PathManager.kt index 44efd0c0ee..011a49533a 100644 --- a/app/src/main/java/org/session/libsession/network/onion/PathManager.kt +++ b/app/src/main/java/org/session/libsession/network/onion/PathManager.kt @@ -93,7 +93,13 @@ open class PathManager @Inject constructor( scope.launch { _paths.drop(1).collectLatest { paths -> if (paths.isEmpty()) storage.clearOnionRequestPaths() - else storage.setOnionRequestPaths(paths) + else { + try { + storage.setOnionRequestPaths(paths) + } catch (e: Exception) { + Log.e("Onion Request", "Failed to persist paths to storage, keeping in-memory only", e) + } + } } } } diff --git a/app/src/main/java/org/session/libsession/network/snode/SnodeDirectory.kt b/app/src/main/java/org/session/libsession/network/snode/SnodeDirectory.kt index e19e0ec5c3..1b1f9cac36 100644 --- a/app/src/main/java/org/session/libsession/network/snode/SnodeDirectory.kt +++ b/app/src/main/java/org/session/libsession/network/snode/SnodeDirectory.kt @@ -2,6 +2,7 @@ package org.session.libsession.network.snode import android.content.Context import dagger.hilt.android.qualifiers.ApplicationContext +import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch import kotlinx.coroutines.sync.Mutex @@ -175,6 +176,8 @@ class SnodeDirectory @Inject constructor( } .result }.onFailure { e -> + if (e is CancellationException) throw e + lastError = e Log.w("SnodeDirectory", "Seed node failed: $target", e) } @@ -277,11 +280,17 @@ class SnodeDirectory @Inject constructor( if (now >= last && now - last < POOL_REFRESH_INTERVAL_MS) return scope.launch { - refreshPoolFromSnodes( - totalSnodeQueries = 3, - minAppearance = 2, - distinctQuerySubnetPrefix = 24, - ) + try { + refreshPoolFromSnodes( + totalSnodeQueries = 3, + minAppearance = 2, + distinctQuerySubnetPrefix = 24, + ) + } catch (e: Throwable) { + if (e is CancellationException) throw e + + Log.w("SnodeDirectory", "Error refreshing snode pool", e) + } } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/api/AutoRetryApiExecutor.kt b/app/src/main/java/org/thoughtcrime/securesms/api/AutoRetryApiExecutor.kt index 9f813877d5..c8d6ebc7cd 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/api/AutoRetryApiExecutor.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/api/AutoRetryApiExecutor.kt @@ -18,6 +18,8 @@ class AutoRetryApiExecutor( private val actualExecutor: ApiExecutor, ) : ApiExecutor { override suspend fun send(ctx: ApiExecutorContext, req: Req): Res { + val initStack = Throwable().stackTrace + var numRetried = 0 while (true) { try { @@ -32,7 +34,16 @@ class AutoRetryApiExecutor( Log.e(TAG, "Retrying $req $numRetried times due to error", e) delay(numRetried * 2000L) } else { - throw e + // If we know the error is ErrorWithFailureDecision, we can + // safely modify its stacktrace as we know that exception contains + // a cause where it can pinpoint to the direct trace of the error. + // Otherwise, we'll create a new exception to modify the stacktrace, so to + // preserve the original error's stacktrace which may contain important + // information about the error. + + val errorToUpdateStack = e.takeIf { it is ErrorWithFailureDecision } ?: RuntimeException(e) + errorToUpdateStack.stackTrace = initStack + throw errorToUpdateStack } } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/configs/ConfigToDatabaseSync.kt b/app/src/main/java/org/thoughtcrime/securesms/configs/ConfigToDatabaseSync.kt index 388f9cf16f..0b151df9f4 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/configs/ConfigToDatabaseSync.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/configs/ConfigToDatabaseSync.kt @@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.configs import android.content.Context import dagger.hilt.android.qualifiers.ApplicationContext +import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -323,10 +324,19 @@ class ConfigToDatabaseSync @Inject constructor( messageHashes = cleanedHashes, swarmAuth = groupAdminAuth ) - swarmApiExecutor.execute(SwarmApiRequest( - swarmPubKeyHex = groupInfoConfig.id.hexString, - api = deleteMessageApi - )) + + try { + swarmApiExecutor.execute( + SwarmApiRequest( + swarmPubKeyHex = groupInfoConfig.id.hexString, + api = deleteMessageApi + ) + ) + } catch (e: Exception) { + if (e is CancellationException) throw e + + Log.e(TAG, "Failed to delete messages from swarm for group", e) + } } } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManagerV2Impl.kt b/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManagerV2Impl.kt index a5f2fc7ae3..ceaee18722 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManagerV2Impl.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManagerV2Impl.kt @@ -4,6 +4,7 @@ import android.content.Context import com.google.protobuf.ByteString import com.squareup.phrase.Phrase import dagger.hilt.android.qualifiers.ApplicationContext +import kotlinx.coroutines.CancellationException import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.async import kotlinx.coroutines.flow.filter @@ -1225,7 +1226,12 @@ class GroupManagerV2Impl @Inject constructor( override fun onBlocked(groupAccountId: AccountId) { scope.launch(groupAccountId, "On blocked") { - respondToInvitation(groupAccountId, false) + try { + respondToInvitation(groupAccountId, false) + } catch (e: Throwable) { + if (e is CancellationException) throw e + Log.e(TAG, "Failed to unapprove invitation for group", e) + } // Remove this group from config regardless configFactory.removeGroup(groupAccountId) diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/MarkReadProcessor.kt b/app/src/main/java/org/thoughtcrime/securesms/notifications/MarkReadProcessor.kt index 94a1f34d2d..b90a4501a9 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/MarkReadProcessor.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/MarkReadProcessor.kt @@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.notifications import android.content.Context import dagger.hilt.android.qualifiers.ApplicationContext +import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch @@ -107,17 +108,23 @@ class MarkReadProcessor @Inject constructor( keySelector = { it.value.expirationInfo.expiresIn }, valueTransform = { it.key } ).forEach { (expiresIn, hashes) -> - swarmApiExecutor.execute( - SwarmApiRequest( - swarmPubKeyHex = userAuth.accountId.hexString, - api = alterTtyFactory.create( - messageHashes = hashes, - auth = userAuth, - alterType = AlterTtlApi.AlterType.Shorten, - newExpiry = snodeClock.currentTimeMillis() + expiresIn + try { + swarmApiExecutor.execute( + SwarmApiRequest( + swarmPubKeyHex = userAuth.accountId.hexString, + api = alterTtyFactory.create( + messageHashes = hashes, + auth = userAuth, + alterType = AlterTtlApi.AlterType.Shorten, + newExpiry = snodeClock.currentTimeMillis() + expiresIn + ) ) ) - ) + } catch (e: Throwable) { + if (e is CancellationException) throw e + + Log.e(TAG, "Failed to shorten expiry for messages with hashes $hashes", e) + } } } }