Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ClosedSendChannelException confusing (actor should report failures) #422

Closed
Khang-NT opened this issue Jul 5, 2018 · 4 comments
Closed
Labels

Comments

@Khang-NT
Copy link

Khang-NT commented Jul 5, 2018

Lets see following snippet code:

val channelA = Channel<Int>(Channel.UNLIMITED)
channelA.offer(0) // just offer some items

val channelB = Channel<Int>(Channel.UNLIMITED)
channelB.close()

launch {
    channelA.consumeEach {
        channelB.offer(0) // throws ClosedSendChannelException here
    }
}

Imagine what happen?

With my vision when read this code, I will think that the program should crash because channelB was closed but I invoke offer().

But It didn't crash. Because ClosedSendChannelException is swallowed by channelA.iterator().

It is kind of bug.

@elizarov
Copy link
Contributor

elizarov commented Jul 9, 2018

@Khang-NT How are you running this code? It looks like the exception is swalled by launch or, actually, but the fact that whatever test code you wrote around terminates even before launch has a chance to execute. Here I rewrote it with runBlocking (as it should be):

fun main(args: Array<String>) = runBlocking {
    val channelA = Channel<Int>(Channel.UNLIMITED)
    channelA.offer(0) // just offer some items

    val channelB = Channel<Int>(Channel.UNLIMITED)
    channelB.close()

    channelA.consumeEach {
        channelB.offer(0) // throws ClosedSendChannelException here
    }
}

And running this code I clearly see ClosedSendChannelException. Does it help?

@Khang-NT
Copy link
Author

Khang-NT commented Jul 9, 2018

Hi @elizarov, you are correct. My assumption was wrong, it's swallowed by launch context instead, seem like because ClosedSendChannelException extends CancellationException.
Now I don't say it is bug anymore, but still very confusing, I troubled to find root cause of a bug in my app some days ago because of this behavior. Let me show the code I made:

val myActor = actor<Int> {
    throws RuntimeException(); 
    // an exception happen here, it causes the actor channel to be closed
};

fun doSomeWork() {
    // here is very complicated
    // some like call library method -> library callback 
    // -> invoke offer() on this callback (all are synchronously).
    myActor.offer(0);
}

val aChannel = Channel<Int>(CONFLATED)
launch(myCoroutineContext) {
    aChannel.consumeEach {
        doSomeWork()
    }
}

fun onSomeEvent() {
    aChannel.offer(0); 
    // my app crashed here: aChannel was closed for send
}

What you will do to debug?

I made a lot of wrongs assumption, and only find out the line myActor.offer(0) after debug line by line.
My actually code even worse:

val myActor = actor<Int> {
    consumeEach {
         throw MyException("useful stacktrace");
    }
}

myActor is closed with cause = null? Because consumeEach closed actor channel by itself :(
Then I need to debug again.

Maybe this is my bad, cuz I very doubt about how exception was propagated.

@elizarov
Copy link
Contributor

That is actually a bug. actors should be launch-like (and we planning to fix it, cc @qwwdfsad) and when your actor had crashed with exception this exception should have printed on the console and it would have made it much easier to debug.

@elizarov elizarov changed the title ClosedSendChannelException confusing ClosedSendChannelException confusing (actor should report failures) Jul 10, 2018
@qwwdfsad
Copy link
Contributor

Actors already behave like launch (#368)
In this particular case exception is properly printed:

val myActor = actor<Int> {
    consumeEach {
        throw RuntimeException("useful stacktrace")
    }
}

fun main(args: Array<String>) {
    val actor = myActor
    Thread.sleep(100)
    actor.offer(1)
    Thread.sleep(100)
}

Note that it was fixed in latest release (#368), so it's worth to update kotlinx.coroutines version.
Nevertheless, interference between channel and actor body is not obvious (and causes bugs like #368), especially with patterns like consumeEach, so new actors (#87) won't expose channel to users.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants