Skip to content

Commit da419c4

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

File tree

1 file changed

+85
-0
lines changed

1 file changed

+85
-0
lines changed

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

+85
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,76 @@ 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+
delay(1)
104+
}
105+
throw exception
106+
}
107+
}
108+
assertTrue(Thread.interrupted())
109+
assertSame(exception, exception2)
110+
expect(11)
111+
}
112+
finish(12)
113+
}
114+
115+
116+
/**
117+
* Tests that [runBlockingNonInterruptible] is going to run its job to completion even if it gets interrupted
118+
* or if thread switches occur.
119+
*/
120+
@Test
121+
fun testNonInterruptibleRunBlockingPropagatingInterruptions() {
122+
val exception = AssertionError()
123+
startInSeparateThreadAndInterrupt { mayInterrupt ->
124+
runBlockingNonInterruptible {
125+
mayInterrupt()
126+
try {
127+
Thread.sleep(Long.MAX_VALUE)
128+
} catch (_: InterruptedException) {
129+
expect(1)
130+
}
131+
}
132+
expect(2)
133+
assertFalse(Thread.interrupted())
134+
}
135+
finish(3)
136+
}
137+
67138
private fun startInSeparateThreadAndInterrupt(action: (mayInterrupt: () -> Unit) -> Unit) {
68139
val latch = CountDownLatch(1)
69140
val thread = thread {
@@ -73,4 +144,18 @@ class RunBlockingJvmTest : TestBase() {
73144
thread.interrupt()
74145
thread.join()
75146
}
147+
148+
private fun <T> runBlockingNonInterruptible(action: suspend () -> T): T {
149+
val result = AtomicReference<Result<T>>()
150+
try {
151+
runBlocking {
152+
withContext(NonCancellable) {
153+
result.set(runCatching { action() })
154+
}
155+
}
156+
} catch (_: InterruptedException) {
157+
Thread.currentThread().interrupt() // restore the interrupted flag
158+
}
159+
return result.get().getOrThrow()
160+
}
76161
}

0 commit comments

Comments
 (0)