Apply watchdog before send request (#2056)

* Apply watchdog before send request

* Cleanup CronetEngine

---------

Co-authored-by: Dude so hot <djohn@fbi.gov>
This commit is contained in:
renovate[bot]andDude so hot authored and GitHub committed 2024-12-31 17:55:39 +08:00
1 parent b7948f7965
commit bfa91bf2fb
5 files changed
+36 -36

No files matched your search

@@ -1,11 +1,11 @@
package com.hippo.ehviewer.ktor
import com.hippo.ehviewer.BuildConfig
import com.hippo.ehviewer.Settings
import com.hippo.ehviewer.client.EhCookieStore
import io.ktor.client.HttpClientConfig
import io.ktor.client.engine.HttpClientEngineConfig
import io.ktor.client.plugins.HttpTimeout
import io.ktor.client.plugins.HttpTimeoutConfig
import io.ktor.client.plugins.SaveBodyPlugin
import io.ktor.client.plugins.UserAgent
import io.ktor.client.plugins.cookies.HttpCookies
@@ -22,7 +22,7 @@ fun <T : HttpClientEngineConfig> HttpClientConfig<T>.configureCommon(redirect: B
storage = EhCookieStore
}
install(HttpTimeout) {
connectTimeoutMillis = Settings.connTimeout * 1000L
reset()
requestTimeoutMillis = 10_000
}
install(UserAgent) {
@@ -37,3 +37,9 @@ fun <T : HttpClientEngineConfig> HttpClientConfig<T>.configureCommon(redirect: B
disabled = true
}
}
fun HttpTimeoutConfig.reset() = apply {
requestTimeoutMillis = HttpTimeoutConfig.INFINITE_TIMEOUT_MS
connectTimeoutMillis = HttpTimeoutConfig.INFINITE_TIMEOUT_MS
socketTimeoutMillis = HttpTimeoutConfig.INFINITE_TIMEOUT_MS
}
@@ -14,7 +14,6 @@ import com.hippo.ehviewer.Settings
import com.hippo.ehviewer.util.restartApplication
import io.ktor.client.engine.HttpClientEngineBase
import io.ktor.client.engine.callContext
import io.ktor.client.plugins.ConnectTimeoutException
import io.ktor.client.plugins.HttpTimeoutCapability
import io.ktor.client.request.HttpRequestData
import io.ktor.client.request.HttpResponseData
@@ -35,14 +34,12 @@ import java.nio.ByteBuffer
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.asExecutor
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.channels.consumeEach
import kotlinx.coroutines.job
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.withTimeoutOrNull
import splitties.preferences.edit
class CronetEngine(override val config: CronetConfig) : HttpClientEngineBase("Cronet") {
@@ -58,12 +55,7 @@ class CronetEngine(override val config: CronetConfig) : HttpClientEngineBase("Cr
}
@InternalAPI
override suspend fun execute(data: HttpRequestData) = data.getCapabilityOrNull(HttpTimeoutCapability)!!.let { cfg ->
val connTimeout = cfg.connectTimeoutMillis!!
withTimeoutOrNull(connTimeout.milliseconds) {
executeHttpRequest(callContext(), data)
} ?: throw ConnectTimeoutException(data)
}
override suspend fun execute(data: HttpRequestData) = executeHttpRequest(callContext(), data)
private suspend fun executeHttpRequest(
callContext: CoroutineContext,
@@ -166,6 +166,7 @@ class SpiderDen(val info: GalleryInfo) {
referer: String?,
notifyProgress: (Long, Long, Int) -> Unit,
) = timeoutBySpeed(
url,
{ ehRequest(url, referer, builder = it) },
{ a, b, c -> notifyProgress(a, b, c) },
{ resp -> check(saveFromHttpResponse(index, resp)) },
@@ -5,19 +5,19 @@ import androidx.collection.mutableObjectIntMapOf
import arrow.fx.coroutines.fixedRate
import com.hippo.ehviewer.Settings
import com.hippo.ehviewer.client.executeSafely
import com.hippo.ehviewer.ktor.reset
import com.hippo.ehviewer.util.LowSpeedException
import com.hippo.ehviewer.util.ensureSuccess
import io.ktor.client.plugins.ConnectTimeoutException
import io.ktor.client.plugins.onDownload
import io.ktor.client.plugins.timeout
import io.ktor.client.request.HttpRequestBuilder
import io.ktor.client.statement.HttpResponse
import io.ktor.client.statement.HttpStatement
import io.ktor.client.statement.request
import kotlin.time.Duration
import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.Duration.Companion.seconds
import kotlin.time.TimeSource
import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.map
@@ -57,40 +57,40 @@ class SpeedTracker(val window: Duration = 1.seconds) {
}
suspend inline fun <R> timeoutBySpeed(
crossinline req: suspend (HttpRequestBuilder.() -> Unit) -> HttpStatement,
url: String,
crossinline request: suspend (HttpRequestBuilder.() -> Unit) -> HttpStatement,
crossinline l: suspend (Long, Long, Int) -> Unit,
crossinline f: suspend (HttpResponse) -> R,
) = with(SpeedTracker(2.seconds)) {
var prev = 0L
req {
) = coroutineScope {
val watchdog = launch {
delay(Settings.connTimeout.seconds)
throw ConnectTimeoutException(url, Settings.connTimeout * 1000L)
}
val tracker = SpeedTracker(2.seconds)
request {
var prev = 0L
onDownload { done, total ->
val bytesRead = (done - prev).toInt()
track(bytesRead)
tracker.track(bytesRead)
l(total!!, done, bytesRead)
prev = done
}
timeout { connectTimeoutMillis = Settings.connTimeout * 1000L }
timeout { reset() }
}.executeSafely { resp ->
watchdog.cancel()
resp.status.ensureSuccess()
val timeoutSpeed = Settings.timeoutSpeed * 1024.0
coroutineScope {
val work = async { f(resp) }
val job = launch {
delay(2.seconds) // Tolerant 4 secs for receiving
speedFlow(1.seconds).collect { speed ->
if (timeoutSpeed != 0.0 && speed < timeoutSpeed) {
throw LowSpeedException(
resp.request.url.toString(),
speed.toLong(),
)
}
val speedWatchdog = launch {
val timeoutSpeed = Settings.timeoutSpeed * 1024.0
tracker.speedFlow(1.seconds).collect { speed ->
if (timeoutSpeed != 0.0 && speed < timeoutSpeed) {
throw LowSpeedException(url, speed.toLong())
}
}
try {
work.await()
} finally {
job.cancel()
}
}
try {
f(resp)
} finally {
speedWatchdog.cancel()
}
}
}
@@ -69,6 +69,7 @@ object AppUpdater {
}
suspend fun downloadUpdate(url: String, path: Path) = timeoutBySpeed(
url,
{ ghStatement(url, builder = it) },
{ _, _, _ -> },
{ response ->