mirror of
https://github.com/mihonapp/mihon.git
synced 2026-10-20 07:32:28 -07:00
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:
@@ -7,7 +7,7 @@ import okhttp3.HttpUrl
|
||||
|
||||
class AndroidCookieJar : CookieJar {
|
||||
|
||||
private val manager = CookieManager.getInstance()
|
||||
private val manager by lazy { CookieManager.getInstance() }
|
||||
|
||||
override fun saveFromResponse(url: HttpUrl, cookies: List<Cookie>) {
|
||||
val urlString = url.toString()
|
||||
|
||||
@@ -5,7 +5,8 @@ import app.cash.quickjs.QuickJs
|
||||
import dev.zacsweers.metro.AppScope
|
||||
import dev.zacsweers.metro.Inject
|
||||
import dev.zacsweers.metro.SingleIn
|
||||
import tachiyomi.core.common.util.lang.withIOContext
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
/**
|
||||
* Util for evaluating JavaScript in sources.
|
||||
@@ -23,7 +24,7 @@ class JavaScriptEngine(context: Context) {
|
||||
* @param script JavaScript to execute.
|
||||
* @return Result of JavaScript code as a primitive type.
|
||||
*/
|
||||
suspend fun <T> evaluate(script: String): T = withIOContext {
|
||||
suspend fun <T> evaluate(script: String): T = withContext(Dispatchers.Default) {
|
||||
QuickJs.create().use {
|
||||
it.evaluate(script) as T
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ class NetworkHelper(
|
||||
|
||||
val cookieJar = AndroidCookieJar()
|
||||
|
||||
private val clientBuilder: OkHttpClient.Builder = run {
|
||||
private fun clientBuilder(): OkHttpClient.Builder = run {
|
||||
val builder = OkHttpClient.Builder()
|
||||
.cookieJar(cookieJar)
|
||||
.connectTimeout(30.seconds)
|
||||
@@ -65,18 +65,20 @@ class NetworkHelper(
|
||||
}
|
||||
}
|
||||
|
||||
val client = clientBuilder
|
||||
.addInterceptor(
|
||||
CloudflareInterceptor(context, cookieJar, scope, ::defaultUserAgentProvider),
|
||||
)
|
||||
.build()
|
||||
val client: OkHttpClient by lazy {
|
||||
clientBuilder()
|
||||
.addInterceptor(
|
||||
CloudflareInterceptor(context, cookieJar, scope, ::defaultUserAgentProvider),
|
||||
)
|
||||
.build()
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Since extension-lib 1.5
|
||||
*/
|
||||
@Deprecated("The regular client handles Cloudflare by default")
|
||||
@Suppress("UNUSED")
|
||||
val cloudflareClient: OkHttpClient = client
|
||||
val cloudflareClient: OkHttpClient get() = client
|
||||
|
||||
fun defaultUserAgentProvider() = preferences.defaultUserAgent.get().trim()
|
||||
}
|
||||
|
||||
+3
-2
@@ -11,11 +11,12 @@ import eu.kanade.tachiyomi.util.system.setDefaultSettings
|
||||
import eu.kanade.tachiyomi.util.system.setUserAgent
|
||||
import eu.kanade.tachiyomi.util.system.toast
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import okhttp3.Headers
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
import tachiyomi.core.common.util.lang.launchUI
|
||||
import tachiyomi.i18n.MR
|
||||
import java.util.Locale
|
||||
import java.util.concurrent.CountDownLatch
|
||||
@@ -59,7 +60,7 @@ abstract class WebViewInterceptor(
|
||||
}
|
||||
|
||||
if (!WebViewUtil.supportsWebView(context)) {
|
||||
scope.launchUI {
|
||||
scope.launch(Dispatchers.Main) {
|
||||
context.toast(MR.strings.information_webview_required, Toast.LENGTH_LONG)
|
||||
}
|
||||
return response
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
package tachiyomi.core.common.util.lang
|
||||
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.NonCancellable
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
fun CoroutineScope.launchUI(block: suspend CoroutineScope.() -> Unit): Job =
|
||||
launch(Dispatchers.Main, block = block)
|
||||
|
||||
fun CoroutineScope.launchIO(block: suspend CoroutineScope.() -> Unit): Job =
|
||||
launch(Dispatchers.IO, block = block)
|
||||
|
||||
fun CoroutineScope.launchNonCancellable(block: suspend CoroutineScope.() -> Unit): Job =
|
||||
launchIO { withContext(NonCancellable, block) }
|
||||
|
||||
suspend fun <T> withUIContext(block: suspend CoroutineScope.() -> T) = withContext(
|
||||
Dispatchers.Main,
|
||||
block,
|
||||
)
|
||||
|
||||
suspend fun <T> withIOContext(block: suspend CoroutineScope.() -> T) = withContext(
|
||||
Dispatchers.IO,
|
||||
block,
|
||||
)
|
||||
|
||||
suspend fun <T> withNonCancellableContext(block: suspend CoroutineScope.() -> T) =
|
||||
withContext(NonCancellable, block)
|
||||
Reference in New Issue
Block a user