Skip to content

Commit 3ae53f5

Browse files
committed
Add coroutine tests and update docs
1 parent 49401c0 commit 3ae53f5

File tree

2 files changed

+57
-4
lines changed

2 files changed

+57
-4
lines changed

docs/index.md

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -707,8 +707,15 @@ Note that `Blocking` versions internally use `kotlinx.coroutines.runBlocking` fu
707707
may cause deadlocks if used not properly. That is why you should avoid using `Blocking` APIs from coroutines and
708708
recursively (from library callbacks).
709709

710-
Such suspendable functions preserve state machines coroutine context (using `kotlinx.coroutines.withContext`),
711-
so it should be ok to call them from any thread.
710+
When you create a state machine with `createStateMachine`/`createStateMachineBlocking` functions you have to provide
711+
`CoroutineScope` on which machine will work, this scope also contains `CoroutineContext` by design.
712+
This is how you can control a thread where state machine works.
713+
714+
Suspendable functions and their `Blocking` analogs internally switch current execution coroutine context
715+
(from which they are called) to state machines one using `kotlinx.coroutines.withContext` or
716+
`kotlinx.coroutines.runBlocking` arguments respectively.
717+
Note that if you created machine with a scope containing `kotlinx.coroutines.EmptyCoroutineContext` switching will not
718+
be performed.
712719

713720
### Migration guide from versions older than v0.20.0
714721

tests/src/test/kotlin/ru/nsk/kstatemachine/CoroutinesTest.kt

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ package ru.nsk.kstatemachine
22

33
import io.kotest.assertions.throwables.shouldThrow
44
import io.kotest.core.spec.style.StringSpec
5+
import io.kotest.matchers.shouldBe
56
import kotlinx.coroutines.*
6-
import java.lang.UnsupportedOperationException
77
import kotlin.coroutines.EmptyCoroutineContext
88

99
class CoroutinesTest : StringSpec({
@@ -50,7 +50,7 @@ class CoroutinesTest : StringSpec({
5050
shouldThrow<UnsupportedOperationException> {
5151
createStdLibStateMachine {
5252
initialState()
53-
onStarted { delay(1) }
53+
onStarted { delay(100) }
5454
}
5555
}
5656
}
@@ -76,4 +76,50 @@ class CoroutinesTest : StringSpec({
7676
scope.cancel()
7777
}
7878
}
79+
80+
"test context preserving by suspend methods called from threads" test@{
81+
val thread = Thread.currentThread()
82+
withContext(Dispatchers.IO) {
83+
println("${Thread.currentThread()}")
84+
createStateMachine(this@test) {
85+
onStarted { Thread.currentThread() shouldBe thread }
86+
initialState()
87+
}
88+
}
89+
}
90+
91+
"empty context does not preserve machine if suspend methods called from threads" {
92+
val scope = CoroutineScope(EmptyCoroutineContext)
93+
withContext(Dispatchers.IO) {
94+
val thread = Thread.currentThread()
95+
createStateMachine(scope) {
96+
onStarted { Thread.currentThread() shouldBe thread }
97+
initialState()
98+
}
99+
}
100+
}
101+
102+
"threaded context preserving by suspend methods called from threads" {
103+
val scope = CoroutineScope(Dispatchers.Default.limitedParallelism(1))
104+
val thread = runBlocking(scope.coroutineContext) { Thread.currentThread() }
105+
106+
withContext(Dispatchers.IO) {
107+
createStateMachine(scope) {
108+
onStarted { Thread.currentThread() shouldBe thread }
109+
initialState()
110+
}
111+
}
112+
}
113+
114+
"current thread context preserving by suspend methods called from threads" {
115+
runBlocking {
116+
val thread = Thread.currentThread()
117+
withContext(Dispatchers.IO) {
118+
createStateMachine(this@runBlocking) {
119+
onStarted { Thread.currentThread() shouldBe thread }
120+
initialState()
121+
}
122+
}
123+
}
124+
}
79125
})

0 commit comments

Comments
 (0)