@@ -2,6 +2,7 @@ package kotlinx.coroutines
2
2
3
3
import kotlinx.coroutines.testing.*
4
4
import java.util.concurrent.CountDownLatch
5
+ import java.util.concurrent.atomic.AtomicReference
5
6
import kotlin.concurrent.thread
6
7
import kotlin.test.*
7
8
import kotlin.time.Duration
@@ -64,6 +65,79 @@ class RunBlockingJvmTest : TestBase() {
64
65
finish(5 )
65
66
}
66
67
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
+
67
141
private fun startInSeparateThreadAndInterrupt (action : (mayInterrupt: () -> Unit ) -> Unit ) {
68
142
val latch = CountDownLatch (1 )
69
143
val thread = thread {
@@ -73,4 +147,18 @@ class RunBlockingJvmTest : TestBase() {
73
147
thread.interrupt()
74
148
thread.join()
75
149
}
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
+ }
76
164
}
0 commit comments