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 androidx.core.content.getSystemService
import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.AppScope
import dev.zacsweers.metro.ContributesBinding 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.core.extensions.runCatchingExceptions
import io.element.android.libraries.di.annotations.ApplicationContext import io.element.android.libraries.di.annotations.ApplicationContext
import io.element.android.libraries.matrix.api.core.EventId 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.PushHistory
import io.element.android.libraries.push.impl.db.PushRequest import io.element.android.libraries.push.impl.db.PushRequest
import io.element.android.services.toolbox.api.systemclock.SystemClock import io.element.android.services.toolbox.api.systemclock.SystemClock
import kotlinx.coroutines.withContext
import kotlin.time.Instant import kotlin.time.Instant
@ContributesBinding(AppScope::class) @ContributesBinding(AppScope::class)
class DefaultPushHistoryService( class DefaultPushHistoryService(
private val pushDatabase: PushDatabase, private val pushDatabase: PushDatabase,
private val systemClock: SystemClock, private val systemClock: SystemClock,
private val dispatchers: CoroutineDispatchers,
@ApplicationContext context: Context, @ApplicationContext context: Context,
) : PushHistoryService { ) : PushHistoryService {
private val powerManager = context.getSystemService<PowerManager>() private val powerManager = context.getSystemService<PowerManager>()
private val packageName = context.packageName private val packageName = context.packageName
override suspend fun insertOrUpdatePushRequest(pushRequest: PushRequest): Result<Unit> { override suspend fun insertOrUpdatePushRequest(pushRequest: PushRequest): Result<Unit> = withContext(dispatchers.io) {
return runCatchingExceptions { pushDatabase.pushRequestQueries.insertPushRequest(pushRequest).await() } runCatchingExceptions<Unit> { pushDatabase.pushRequestQueries.insertPushRequest(pushRequest).await() }
} }
override suspend fun insertOrUpdatePushRequests(pushRequests: List<PushRequest>): Result<Unit> { override suspend fun insertOrUpdatePushRequests(pushRequests: List<PushRequest>): Result<Unit> = withContext(dispatchers.io) {
return runCatchingExceptions { runCatchingExceptions {
pushDatabase.transaction { pushDatabase.transaction {
for (request in pushRequests) { for (request in pushRequests) {
pushDatabase.pushRequestQueries.insertPushRequest(request) pushDatabase.pushRequestQueries.insertPushRequest(request)
@@ -48,8 +51,8 @@ class DefaultPushHistoryService(
} }
} }
override suspend fun getPendingPushRequests(sessionId: SessionId, since: Instant?): Result<List<PushRequest>> { override suspend fun getPendingPushRequests(sessionId: SessionId, since: Instant?): Result<List<PushRequest>> = withContext(dispatchers.io) {
return runCatchingExceptions { runCatchingExceptions {
pushDatabase.transactionWithResult { pushDatabase.transactionWithResult {
val sinceTimeMillis = since?.toEpochMilliseconds() ?: 0 val sinceTimeMillis = since?.toEpochMilliseconds() ?: 0
pushDatabase.pushRequestQueries.selectAllPendingForSession(sessionId.value, sinceTimeMillis).executeAsList() pushDatabase.pushRequestQueries.selectAllPendingForSession(sessionId.value, sinceTimeMillis).executeAsList()
@@ -57,14 +60,14 @@ class DefaultPushHistoryService(
} }
} }
override suspend fun removeOldPushRequests(sessionId: SessionId): Result<Unit> { override suspend fun removeOldPushRequests(sessionId: SessionId): Result<Unit> = withContext(dispatchers.io) {
return runCatchingExceptions { runCatchingExceptions<Unit> {
val keepAmount = 100L val keepAmount = 100L
pushDatabase.pushRequestQueries.removeOldest(keepAmount) pushDatabase.pushRequestQueries.removeOldest(keepAmount)
} }
} }
override fun onPushResult( override suspend fun onPushResult(
providerInfo: String, providerInfo: String,
eventId: EventId?, eventId: EventId?,
roomId: RoomId?, roomId: RoomId?,
@@ -91,19 +94,21 @@ class DefaultPushHistoryService(
} }
}.takeIf { it.isNotEmpty() } }.takeIf { it.isNotEmpty() }
pushDatabase.pushHistoryQueries.insertPushHistory( withContext(dispatchers.io) {
PushHistory( pushDatabase.pushHistoryQueries.insertPushHistory(
pushDate = systemClock.epochMillis(), PushHistory(
providerInfo = providerInfo, pushDate = systemClock.epochMillis(),
eventId = eventId?.value, providerInfo = providerInfo,
roomId = roomId?.value, eventId = eventId?.value,
sessionId = sessionId?.value, roomId = roomId?.value,
hasBeenResolved = if (hasBeenResolved) 1 else 0, sessionId = sessionId?.value,
comment = finalComment, hasBeenResolved = if (hasBeenResolved) 1 else 0,
comment = finalComment,
)
) )
)
// Keep only the last 1_000 events // Keep only the last 1_000 events
pushDatabase.pushHistoryQueries.removeOldest(1_000) pushDatabase.pushHistoryQueries.removeOldest(1_000)
}
} }
} }
@@ -20,7 +20,7 @@ interface PushHistoryService {
* Create a new push history entry. * Create a new push history entry.
* Do not use directly, prefer using the extension functions. * Do not use directly, prefer using the extension functions.
*/ */
fun onPushResult( suspend fun onPushResult(
providerInfo: String, providerInfo: String,
eventId: EventId?, eventId: EventId?,
roomId: RoomId?, roomId: RoomId?,
@@ -52,7 +52,7 @@ interface PushHistoryService {
suspend fun removeOldPushRequests(sessionId: SessionId): Result<Unit> suspend fun removeOldPushRequests(sessionId: SessionId): Result<Unit>
} }
fun PushHistoryService.onInvalidPushReceived( suspend fun PushHistoryService.onInvalidPushReceived(
providerInfo: String, providerInfo: String,
data: String, data: String,
) = onPushResult( ) = onPushResult(
@@ -65,7 +65,7 @@ fun PushHistoryService.onInvalidPushReceived(
comment = "Invalid or ignored push data:\n$data", comment = "Invalid or ignored push data:\n$data",
) )
fun PushHistoryService.onUnableToRetrieveSession( suspend fun PushHistoryService.onUnableToRetrieveSession(
providerInfo: String, providerInfo: String,
eventId: EventId, eventId: EventId,
roomId: RoomId, roomId: RoomId,
@@ -80,7 +80,7 @@ fun PushHistoryService.onUnableToRetrieveSession(
comment = "Unable to retrieve session: $reason", comment = "Unable to retrieve session: $reason",
) )
fun PushHistoryService.onUnableToResolveEvent( suspend fun PushHistoryService.onUnableToResolveEvent(
providerInfo: String, providerInfo: String,
eventId: EventId, eventId: EventId,
roomId: RoomId, roomId: RoomId,
@@ -96,7 +96,7 @@ fun PushHistoryService.onUnableToResolveEvent(
comment = "Unable to resolve event: $reason", comment = "Unable to resolve event: $reason",
) )
fun PushHistoryService.onSuccess( suspend fun PushHistoryService.onSuccess(
providerInfo: String, providerInfo: String,
eventId: EventId, eventId: EventId,
roomId: RoomId, roomId: RoomId,
@@ -117,7 +117,7 @@ fun PushHistoryService.onSuccess(
}, },
) )
fun PushHistoryService.onDiagnosticPush( suspend fun PushHistoryService.onDiagnosticPush(
providerInfo: String, providerInfo: String,
) = onPushResult( ) = onPushResult(
providerInfo = providerInfo, providerInfo = providerInfo,
@@ -30,7 +30,7 @@ class FakePushHistoryService(
private val getPendingPushRequests: (SessionId, Instant?) -> Result<List<PushRequest>> = { _, _ -> lambdaError() }, private val getPendingPushRequests: (SessionId, Instant?) -> Result<List<PushRequest>> = { _, _ -> lambdaError() },
private val removeOldPushRequests: (SessionId) -> Result<Unit> = { lambdaError() }, private val removeOldPushRequests: (SessionId) -> Result<Unit> = { lambdaError() },
) : PushHistoryService { ) : PushHistoryService {
override fun onPushResult( override suspend fun onPushResult(
providerInfo: String, providerInfo: String,
eventId: EventId?, eventId: EventId?,
roomId: RoomId?, roomId: RoomId?,