Coder Social home page Coder Social logo

Comments (10)

gregschlom avatar gregschlom commented on September 23, 2024 1

@nkiesel

Why is nested2(1, 2, 3) not returning "outer timeout"?

  • The outer timeout fires at 1 second, and makes delay(3) resume with a CancellationException
  • The inner withTimeoutOrNull catches the exception, concludes that it timed out, returns null which gets converted to the string "inner timeout"
  • The block in outer timeout completes with a valid value ("inner timeout"), has no idea it actually timed out because the CancellationException was swallowed by the inner withTimeoutOrNull

For the other cases:

  • test(1, 3, 2) is identical to test(1, 2, 3) - outer times out while suspended in the delay()
  • test(2, 1, 3): inner times out first so you get "inner timeout" as expected (ie: behavior is correct)
  • test(2, 3, 1): delay is lower than time outs, so you get the correct behavior
  • test(3, 1, 2): inner times out first, so you get the correct behavior
  • test(3, 2, 1): delay is lower than time outs, so you get the correct behavior

For reference, the output of the test above is:

test(1, 2, 3) = { 1: inner timeout, 2: inner timeout }
test(1, 3, 2) = { 1: inner timeout, 2: inner timeout }
test(2, 1, 3) = { 1: inner timeout, 2: inner timeout }
test(2, 3, 1) = { 1: delay, 2: delay }
test(3, 1, 2) = { 1: inner timeout, 2: inner timeout }
test(3, 2, 1) = { 1: delay, 2: delay }

from kotlinx.coroutines.

gregschlom avatar gregschlom commented on September 23, 2024

Modifiying nextValueWithTimout like so:

    suspend fun nextValueWithTimout(t: Long): Int? {
        val result = withTimeoutOrNull(t) { nexValue() }
        yield()
        return result 
    }

Fixes the issue for me, but Iā€™m not sure I fully understand the semantics of yield. Is this logically correct? Iā€™m worried that the call to yield() might suspend my coroutine for an undetermined amount of time.

from kotlinx.coroutines.

elizarov avatar elizarov commented on September 23, 2024

Yield just reschedules the task and checks for cancellation. That is why your particular problem is fixed. It does not address the core issue itself. The root questions here is what semantics should withTimeoutOrNull have when some other withTimeout invocation (either inside or outside of it) times out.

from kotlinx.coroutines.

elizarov avatar elizarov commented on September 23, 2024

I'm personally inclined to rule that withTimeoutOrNull shall return null only if its own timeout is exceeded and change its implementation accordingly.

from kotlinx.coroutines.

gregschlom avatar gregschlom commented on September 23, 2024

And same goes for withTimeout, right? Should return null only if its own timeout fires.

from kotlinx.coroutines.

elizarov avatar elizarov commented on September 23, 2024

There is no problem with withTimeout. It throws an exception on timeout and always rethrows an inner exception if any. It cannot/should not consume or alter inner exception in any way, so withTimeout does not provide any way to distinguish which timeout had fired and it has to stay this way.

from kotlinx.coroutines.

gregschlom avatar gregschlom commented on September 23, 2024

I see. I originally encountered this problem because I wasn't aware of withTimeoutOrNull (it looks like it's a recent addition) and was doing things this way:

suspend fun nextValueWithTimout(t: Long): Int? {
    try {
        return withTimeout(t) { nexValue() }
    catch (e: CancellationException) {
        return null
    }
}

I understand they might be different use cases, but from a user point of view this way of doing things also looks pretty innocent at first. Hard to see that you're shooting yourself in the foot here.

I wonder if there's a way to make it harder for users to do stupid things like this.

from kotlinx.coroutines.

nkiesel avatar nkiesel commented on September 23, 2024

Another test program. I'm surprised that both nested1 and nested2 print the same value for all permutations. Why is nested2(1, 2, 3) not returning "outer timeout"?

import kotlinx.coroutines.experimental.*
import java.util.concurrent.TimeUnit

val S = TimeUnit.SECONDS

suspend fun <T> withTimeoutOrNull2(time: Long, unit: TimeUnit, block: suspend () -> T): T? = try {
    withTimeout(time, unit) {
        block()
    }
} catch (_: CancellationException) { null }

suspend fun nested1(outer: Long, inner: Long, delayed: Long) =
        withTimeoutOrNull(outer, S) {
            withTimeoutOrNull(inner, S) {
                delay(delayed, S)
                "delay"
            } ?: "inner timeout"
        } ?: "outer timeout"

suspend fun nested2(outer: Long, inner: Long, delayed: Long) =
        withTimeoutOrNull2(outer, S) {
            withTimeoutOrNull2(inner, S) {
                delay(delayed, S)
                "delay"
            } ?: "inner timeout"
        } ?: "outer timeout"

suspend fun test(outer: Long, inner: Long, delayed: Long) {
    val result1 = nested1(outer, inner, delayed)
    val result2 = nested2(outer, inner, delayed)
    println("test($outer, $inner, $delayed) = { 1: $result1, 2: $result2 }")
}

fun main(args: Array<String>) = runBlocking {
    test(1, 2, 3)
    test(1, 3, 2)
    test(2, 1, 3)
    test(2, 3, 1)
    test(3, 1, 2)
    test(3, 2, 1)
}

from kotlinx.coroutines.

gregschlom avatar gregschlom commented on September 23, 2024

FYI, here's my workaround to the problem:

    /**
     * Throws if the current coroutine is completed or cancelled
     */
    suspend fun checkStillActive() {
        suspendCoroutine<Unit> { cont ->
            val job = cont.context[Job]
            if (job?.isCompleted == true) throw job.getCompletionException()
            cont.resume(Unit)
        }
    }

The call checkStillActive() after each call to withTimeout[OrNull], like so:

    suspend fun nested1(outer: Long, inner: Long, delayed: Long) =
            withTimeoutOrNull(outer, S) {
                val result = withTimeoutOrNull(inner, S) {
                    delay(delayed, S)
                    "delay"
                } ?: "inner timeout"
                checkStillActive()  // <---- here
                result
            } ?: "outer timeout"

And:

    suspend fun <T> withTimeoutOrNull2(time: Long, unit: TimeUnit, block: suspend () -> T): T? = try {
        withTimeout(time, unit) {
            block()
        }
    } catch (_: CancellationException) {
        checkStillActive()  // <---- here
        null
    }

This gives the correct results:

test(1, 2, 3) = { 1: outer timeout, 2: outer timeout }
test(1, 3, 2) = { 1: outer timeout, 2: outer timeout }
test(2, 1, 3) = { 1: inner timeout, 2: inner timeout }
test(2, 3, 1) = { 1: delay, 2: delay }
test(3, 1, 2) = { 1: inner timeout, 2: inner timeout }
test(3, 2, 1) = { 1: delay, 2: delay }

from kotlinx.coroutines.

elizarov avatar elizarov commented on September 23, 2024

Fixed in develop branch

from kotlinx.coroutines.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    šŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. šŸ“ŠšŸ“ˆšŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ā¤ļø Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.