https://kotlinlang.org logo
#coroutines
Title
# coroutines
d

Daniel Asher

12/10/2019, 5:37 AM
Hi, I’m new to kotlin and coroutines, coming from RxFeedback on Swift/iOS and really excited about the language, platform and tooling. But I’m having some problems understanding exception handling. I’d like to have an event bus and state machine for a uniflow architecture and am used to wrapping this in a nice interface for clients. The reducer function throws an exception if the event in invalid and I’d like to give the client the option of handling this. It looks to me that I need a
SupervisorJob
and to
launch
a new coroutine for every transition so as not to have parent coroutines cancelled, but when I’m calling the API, I can’t quite figure out how to get the exception back to the client. Here’s some code, apologies for any conceptual errors on my part:
Copy code
class CoroutineExceptionTest: BehaviorSpec() {

    val supervisorJob = SupervisorJob()
    val coroutineScope: CoroutineScope = CoroutineScope(newSingleThreadContext("serviceContext") + supervisorJob)
    class InvalidEventException(override val message: String): Exception(message)
    fun tryTransition(state: Int, event: Int): Int {
        return when (event >= state) {
            true -> event + state
            false -> throw InvalidEventException("Argument $event is invalid")
        }
    }
    val stateChannel = ConflatedBroadcastChannel(0)
    val currentState: Int get() = stateChannel.value

    var eventChannel = coroutineScope.actor<Int>() {
        for (event in channel) {
            if (coroutineScope.isActive) {
                launch {
                    val newState = tryTransition(currentState, event)
                    stateChannel.send(newState)
                }
            }
        }
    }

    suspend fun add(number: Int): Int { // HOW DO I GET EXCEPTION THROWN HERE?
        eventChannel.send(number)
        return stateChannel.value
    }

    init {
        Given("we are in initial state") {
            currentState shouldBe 0
            When("adding 2") {
                add(2)
                Then("the state should be 2") {
                    currentState shouldBe 2
                }
            }
            When("trying to add 1") {
                Then("we should get an invalid event exception") {
                    val exception = shouldThrow<InvalidEventException> {
                        add(1) // THIS IS MY PROBLEM: java.lang.AssertionError: Expected exception CoroutineExceptionTest.InvalidEventException but no exception was thrown.
                    }
                }
            }
        }
    }
}
I’m guessing my misconception was that
Channel
supported a
next* (error | completed)
grammar, but it looks like the grammar is
next* completed
so error propagation is handled manually. I’ll take a look at
Flow
. The reason I chose
Channel
and
ConflatedBroadcastChannel
was I was trying to replicate
BehaviorSubject
from the
Rx
world.
2 Views