1
1
package kotlinx.coroutines
2
2
3
3
import kotlinx.coroutines.testing.*
4
- import org.junit.*
4
+ import java.util.concurrent.CountDownLatch
5
+ import java.util.concurrent.atomic.AtomicReference
6
+ import kotlin.concurrent.thread
7
+ import kotlin.test.*
8
+ import kotlin.time.Duration
5
9
6
10
class RunBlockingJvmTest : TestBase () {
7
11
@Test
@@ -12,5 +16,177 @@ class RunBlockingJvmTest : TestBase() {
12
16
}
13
17
rb.hashCode() // unused
14
18
}
15
- }
16
19
20
+ /* * Tests that the [runBlocking] coroutine runs to completion even it was interrupted. */
21
+ @Test
22
+ fun testFinishingWhenInterrupted () {
23
+ startInSeparateThreadAndInterrupt { mayInterrupt ->
24
+ expect(1 )
25
+ try {
26
+ runBlocking {
27
+ try {
28
+ mayInterrupt()
29
+ expect(2 )
30
+ delay(Duration .INFINITE )
31
+ } finally {
32
+ withContext(NonCancellable ) {
33
+ expect(3 )
34
+ repeat(10 ) { yield () }
35
+ expect(4 )
36
+ }
37
+ }
38
+ }
39
+ } catch (_: InterruptedException ) {
40
+ expect(5 )
41
+ }
42
+ }
43
+ finish(6 )
44
+ }
45
+
46
+ /* * Tests that [runBlocking] will exit if it gets interrupted. */
47
+ @Test
48
+ fun testCancellingWhenInterrupted () {
49
+ startInSeparateThreadAndInterrupt { mayInterrupt ->
50
+ expect(1 )
51
+ try {
52
+ runBlocking {
53
+ try {
54
+ mayInterrupt()
55
+ expect(2 )
56
+ delay(Duration .INFINITE )
57
+ } catch (_: CancellationException ) {
58
+ expect(3 )
59
+ }
60
+ }
61
+ } catch (_: InterruptedException ) {
62
+ expect(4 )
63
+ }
64
+ }
65
+ finish(5 )
66
+ }
67
+
68
+ /* * Tests that [runBlocking] does not check for interruptions before the first attempt to suspend,
69
+ * as no blocking actually happens. */
70
+ @Test
71
+ fun testInitialPortionRunningDespiteInterruptions () {
72
+ Thread .currentThread().interrupt()
73
+ runBlocking {
74
+ expect(1 )
75
+ try {
76
+ Thread .sleep(Long .MAX_VALUE )
77
+ } catch (_: InterruptedException ) {
78
+ expect(2 )
79
+ }
80
+ }
81
+ assertFalse(Thread .interrupted())
82
+ finish(3 )
83
+ }
84
+
85
+ /* *
86
+ * Tests that [runBlockingNonInterruptible] is going to run its job to completion even if it gets interrupted
87
+ * or if thread switches occur.
88
+ */
89
+ @Test
90
+ fun testNonInterruptibleRunBlocking () {
91
+ startInSeparateThreadAndInterrupt { mayInterrupt ->
92
+ val v = runBlockingNonInterruptible {
93
+ mayInterrupt()
94
+ repeat(10 ) {
95
+ expect(it + 1 )
96
+ delay(1 )
97
+ }
98
+ 42
99
+ }
100
+ assertTrue(Thread .interrupted())
101
+ assertEquals(42 , v)
102
+ expect(11 )
103
+ }
104
+ finish(12 )
105
+ }
106
+
107
+ /* *
108
+ * Tests that [runBlockingNonInterruptible] is going to run its job to completion even if it gets interrupted
109
+ * or if thread switches occur, and then will rethrow the exception thrown by the job.
110
+ */
111
+ @Test
112
+ fun testNonInterruptibleRunBlockingFailure () {
113
+ val exception = AssertionError ()
114
+ startInSeparateThreadAndInterrupt { mayInterrupt ->
115
+ val exception2 = assertFailsWith<AssertionError > {
116
+ runBlockingNonInterruptible {
117
+ mayInterrupt()
118
+ repeat(10 ) {
119
+ expect(it + 1 )
120
+ // even thread switches should not be a problem
121
+ withContext(Dispatchers .IO ) {
122
+ delay(1 )
123
+ }
124
+ }
125
+ throw exception
126
+ }
127
+ }
128
+ assertTrue(Thread .interrupted())
129
+ assertSame(exception, exception2)
130
+ expect(11 )
131
+ }
132
+ finish(12 )
133
+ }
134
+
135
+
136
+ /* *
137
+ * Tests that [runBlockingNonInterruptible] is going to run its job to completion even if it gets interrupted
138
+ * or if thread switches occur.
139
+ */
140
+ @Test
141
+ fun testNonInterruptibleRunBlockingPropagatingInterruptions () {
142
+ val exception = AssertionError ()
143
+ startInSeparateThreadAndInterrupt { mayInterrupt ->
144
+ runBlockingNonInterruptible {
145
+ mayInterrupt()
146
+ try {
147
+ Thread .sleep(Long .MAX_VALUE )
148
+ } catch (_: InterruptedException ) {
149
+ expect(1 )
150
+ }
151
+ }
152
+ expect(2 )
153
+ assertFalse(Thread .interrupted())
154
+ }
155
+ finish(3 )
156
+ }
157
+
158
+ /* *
159
+ * Tests that starting [runBlockingNonInterruptible] in an interrupted thread does not affect the result.
160
+ */
161
+ @Test
162
+ fun testNonInterruptibleRunBlockingStartingInterrupted () {
163
+ Thread .currentThread().interrupt()
164
+ val v = runBlockingNonInterruptible { 42 }
165
+ assertEquals(42 , v)
166
+ assertTrue(Thread .interrupted())
167
+ }
168
+
169
+ private fun startInSeparateThreadAndInterrupt (action : (mayInterrupt: () -> Unit ) -> Unit ) {
170
+ val latch = CountDownLatch (1 )
171
+ val thread = thread {
172
+ action { latch.countDown() }
173
+ }
174
+ latch.await()
175
+ thread.interrupt()
176
+ thread.join()
177
+ }
178
+
179
+ private fun <T > runBlockingNonInterruptible (action : suspend () -> T ): T {
180
+ val result = AtomicReference <Result <T >>()
181
+ try {
182
+ runBlocking {
183
+ withContext(NonCancellable ) {
184
+ result.set(runCatching { action() })
185
+ }
186
+ }
187
+ } catch (_: InterruptedException ) {
188
+ Thread .currentThread().interrupt() // restore the interrupted flag
189
+ }
190
+ return result.get().getOrThrow()
191
+ }
192
+ }
0 commit comments