KStateMachine / kstatemachine

KStateMachine is a Kotlin DSL library for creating state machines and statecharts.
https://kstatemachine.github.io/kstatemachine/
Boost Software License 1.0
339 stars 19 forks source link

exportToPlantUml throws "JobCancellationException: ScopeCoroutine has completed normally;" #66

Closed gepz closed 1 year ago

gepz commented 1 year ago
    @OptIn(ExperimentalCoroutinesApi::class)
    @Test
    fun testUML() = runTest {
        val test = makeUML() // throws "JobCancellationException: ScopeCoroutine has completed normally;"
        assert(test != "")
    }

    suspend fun makeUML() = coroutineScope {
        val m = makeM()
        val uml = m.exportToPlantUml()
        return@coroutineScope uml
    }

    suspend fun makeM() = coroutineScope {
        return@coroutineScope createStateMachine(this) {
            setInitialState(state {  })
        }
    }

The above JUnit5 test testUML will throw JobCancellationException for some reason. It wouldn't if I put val uml = m.exportToPlantUml() inside makeM though. I'm corrently using the legacy non-coroutine method as the workaround

nsk90 commented 1 year ago

I think the reason is that scope created here suspend fun makeM() = coroutineScope { and used in state machine here createStateMachine(this) is already cancelled when you call m.exportToPlantUml().

Check this version, please.

gepz commented 1 year ago

Does the scope used by the factory function have to exist throughout the entire life time of the state machine? If so, how can I save the machine as a variable?

nsk90 commented 1 year ago

Yes life time of a scope should be equal or longer then machine lifetime. There is no problem with storing machine as variable. Just create your couroutine scope or use standard one (like GlobalScope). The reason of your problem is that you are using coroutineScope function which is not designed for scope creation, but for parallel decomposition of work.

https://kt.academy/article/cc-constructing-scope here you can find samples of scope creation that I mean.

Actually to call coroutineScope you already need to have a scope, you have created it by runTest block in your sample.

gepz commented 1 year ago

I see, thanks for the info.