Repository: ktorio/ktor
File: ktor-client/ktor-client-cio/jvmAndPosix/src/io/ktor/client/engine/cio/Endpoint.kt (Kotlin)

Code before:
    private fun getTimeoutException(
        connectAttempts: Int,
        timeoutFails: Int,
        request: HttpRequestData
    ): Exception = when (timeoutFails) {
        connectAttempts -> ConnectTimeoutException(request)
        else -> FailToConnectException()
    }

    /**
     * Takes timeout attributes from [config] and [HttpTimeout.HttpTimeoutCapabilityConfiguration] and returns a pair of
     * connection timeout and socket timeout to be applied.
     */
    private fun retrieveTimeouts(requestData: HttpRequestData): Pair<Long, Long> {
        val default = config.endpoint.connectTimeout to config.endpoint.socketTimeout
        val timeoutAttributes = requestData.getCapabilityOrNull(HttpTimeoutCapability)
            ?: return default

        val socketTimeout = timeoutAttributes.socketTimeoutMillis ?: config.endpoint.socketTimeout
        val connectTimeout = timeoutAttributes.connectTimeoutMillis ?: config.endpoint.connectTimeout
        return connectTimeout to socketTimeout
    }

    private fun releaseConnection() {
        val address = lastResolvedAddress ?: return
        connectionFactory.release(address)
        connections.decrementAndGet()
    }

    override fun close() {
        timeout.cancel()
    }

    companion object {
        const val CONTINUE_RESPONSE_TIMEOUT_MILLIS = 1000L
    }
}

@OptIn(DelicateCoroutinesApi::class)
private fun setupTimeout(callContext: CoroutineContext, request: HttpRequestData, timeout: Long) {
    if (timeout == HttpTimeoutConfig.INFINITE_TIMEOUT_MS || timeout == 0L) return

    val timeoutJob = GlobalScope.launch {
        delay(timeout)
        callContext.job.cancel("Request is timed out", HttpRequestTimeoutException(request))
    }

    callContext.job.invokeOnCompletion {
        timeoutJob.cancel()
    }
}

Review comment (suggestion):
Not sure if `lastResolvedAddress` can be `null` normally. It would mean that we're trying to release a connection that is not open. If it is not possible, we probably should throw an exception here

Code after:
     */
    private fun getTimeoutException(
        connectAttempts: Int,
        timeoutFails: Int,
        request: HttpRequestData
    ): Exception = when (timeoutFails) {
        connectAttempts -> ConnectTimeoutException(request)
        else -> FailToConnectException()
    }

    /**
     * Takes timeout attributes from [config] and [HttpTimeout.HttpTimeoutCapabilityConfiguration] and returns a pair of
     * connection timeout and socket timeout to be applied.
     */
    private fun retrieveTimeouts(requestData: HttpRequestData): Pair<Long, Long> {
        val default = config.endpoint.connectTimeout to config.endpoint.socketTimeout
        val timeoutAttributes = requestData.getCapabilityOrNull(HttpTimeoutCapability)
            ?: return default

        val socketTimeout = timeoutAttributes.socketTimeoutMillis ?: config.endpoint.socketTimeout
        val connectTimeout = timeoutAttributes.connectTimeoutMillis ?: config.endpoint.connectTimeout
        return connectTimeout to socketTimeout
    }

    private fun releaseConnection() {
        val address = connectionAddress ?: return
        connectionFactory.release(address)
        connections.decrementAndGet()
    }

    override fun close() {
        timeout.cancel()
    }

    companion object {
        const val CONTINUE_RESPONSE_TIMEOUT_MILLIS = 1000L
    }
}

@OptIn(DelicateCoroutinesApi::class)
private fun setupTimeout(callContext: CoroutineContext, request: HttpRequestData, timeout: Long) {
    if (timeout == HttpTimeoutConfig.INFINITE_TIMEOUT_MS || timeout == 0L) return

    val timeoutJob = GlobalScope.launch {
        delay(timeout)
        callContext.job.cancel("Request is timed out", HttpRequestTimeoutException(request))
    }

    callContext.job.invokeOnCompletion {
        timeoutJob.cancel()
    }