File tree 8 files changed +228
-30
lines changed
kotlinx-coroutines-reactive
kotlinx-coroutines-reactor/test
8 files changed +228
-30
lines changed Original file line number Diff line number Diff line change @@ -30,9 +30,8 @@ public fun <T> Publisher<T>.openSubscription(request: Int = 0): ReceiveChannel<T
30
30
/* *
31
31
* Subscribes to this [Publisher] and performs the specified action for each received element.
32
32
*/
33
- public suspend inline fun <T > Publisher<T>.consumeEach (action : (T ) -> Unit ) {
33
+ public suspend inline fun <T > Publisher<T>.consumeEach (action : (T ) -> Unit ) =
34
34
openSubscription().consumeEach(action)
35
- }
36
35
37
36
@Suppress(" INVISIBLE_REFERENCE" , " INVISIBLE_MEMBER" )
38
37
private class SubscriptionChannel <T >(
@@ -75,6 +74,7 @@ private class SubscriptionChannel<T>(
75
74
@Suppress(" CANNOT_OVERRIDE_INVISIBLE_MEMBER" )
76
75
override fun onClosedIdempotent (closed : LockFreeLinkedListNode ) {
77
76
subscription?.cancel()
77
+ subscription = null // optimization -- no need to cancel it again
78
78
}
79
79
80
80
// Subscriber overrides
Original file line number Diff line number Diff line change @@ -74,24 +74,6 @@ class IntegrationTest(
74
74
assertThat(cnt, IsEqual (1 ))
75
75
}
76
76
77
- @Test
78
- fun testFailingConsumer () = runTest {
79
- val pub = publish {
80
- repeat(3 ) {
81
- expect(it + 1 ) // expect(1), expect(2) *should* be invoked
82
- send(it)
83
- }
84
- }
85
-
86
- try {
87
- pub.consumeEach {
88
- throw TestException ()
89
- }
90
- } catch (e: TestException ) {
91
- finish(3 )
92
- }
93
- }
94
-
95
77
@Test
96
78
fun testNumbers () = runBlocking<Unit > {
97
79
val n = 100 * stressTestMultiplier
Original file line number Diff line number Diff line change @@ -252,4 +252,21 @@ class PublishTest : TestBase() {
252
252
latch.await()
253
253
finish(8 )
254
254
}
255
+
256
+ @Test
257
+ fun testFailingConsumer () = runTest {
258
+ val pub = publish {
259
+ repeat(3 ) {
260
+ expect(it + 1 ) // expect(1), expect(2) *should* be invoked
261
+ send(it)
262
+ }
263
+ }
264
+ try {
265
+ pub.consumeEach {
266
+ throw TestException ()
267
+ }
268
+ } catch (e: TestException ) {
269
+ finish(3 )
270
+ }
271
+ }
255
272
}
Original file line number Diff line number Diff line change 5
5
package kotlinx.coroutines.reactor
6
6
7
7
import kotlinx.coroutines.*
8
+ import kotlinx.coroutines.reactive.*
8
9
import org.hamcrest.core.*
9
10
import org.junit.*
11
+ import org.junit.Test
12
+ import kotlin.test.*
10
13
11
14
class FluxTest : TestBase () {
12
15
@Test
@@ -80,4 +83,59 @@ class FluxTest : TestBase() {
80
83
{ assert (it is RuntimeException ) }
81
84
)
82
85
}
86
+
87
+ @Test
88
+ fun testNotifyOnceOnCancellation () = runTest {
89
+ expect(1 )
90
+ val observable =
91
+ flux {
92
+ expect(5 )
93
+ send(" OK" )
94
+ try {
95
+ delay(Long .MAX_VALUE )
96
+ } catch (e: CancellationException ) {
97
+ expect(11 )
98
+ }
99
+ }
100
+ .doOnNext {
101
+ expect(6 )
102
+ assertEquals(" OK" , it)
103
+ }
104
+ .doOnCancel {
105
+ expect(10 ) // notified once!
106
+ }
107
+ expect(2 )
108
+ val job = launch(start = CoroutineStart .UNDISPATCHED ) {
109
+ expect(3 )
110
+ observable.consumeEach {
111
+ expect(8 )
112
+ assertEquals(" OK" , it)
113
+ }
114
+ }
115
+ expect(4 )
116
+ yield () // to observable code
117
+ expect(7 )
118
+ yield () // to consuming coroutines
119
+ expect(9 )
120
+ job.cancel()
121
+ job.join()
122
+ finish(12 )
123
+ }
124
+
125
+ @Test
126
+ fun testFailingConsumer () = runTest {
127
+ val pub = flux {
128
+ repeat(3 ) {
129
+ expect(it + 1 ) // expect(1), expect(2) *should* be invoked
130
+ send(it)
131
+ }
132
+ }
133
+ try {
134
+ pub.consumeEach {
135
+ throw TestException ()
136
+ }
137
+ } catch (e: TestException ) {
138
+ finish(3 )
139
+ }
140
+ }
83
141
}
Original file line number Diff line number Diff line change @@ -43,20 +43,14 @@ public fun <T> ObservableSource<T>.openSubscription(): ReceiveChannel<T> {
43
43
/* *
44
44
* Subscribes to this [MaybeSource] and performs the specified action for each received element.
45
45
*/
46
- public suspend inline fun <T > MaybeSource<T>.consumeEach (action : (T ) -> Unit ) {
47
- val channel = openSubscription()
48
- for (x in channel) action(x)
49
- channel.cancel()
50
- }
46
+ public suspend inline fun <T > MaybeSource<T>.consumeEach (action : (T ) -> Unit ) =
47
+ openSubscription().consumeEach(action)
51
48
52
49
/* *
53
50
* Subscribes to this [ObservableSource] and performs the specified action for each received element.
54
51
*/
55
- public suspend inline fun <T > ObservableSource<T>.consumeEach (action : (T ) -> Unit ) {
56
- val channel = openSubscription()
57
- for (x in channel) action(x)
58
- channel.cancel()
59
- }
52
+ public suspend inline fun <T > ObservableSource<T>.consumeEach (action : (T ) -> Unit ) =
53
+ openSubscription().consumeEach(action)
60
54
61
55
@Suppress(" INVISIBLE_REFERENCE" , " INVISIBLE_MEMBER" )
62
56
private class SubscriptionChannel <T > :
@@ -68,6 +62,7 @@ private class SubscriptionChannel<T> :
68
62
@Suppress(" CANNOT_OVERRIDE_INVISIBLE_MEMBER" )
69
63
override fun onClosedIdempotent (closed : LockFreeLinkedListNode ) {
70
64
subscription?.dispose()
65
+ subscription = null // optimization -- no need to dispose it again
71
66
}
72
67
73
68
// Observer overrider
Original file line number Diff line number Diff line change @@ -8,6 +8,8 @@ import kotlinx.coroutines.*
8
8
import kotlinx.coroutines.reactive.*
9
9
import org.hamcrest.core.*
10
10
import org.junit.*
11
+ import org.junit.Test
12
+ import kotlin.test.*
11
13
12
14
class FlowableTest : TestBase () {
13
15
@Test
@@ -81,4 +83,59 @@ class FlowableTest : TestBase() {
81
83
{ assert (it is RuntimeException ) }
82
84
)
83
85
}
86
+
87
+ @Test
88
+ fun testNotifyOnceOnCancellation () = runTest {
89
+ expect(1 )
90
+ val observable =
91
+ rxFlowable {
92
+ expect(5 )
93
+ send(" OK" )
94
+ try {
95
+ delay(Long .MAX_VALUE )
96
+ } catch (e: CancellationException ) {
97
+ expect(11 )
98
+ }
99
+ }
100
+ .doOnNext {
101
+ expect(6 )
102
+ assertEquals(" OK" , it)
103
+ }
104
+ .doOnCancel {
105
+ expect(10 ) // notified once!
106
+ }
107
+ expect(2 )
108
+ val job = launch(start = CoroutineStart .UNDISPATCHED ) {
109
+ expect(3 )
110
+ observable.consumeEach{
111
+ expect(8 )
112
+ assertEquals(" OK" , it)
113
+ }
114
+ }
115
+ expect(4 )
116
+ yield () // to observable code
117
+ expect(7 )
118
+ yield () // to consuming coroutines
119
+ expect(9 )
120
+ job.cancel()
121
+ job.join()
122
+ finish(12 )
123
+ }
124
+
125
+ @Test
126
+ fun testFailingConsumer () = runTest {
127
+ val pub = rxFlowable {
128
+ repeat(3 ) {
129
+ expect(it + 1 ) // expect(1), expect(2) *should* be invoked
130
+ send(it)
131
+ }
132
+ }
133
+ try {
134
+ pub.consumeEach {
135
+ throw TestException ()
136
+ }
137
+ } catch (e: TestException ) {
138
+ finish(3 )
139
+ }
140
+ }
84
141
}
Original file line number Diff line number Diff line change @@ -12,6 +12,7 @@ import org.hamcrest.core.*
12
12
import org.junit.*
13
13
import org.junit.Assert.*
14
14
import java.util.concurrent.*
15
+ import java.util.concurrent.CancellationException
15
16
16
17
class MaybeTest : TestBase () {
17
18
@Before
@@ -211,4 +212,30 @@ class MaybeTest : TestBase() {
211
212
{ assert (it is RuntimeException ) }
212
213
)
213
214
}
215
+
216
+ @Test
217
+ fun testCancelledConsumer () = runTest {
218
+ expect(1 )
219
+ val maybe = rxMaybe<Int > {
220
+ expect(4 )
221
+ try {
222
+ delay(Long .MAX_VALUE )
223
+ } catch (e: CancellationException ) {
224
+ expect(6 )
225
+ }
226
+ 42
227
+ }
228
+ expect(2 )
229
+ val timeout = withTimeoutOrNull(100 ) {
230
+ expect(3 )
231
+ maybe.consumeEach {
232
+ expectUnreached()
233
+ }
234
+ expectUnreached()
235
+ }
236
+ assertNull(timeout)
237
+ expect(5 )
238
+ yield () // must cancel code inside maybe!!!
239
+ finish(7 )
240
+ }
214
241
}
Original file line number Diff line number Diff line change @@ -7,6 +7,8 @@ package kotlinx.coroutines.rx2
7
7
import kotlinx.coroutines.*
8
8
import org.hamcrest.core.*
9
9
import org.junit.*
10
+ import org.junit.Test
11
+ import kotlin.test.*
10
12
11
13
class ObservableTest : TestBase () {
12
14
@Test
@@ -80,4 +82,64 @@ class ObservableTest : TestBase() {
80
82
{ assert (it is RuntimeException ) }
81
83
)
82
84
}
85
+
86
+ @Test
87
+ fun testNotifyOnceOnCancellation () = runTest {
88
+ expect(1 )
89
+ val observable =
90
+ rxObservable {
91
+ expect(5 )
92
+ send(" OK" )
93
+ try {
94
+ delay(Long .MAX_VALUE )
95
+ } catch (e: CancellationException ) {
96
+ expect(11 )
97
+ }
98
+ }
99
+ .doOnNext {
100
+ expect(6 )
101
+ assertEquals(" OK" , it)
102
+ }
103
+ .doOnDispose {
104
+ expect(10 ) // notified once!
105
+ }
106
+ expect(2 )
107
+ val job = launch(start = CoroutineStart .UNDISPATCHED ) {
108
+ expect(3 )
109
+ observable.consumeEach{
110
+ expect(8 )
111
+ assertEquals(" OK" , it)
112
+ }
113
+ }
114
+ expect(4 )
115
+ yield () // to observable code
116
+ expect(7 )
117
+ yield () // to consuming coroutines
118
+ expect(9 )
119
+ job.cancel()
120
+ job.join()
121
+ finish(12 )
122
+ }
123
+
124
+ @Test
125
+ fun testFailingConsumer () = runTest {
126
+ expect(1 )
127
+ val pub = rxObservable {
128
+ expect(2 )
129
+ send(" OK" )
130
+ try {
131
+ delay(Long .MAX_VALUE )
132
+ } catch (e: CancellationException ) {
133
+ finish(5 )
134
+ }
135
+ }
136
+ try {
137
+ pub.consumeEach {
138
+ expect(3 )
139
+ throw TestException ()
140
+ }
141
+ } catch (e: TestException ) {
142
+ expect(4 )
143
+ }
144
+ }
83
145
}
You can’t perform that action at this time.
0 commit comments