Merge pull request #4193 from vector-im/feature/bma/fix_logout_crash

Try to fix #4007
This commit is contained in:
Benoit Marty 2021-10-12 15:19:20 +02:00 committed by GitHub
commit 7338982030
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 53 additions and 35 deletions

1
changelog.d/4193.bugfix Normal file
View File

@ -0,0 +1 @@
Fix random crash when user logs out just after the log in.

View File

@ -379,7 +379,8 @@ internal interface IMXCryptoStore {
fun getOrAddOutgoingSecretShareRequest(secretName: String, recipients: Map<String, List<String>>): OutgoingSecretRequest?
fun saveGossipingEvent(event: Event)
fun saveGossipingEvent(event: Event) = saveGossipingEvents(listOf(event))
fun saveGossipingEvents(events: List<Event>)
fun updateGossipingRequestState(request: IncomingShareRequestCommon, state: GossipingRequestState) {

View File

@ -25,6 +25,7 @@ import io.realm.Realm
import io.realm.RealmConfiguration
import io.realm.Sort
import io.realm.kotlin.where
import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.session.crypto.crosssigning.MXCrossSigningInfo
import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.session.room.send.SendState
@ -100,6 +101,8 @@ import org.matrix.olm.OlmAccount
import org.matrix.olm.OlmException
import org.matrix.olm.OlmOutboundGroupSession
import timber.log.Timber
import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit
import javax.inject.Inject
import kotlin.collections.set
@ -137,8 +140,11 @@ internal class RealmCryptoStore @Inject constructor(
newSessionListeners.remove(listener)
}
private val monarchyWriteAsyncExecutor = Executors.newSingleThreadExecutor()
private val monarchy = Monarchy.Builder()
.setRealmConfiguration(realmConfiguration)
.setWriteAsyncExecutor(monarchyWriteAsyncExecutor)
.build()
init {
@ -199,6 +205,14 @@ internal class RealmCryptoStore @Inject constructor(
}
override fun close() {
// Ensure no async request will be run later
val tasks = monarchyWriteAsyncExecutor.shutdownNow()
Timber.w("Closing RealmCryptoStore, ${tasks.size} async task(s) cancelled")
tryOrNull("Interrupted") {
// Wait 1 minute max
monarchyWriteAsyncExecutor.awaitTermination(1, TimeUnit.MINUTES)
}
olmSessionsToRelease.forEach {
it.value.olmSession.releaseSession()
}
@ -1163,8 +1177,8 @@ internal class RealmCryptoStore @Inject constructor(
}
override fun saveGossipingEvents(events: List<Event>) {
val now = System.currentTimeMillis()
monarchy.writeAsync { realm ->
val now = System.currentTimeMillis()
events.forEach { event ->
val ageLocalTs = event.unsignedData?.age?.let { now - it } ?: now
val entity = GossipingEventEntity(
@ -1182,23 +1196,6 @@ internal class RealmCryptoStore @Inject constructor(
}
}
override fun saveGossipingEvent(event: Event) {
monarchy.writeAsync { realm ->
val now = System.currentTimeMillis()
val ageLocalTs = event.unsignedData?.age?.let { now - it } ?: now
val entity = GossipingEventEntity(
type = event.type,
sender = event.senderId,
ageLocalTs = ageLocalTs,
content = ContentMapper.map(event.content)
).apply {
sendState = SendState.SYNCED
decryptionResultJson = MoshiProvider.providesMoshi().adapter(OlmDecryptionResult::class.java).toJson(event.mxDecryptionResult)
decryptionErrorCode = event.mCryptoError?.name
}
realm.insertOrUpdate(entity)
}
}
// override fun getOutgoingRoomKeyRequestByState(states: Set<ShareRequestState>): OutgoingRoomKeyRequest? {
// val statesIndex = states.map { it.ordinal }.toTypedArray()
// return doRealmQueryAndCopy(realmConfiguration) { realm ->

View File

@ -18,7 +18,7 @@ package org.matrix.android.sdk.internal.session.cleanup
import io.realm.Realm
import io.realm.RealmConfiguration
import org.matrix.android.sdk.BuildConfig
import kotlinx.coroutines.delay
import org.matrix.android.sdk.internal.SessionManager
import org.matrix.android.sdk.internal.auth.SessionParamsStore
import org.matrix.android.sdk.internal.crypto.CryptoModule
@ -51,37 +51,56 @@ internal class CleanupSession @Inject constructor(
@UserMd5 private val userMd5: String
) {
suspend fun handle() {
val sessionRealmCount = Realm.getGlobalInstanceCount(realmSessionConfiguration)
val cryptoRealmCount = Realm.getGlobalInstanceCount(realmCryptoConfiguration)
Timber.d("Realm instance ($sessionRealmCount - $cryptoRealmCount)")
Timber.d("Cleanup: delete session params...")
sessionParamsStore.delete(sessionId)
Timber.d("Cleanup: cancel pending works...")
workManagerProvider.cancelAllWorks()
Timber.d("Cleanup: release session...")
sessionManager.releaseSession(sessionId)
Timber.d("Cleanup: clear session data...")
clearSessionDataTask.execute(Unit)
Timber.d("Cleanup: clear crypto data...")
clearCryptoDataTask.execute(Unit)
Timber.d("Cleanup: clear file system")
sessionFiles.deleteRecursively()
sessionCache.deleteRecursively()
Timber.d("Cleanup: clear the database keys")
realmKeysUtils.clear(SessionModule.getKeyAlias(userMd5))
realmKeysUtils.clear(CryptoModule.getKeyAlias(userMd5))
Timber.d("Cleanup: release session...")
sessionManager.releaseSession(sessionId)
// Wait for all the Realm instance to be released properly. Closing Realm instance is async.
// After that we can safely delete the Realm files
waitRealmRelease()
// Sanity check
if (BuildConfig.DEBUG) {
Realm.getGlobalInstanceCount(realmSessionConfiguration)
.takeIf { it > 0 }
?.let { Timber.e("All realm instance for session has not been closed ($it)") }
Realm.getGlobalInstanceCount(realmCryptoConfiguration)
.takeIf { it > 0 }
?.let { Timber.e("All realm instance for crypto has not been closed ($it)") }
}
Timber.d("Cleanup: clear file system")
sessionFiles.deleteRecursively()
sessionCache.deleteRecursively()
}
private suspend fun waitRealmRelease() {
var timeToWaitMillis = MAX_TIME_TO_WAIT_MILLIS
do {
val sessionRealmCount = Realm.getGlobalInstanceCount(realmSessionConfiguration)
val cryptoRealmCount = Realm.getGlobalInstanceCount(realmCryptoConfiguration)
Timber.d("Wait for all Realm instance to be closed ($sessionRealmCount - $cryptoRealmCount)")
if (sessionRealmCount > 0 || cryptoRealmCount > 0) {
Timber.d("Waiting ${TIME_TO_WAIT_MILLIS}ms")
delay(TIME_TO_WAIT_MILLIS)
timeToWaitMillis -= TIME_TO_WAIT_MILLIS
} else {
timeToWaitMillis = 0
}
} while (timeToWaitMillis > 0)
}
companion object {
private const val MAX_TIME_TO_WAIT_MILLIS = 10_000L
private const val TIME_TO_WAIT_MILLIS = 10L
}
}