From 6efb10df11a36c2fb1ca379a6b3d33c45b0c8d84 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 12 Oct 2026 14:33:17 +0200 Subject: [PATCH] Write the push history off the main thread The push history was written to the database synchronously, from the app coroutine scope which runs on the main thread, for every received push. Make PushHistoryService.onPushResult suspend and run all the database queries on the IO dispatcher. Co-Authored-By: Claude Opus 5.5 (1M context) --- .../impl/history/DefaultPushHistoryService.kt | 47 ++++++++++--------- .../push/impl/history/PushHistoryService.kt | 12 ++--- .../impl/history/FakePushHistoryService.kt | 2 +- 3 files changed, 33 insertions(+), 28 deletions(-) diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/history/DefaultPushHistoryService.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/history/DefaultPushHistoryService.kt index ab6f01e423..f1c4c9d9a3 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/history/DefaultPushHistoryService.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/history/DefaultPushHistoryService.kt @@ -14,6 +14,7 @@ import android.os.PowerManager import androidx.core.content.getSystemService import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding +import io.element.android.libraries.core.coroutine.CoroutineDispatchers import io.element.android.libraries.core.extensions.runCatchingExceptions import io.element.android.libraries.di.annotations.ApplicationContext import io.element.android.libraries.matrix.api.core.EventId @@ -23,23 +24,25 @@ import io.element.android.libraries.push.impl.PushDatabase import io.element.android.libraries.push.impl.db.PushHistory import io.element.android.libraries.push.impl.db.PushRequest import io.element.android.services.toolbox.api.systemclock.SystemClock +import kotlinx.coroutines.withContext import kotlin.time.Instant @ContributesBinding(AppScope::class) class DefaultPushHistoryService( private val pushDatabase: PushDatabase, private val systemClock: SystemClock, + private val dispatchers: CoroutineDispatchers, @ApplicationContext context: Context, ) : PushHistoryService { private val powerManager = context.getSystemService() private val packageName = context.packageName - override suspend fun insertOrUpdatePushRequest(pushRequest: PushRequest): Result { - return runCatchingExceptions { pushDatabase.pushRequestQueries.insertPushRequest(pushRequest).await() } + override suspend fun insertOrUpdatePushRequest(pushRequest: PushRequest): Result = withContext(dispatchers.io) { + runCatchingExceptions { pushDatabase.pushRequestQueries.insertPushRequest(pushRequest).await() } } - override suspend fun insertOrUpdatePushRequests(pushRequests: List): Result { - return runCatchingExceptions { + override suspend fun insertOrUpdatePushRequests(pushRequests: List): Result = withContext(dispatchers.io) { + runCatchingExceptions { pushDatabase.transaction { for (request in pushRequests) { pushDatabase.pushRequestQueries.insertPushRequest(request) @@ -48,8 +51,8 @@ class DefaultPushHistoryService( } } - override suspend fun getPendingPushRequests(sessionId: SessionId, since: Instant?): Result> { - return runCatchingExceptions { + override suspend fun getPendingPushRequests(sessionId: SessionId, since: Instant?): Result> = withContext(dispatchers.io) { + runCatchingExceptions { pushDatabase.transactionWithResult { val sinceTimeMillis = since?.toEpochMilliseconds() ?: 0 pushDatabase.pushRequestQueries.selectAllPendingForSession(sessionId.value, sinceTimeMillis).executeAsList() @@ -57,14 +60,14 @@ class DefaultPushHistoryService( } } - override suspend fun removeOldPushRequests(sessionId: SessionId): Result { - return runCatchingExceptions { + override suspend fun removeOldPushRequests(sessionId: SessionId): Result = withContext(dispatchers.io) { + runCatchingExceptions { val keepAmount = 100L pushDatabase.pushRequestQueries.removeOldest(keepAmount) } } - override fun onPushResult( + override suspend fun onPushResult( providerInfo: String, eventId: EventId?, roomId: RoomId?, @@ -91,19 +94,21 @@ class DefaultPushHistoryService( } }.takeIf { it.isNotEmpty() } - pushDatabase.pushHistoryQueries.insertPushHistory( - PushHistory( - pushDate = systemClock.epochMillis(), - providerInfo = providerInfo, - eventId = eventId?.value, - roomId = roomId?.value, - sessionId = sessionId?.value, - hasBeenResolved = if (hasBeenResolved) 1 else 0, - comment = finalComment, + withContext(dispatchers.io) { + pushDatabase.pushHistoryQueries.insertPushHistory( + PushHistory( + pushDate = systemClock.epochMillis(), + providerInfo = providerInfo, + eventId = eventId?.value, + roomId = roomId?.value, + sessionId = sessionId?.value, + hasBeenResolved = if (hasBeenResolved) 1 else 0, + comment = finalComment, + ) ) - ) - // Keep only the last 1_000 events - pushDatabase.pushHistoryQueries.removeOldest(1_000) + // Keep only the last 1_000 events + pushDatabase.pushHistoryQueries.removeOldest(1_000) + } } } diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/history/PushHistoryService.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/history/PushHistoryService.kt index 3996924322..298abbd441 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/history/PushHistoryService.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/history/PushHistoryService.kt @@ -20,7 +20,7 @@ interface PushHistoryService { * Create a new push history entry. * Do not use directly, prefer using the extension functions. */ - fun onPushResult( + suspend fun onPushResult( providerInfo: String, eventId: EventId?, roomId: RoomId?, @@ -52,7 +52,7 @@ interface PushHistoryService { suspend fun removeOldPushRequests(sessionId: SessionId): Result } -fun PushHistoryService.onInvalidPushReceived( +suspend fun PushHistoryService.onInvalidPushReceived( providerInfo: String, data: String, ) = onPushResult( @@ -65,7 +65,7 @@ fun PushHistoryService.onInvalidPushReceived( comment = "Invalid or ignored push data:\n$data", ) -fun PushHistoryService.onUnableToRetrieveSession( +suspend fun PushHistoryService.onUnableToRetrieveSession( providerInfo: String, eventId: EventId, roomId: RoomId, @@ -80,7 +80,7 @@ fun PushHistoryService.onUnableToRetrieveSession( comment = "Unable to retrieve session: $reason", ) -fun PushHistoryService.onUnableToResolveEvent( +suspend fun PushHistoryService.onUnableToResolveEvent( providerInfo: String, eventId: EventId, roomId: RoomId, @@ -96,7 +96,7 @@ fun PushHistoryService.onUnableToResolveEvent( comment = "Unable to resolve event: $reason", ) -fun PushHistoryService.onSuccess( +suspend fun PushHistoryService.onSuccess( providerInfo: String, eventId: EventId, roomId: RoomId, @@ -117,7 +117,7 @@ fun PushHistoryService.onSuccess( }, ) -fun PushHistoryService.onDiagnosticPush( +suspend fun PushHistoryService.onDiagnosticPush( providerInfo: String, ) = onPushResult( providerInfo = providerInfo, diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/history/FakePushHistoryService.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/history/FakePushHistoryService.kt index aa9612717a..e7cd342514 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/history/FakePushHistoryService.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/history/FakePushHistoryService.kt @@ -30,7 +30,7 @@ class FakePushHistoryService( private val getPendingPushRequests: (SessionId, Instant?) -> Result> = { _, _ -> lambdaError() }, private val removeOldPushRequests: (SessionId) -> Result = { lambdaError() }, ) : PushHistoryService { - override fun onPushResult( + override suspend fun onPushResult( providerInfo: String, eventId: EventId?, roomId: RoomId?,