Skip to content

Commit 133ed42

Browse files
committed
Implement runBlockingNonInterruptible in tests and test it
1 parent 4503d2a commit 133ed42

File tree

1 file changed

+88
-0
lines changed

1 file changed

+88
-0
lines changed

kotlinx-coroutines-core/jvm/test/RunBlockingJvmTest.kt

+88
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package kotlinx.coroutines
22

33
import kotlinx.coroutines.testing.*
44
import java.util.concurrent.CountDownLatch
5+
import java.util.concurrent.atomic.AtomicReference
56
import kotlin.concurrent.thread
67
import kotlin.test.*
78
import kotlin.time.Duration
@@ -64,6 +65,79 @@ class RunBlockingJvmTest : TestBase() {
6465
finish(5)
6566
}
6667

68+
/**
69+
* Tests that [runBlockingNonInterruptible] is going to run its job to completion even if it gets interrupted
70+
* or if thread switches occur.
71+
*/
72+
@Test
73+
fun testNonInterruptibleRunBlocking() {
74+
startInSeparateThreadAndInterrupt { mayInterrupt ->
75+
val v = runBlockingNonInterruptible {
76+
mayInterrupt()
77+
repeat(10) {
78+
expect(it + 1)
79+
delay(1)
80+
}
81+
42
82+
}
83+
assertTrue(Thread.interrupted())
84+
assertEquals(42, v)
85+
expect(11)
86+
}
87+
finish(12)
88+
}
89+
90+
/**
91+
* Tests that [runBlockingNonInterruptible] is going to run its job to completion even if it gets interrupted
92+
* or if thread switches occur, and then will rethrow the exception thrown by the job.
93+
*/
94+
@Test
95+
fun testNonInterruptibleRunBlockingFailure() {
96+
val exception = AssertionError()
97+
startInSeparateThreadAndInterrupt { mayInterrupt ->
98+
val exception2 = assertFailsWith<AssertionError> {
99+
runBlockingNonInterruptible {
100+
mayInterrupt()
101+
repeat(10) {
102+
expect(it + 1)
103+
// even thread switches should not be a problem
104+
withContext(Dispatchers.IO) {
105+
delay(1)
106+
}
107+
}
108+
throw exception
109+
}
110+
}
111+
assertTrue(Thread.interrupted())
112+
assertSame(exception, exception2)
113+
expect(11)
114+
}
115+
finish(12)
116+
}
117+
118+
119+
/**
120+
* Tests that [runBlockingNonInterruptible] is going to run its job to completion even if it gets interrupted
121+
* or if thread switches occur.
122+
*/
123+
@Test
124+
fun testNonInterruptibleRunBlockingPropagatingInterruptions() {
125+
val exception = AssertionError()
126+
startInSeparateThreadAndInterrupt { mayInterrupt ->
127+
runBlockingNonInterruptible {
128+
mayInterrupt()
129+
try {
130+
Thread.sleep(Long.MAX_VALUE)
131+
} catch (_: InterruptedException) {
132+
expect(1)
133+
}
134+
}
135+
expect(2)
136+
assertFalse(Thread.interrupted())
137+
}
138+
finish(3)
139+
}
140+
67141
private fun startInSeparateThreadAndInterrupt(action: (mayInterrupt: () -> Unit) -> Unit) {
68142
val latch = CountDownLatch(1)
69143
val thread = thread {
@@ -73,4 +147,18 @@ class RunBlockingJvmTest : TestBase() {
73147
thread.interrupt()
74148
thread.join()
75149
}
150+
151+
private fun <T> runBlockingNonInterruptible(action: suspend () -> T): T {
152+
val result = AtomicReference<Result<T>>()
153+
try {
154+
runBlocking {
155+
withContext(NonCancellable) {
156+
result.set(runCatching { action() })
157+
}
158+
}
159+
} catch (_: InterruptedException) {
160+
Thread.currentThread().interrupt() // restore the interrupted flag
161+
}
162+
return result.get().getOrThrow()
163+
}
76164
}

0 commit comments

Comments
 (0)