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) <noreply@anthropic.com>
This commit is contained in:
Benoit Marty
2026-10-12 14:33:17 +02:00
parent 9be2a31c5f
commit 6efb10df11
3 changed files with 33 additions and 28 deletions
@@ -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<PowerManager>()
private val packageName = context.packageName
override suspend fun insertOrUpdatePushRequest(pushRequest: PushRequest): Result<Unit> {
return runCatchingExceptions { pushDatabase.pushRequestQueries.insertPushRequest(pushRequest).await() }
override suspend fun insertOrUpdatePushRequest(pushRequest: PushRequest): Result<Unit> = withContext(dispatchers.io) {
runCatchingExceptions<Unit> { pushDatabase.pushRequestQueries.insertPushRequest(pushRequest).await() }
}
override suspend fun insertOrUpdatePushRequests(pushRequests: List<PushRequest>): Result<Unit> {
return runCatchingExceptions {
override suspend fun insertOrUpdatePushRequests(pushRequests: List<PushRequest>): Result<Unit> = 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<List<PushRequest>> {
return runCatchingExceptions {
override suspend fun getPendingPushRequests(sessionId: SessionId, since: Instant?): Result<List<PushRequest>> = 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<Unit> {
return runCatchingExceptions {
override suspend fun removeOldPushRequests(sessionId: SessionId): Result<Unit> = withContext(dispatchers.io) {
runCatchingExceptions<Unit> {
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)
}
}
}
@@ -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<Unit>
}
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,
@@ -30,7 +30,7 @@ class FakePushHistoryService(
private val getPendingPushRequests: (SessionId, Instant?) -> Result<List<PushRequest>> = { _, _ -> lambdaError() },
private val removeOldPushRequests: (SessionId) -> Result<Unit> = { lambdaError() },
) : PushHistoryService {
override fun onPushResult(
override suspend fun onPushResult(
providerInfo: String,
eventId: EventId?,
roomId: RoomId?,