Move blocking work off the main thread and fix threading races (#4068)

* Defer startup work until first use

* Move cover work off the main thread

* Make tracker calls main-safe and token refresh thread-safe

Kitsu, Shikimori and MangaBaka refresh their token under a lock like
MAL, so parallel calls no longer refresh it twice.

* Fix extension threading and make installs cancellable

The store service read response bodies on Main, which failed with
NetworkOnMainThreadException for http stores.

* Move reader work off the main thread and save progress in order

* Serialize the downloader and fix download threading

* Stop blocking on WorkManager status queries

Also removes NotificationReceiver's open chapter action: nothing sends
it, and its handler used runBlocking.

* Start migration jobs on the migrator's dispatcher

They started undispatched, so each ran on the caller's thread until its
first suspension.

* Run screen and view model work on fitting dispatchers

* Launch work that outlives a screen on the app scope

A launch on another dispatcher is dropped if the screen closes before it
starts, which dialogs that dismiss right away often do. Only the UI
follow-up stays on viewModelScope.

* Remove the coroutine helper extensions

They hid which dispatcher and scope a call used, and the GlobalScope
ones hid that it was global.
This commit is contained in:
AntsyLich
2026-10-10 17:08:49 +00:00
committed by GitHub
parent f9c5fa9011
commit c46317f099
131 changed files with 1612 additions and 1274 deletions
@@ -22,7 +22,6 @@ import androidx.glance.layout.padding
import androidx.glance.unit.ColorProvider
import coil3.annotation.ExperimentalCoilApi
import coil3.asDrawable
import coil3.executeBlocking
import coil3.imageLoader
import coil3.request.CachePolicy
import coil3.request.ImageRequest
@@ -34,13 +33,14 @@ import dev.zacsweers.metro.HasMemberInjections
import dev.zacsweers.metro.Inject
import eu.kanade.tachiyomi.core.security.SecurityPreferences
import eu.kanade.tachiyomi.util.system.dpToPx
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.withContext
import kotlinx.datetime.DateTimeUnit
import kotlinx.datetime.TimeZone
import kotlinx.datetime.minus
import mihon.core.metro.metroGraph
import mihon.presentation.widget.di.PresentationWidgetGraph
import tachiyomi.core.common.util.lang.withIOContext
import tachiyomi.domain.manga.model.MangaCover
import tachiyomi.domain.updates.interactor.GetUpdates
import tachiyomi.domain.updates.model.UpdatesWithRelations
@@ -122,7 +122,7 @@ abstract class BaseUpdatesGridGlanceWidget : GlanceAppWidget() {
val widthPx = CoverWidth.value.toInt().dpToPx
val heightPx = CoverHeight.value.toInt().dpToPx
val roundPx = context.resources.getDimension(R.dimen.appwidget_inner_radius)
return withIOContext {
return withContext(Dispatchers.Default) {
this@prepareData
.distinctBy { it.mangaId }
.take(rowCount * columnCount)
@@ -149,7 +149,7 @@ abstract class BaseUpdatesGridGlanceWidget : GlanceAppWidget() {
}
}
.build()
val bitmap = context.imageLoader.executeBlocking(request)
val bitmap = context.imageLoader.execute(request)
.image
?.asDrawable(context.resources)
?.toBitmap()