@@ -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,76 @@ 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
+ 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
+
67
138
private fun startInSeparateThreadAndInterrupt (action : (mayInterrupt: () -> Unit ) -> Unit ) {
68
139
val latch = CountDownLatch (1 )
69
140
val thread = thread {
@@ -73,4 +144,18 @@ class RunBlockingJvmTest : TestBase() {
73
144
thread.interrupt()
74
145
thread.join()
75
146
}
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
+ }
76
161
}
0 commit comments