Skip to content

Commit

Permalink
KTOR-7734 Do not abort fetch after its completion (#4629)
Browse files Browse the repository at this point in the history
  • Loading branch information
osipxd authored Jan 28, 2025
1 parent 2efc770 commit 5ff1a3c
Show file tree
Hide file tree
Showing 4 changed files with 44 additions and 33 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2014-2019 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
* Copyright 2014-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
*/

package io.ktor.client.engine.js
Expand All @@ -16,9 +16,11 @@ import io.ktor.util.*
import io.ktor.util.date.*
import io.ktor.utils.io.*
import kotlinx.coroutines.*
import org.w3c.dom.*
import org.w3c.dom.events.*
import kotlin.coroutines.*
import org.w3c.dom.WebSocket
import org.w3c.dom.events.Event
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
import kotlin.js.Promise

internal class JsClientEngine(
Expand All @@ -31,7 +33,7 @@ internal class JsClientEngine(
check(config.proxy == null) { "Proxy unsupported in Js engine." }
}

@OptIn(InternalAPI::class, InternalCoroutinesApi::class)
@OptIn(InternalAPI::class)
override suspend fun execute(data: HttpRequestData): HttpResponseData {
val callContext = callContext()
val clientConfig = data.attributes[CLIENT_CONFIG]
Expand All @@ -42,14 +44,7 @@ internal class JsClientEngine(

val requestTime = GMTDate()
val rawRequest = data.toRaw(clientConfig, callContext)

val controller = AbortController()
rawRequest.signal = controller.signal
callContext.job.invokeOnCompletion(onCancelling = true) {
controller.abort()
}

val rawResponse = commonFetch(data.url.toString(), rawRequest, config)
val rawResponse = commonFetch(data.url.toString(), rawRequest, config, callContext.job)

val status = HttpStatusCode(rawResponse.status.toInt(), rawResponse.statusText)
val headers = rawResponse.headers.mapToKtor(data.method, data.attributes)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2014-2019 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
* Copyright 2014-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
*/

package io.ktor.client.engine.js.compatibility
Expand All @@ -9,14 +9,26 @@ import io.ktor.client.engine.js.browser.*
import io.ktor.client.fetch.*
import io.ktor.util.*
import io.ktor.utils.io.*
import kotlinx.coroutines.*
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.InternalCoroutinesApi
import kotlinx.coroutines.Job
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlin.js.Promise

@OptIn(InternalCoroutinesApi::class)
internal suspend fun commonFetch(
input: String,
init: RequestInit,
config: JsClientEngineConfig,
callJob: Job,
): org.w3c.fetch.Response = suspendCancellableCoroutine { continuation ->
val controller = AbortController()
init.signal = controller.signal

val abortOnCallCompletion = callJob.invokeOnCompletion(onCancelling = true) {
controller.abort()
}

val promise: Promise<org.w3c.fetch.Response> = when {
PlatformUtils.IS_BROWSER -> fetch(input, init)
else -> {
Expand All @@ -32,12 +44,10 @@ internal suspend fun commonFetch(
onRejected = {
continuation.resumeWith(Result.failure(Error("Fail to fetch", it)))
}
)
).finally { abortOnCallCompletion.dispose() }
}

internal fun AbortController(): AbortController {
return js("new AbortController()")
}
private fun AbortController(): AbortController = js("new AbortController()")

internal fun CoroutineScope.readBody(
response: org.w3c.fetch.Response
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2014-2023 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
* Copyright 2014-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
*/

package io.ktor.client.engine.js
Expand Down Expand Up @@ -56,13 +56,8 @@ internal class JsClientEngine(

val requestTime = GMTDate()
val rawRequest = data.toRaw(clientConfig, callContext)
val controller = AbortController()
rawRequest.signal = controller.signal
callContext.job.invokeOnCompletion(onCancelling = true) {
controller.abort()
}
val rawResponse = commonFetch(data.url.toString(), rawRequest, config, callContext.job)

val rawResponse = commonFetch(data.url.toString(), rawRequest, config)
val status = HttpStatusCode(rawResponse.status.toInt(), rawResponse.statusText)
val headers = rawResponse.headers.mapToKtor(data.method, data.attributes)
val version = HttpProtocolVersion.HTTP_1_1
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2014-2023 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
* Copyright 2014-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
*/

package io.ktor.client.engine.js.compatibility
Expand All @@ -10,14 +10,26 @@ import io.ktor.client.fetch.*
import io.ktor.client.utils.*
import io.ktor.util.*
import io.ktor.utils.io.*
import kotlinx.coroutines.*
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.InternalCoroutinesApi
import kotlinx.coroutines.Job
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlin.js.Promise

@OptIn(InternalCoroutinesApi::class)
internal suspend fun commonFetch(
input: String,
init: RequestInit,
config: JsClientEngineConfig,
callJob: Job,
): org.w3c.fetch.Response = suspendCancellableCoroutine { continuation ->
val controller = AbortController()
init.signal = controller.signal

val abortOnCallCompletion = callJob.invokeOnCompletion(onCancelling = true) {
controller.abort()
}

val promise: Promise<org.w3c.fetch.Response> = when {
PlatformUtils.IS_BROWSER -> fetch(input, init)
else -> {
Expand All @@ -40,17 +52,16 @@ internal suspend fun commonFetch(
continuation.resumeWith(Result.failure(Error("Fail to fetch", JsError(it))))
null
}
)
).finally { abortOnCallCompletion.dispose() }
}

private fun abortControllerCtorBrowser(): AbortController =
js("AbortController")

internal fun AbortController(): AbortController {
private fun AbortController(): AbortController {
val ctor = abortControllerCtorBrowser()
return makeJsNew(ctor)
}

private fun abortControllerCtorBrowser(): AbortController = js("AbortController")

internal fun CoroutineScope.readBody(
response: org.w3c.fetch.Response
): ByteReadChannel =
Expand Down

0 comments on commit 5ff1a3c

Please sign in to comment.