Skip to content

Commit d100a3f

Browse files
authored
Reactive scopeless (#1341)
Make all reactive builders top-level functions instead of extensions on CoroutineScope and prohibit jobs in their context Downsides of having lifecycle-managed scoped builders: * The lifecycle of semantically cold entity is managed externally by the hot-one. * Independent failures in independent triggered computations affect each other * Two cancellation sources should be managed, coroutine-related Job parent and disposable/subscription
1 parent ace5899 commit d100a3f

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+447
-508
lines changed

binary-compatibility-validator/reference-public-api/kotlinx-coroutines-core.txt

+1
Original file line numberDiff line numberDiff line change
@@ -437,6 +437,7 @@ public final class kotlinx/coroutines/NonCancellable : kotlin/coroutines/Abstrac
437437
public fun join (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
438438
public fun plus (Lkotlinx/coroutines/Job;)Lkotlinx/coroutines/Job;
439439
public fun start ()Z
440+
public fun toString ()Ljava/lang/String;
440441
}
441442

442443
public final class kotlinx/coroutines/NonDisposableHandle : kotlinx/coroutines/ChildHandle, kotlinx/coroutines/DisposableHandle {

binary-compatibility-validator/reference-public-api/kotlinx-coroutines-reactive.txt

+3
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,11 @@ public final class kotlinx/coroutines/reactive/ConvertKt {
2020
}
2121

2222
public final class kotlinx/coroutines/reactive/PublishKt {
23+
public static final fun publish (Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;)Lorg/reactivestreams/Publisher;
2324
public static final fun publish (Lkotlinx/coroutines/CoroutineScope;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;)Lorg/reactivestreams/Publisher;
25+
public static synthetic fun publish$default (Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lorg/reactivestreams/Publisher;
2426
public static synthetic fun publish$default (Lkotlinx/coroutines/CoroutineScope;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lorg/reactivestreams/Publisher;
27+
public static final fun publishInternal (Lkotlinx/coroutines/CoroutineScope;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;)Lorg/reactivestreams/Publisher;
2528
}
2629

2730
public final class kotlinx/coroutines/reactive/flow/FlowAsPublisherKt {

binary-compatibility-validator/reference-public-api/kotlinx-coroutines-reactor.txt

+4
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,16 @@ public final class kotlinx/coroutines/reactor/ConvertKt {
66
}
77

88
public final class kotlinx/coroutines/reactor/FluxKt {
9+
public static final fun flux (Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;)Lreactor/core/publisher/Flux;
910
public static final fun flux (Lkotlinx/coroutines/CoroutineScope;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;)Lreactor/core/publisher/Flux;
11+
public static synthetic fun flux$default (Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lreactor/core/publisher/Flux;
1012
public static synthetic fun flux$default (Lkotlinx/coroutines/CoroutineScope;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lreactor/core/publisher/Flux;
1113
}
1214

1315
public final class kotlinx/coroutines/reactor/MonoKt {
16+
public static final fun mono (Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;)Lreactor/core/publisher/Mono;
1417
public static final fun mono (Lkotlinx/coroutines/CoroutineScope;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;)Lreactor/core/publisher/Mono;
18+
public static synthetic fun mono$default (Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lreactor/core/publisher/Mono;
1519
public static synthetic fun mono$default (Lkotlinx/coroutines/CoroutineScope;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lreactor/core/publisher/Mono;
1620
}
1721

binary-compatibility-validator/reference-public-api/kotlinx-coroutines-rx2.txt

+10
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,9 @@ public final class kotlinx/coroutines/rx2/RxChannelKt {
2121
}
2222

2323
public final class kotlinx/coroutines/rx2/RxCompletableKt {
24+
public static final fun rxCompletable (Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;)Lio/reactivex/Completable;
2425
public static final fun rxCompletable (Lkotlinx/coroutines/CoroutineScope;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;)Lio/reactivex/Completable;
26+
public static synthetic fun rxCompletable$default (Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lio/reactivex/Completable;
2527
public static synthetic fun rxCompletable$default (Lkotlinx/coroutines/CoroutineScope;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lio/reactivex/Completable;
2628
}
2729

@@ -35,17 +37,23 @@ public final class kotlinx/coroutines/rx2/RxConvertKt {
3537
}
3638

3739
public final class kotlinx/coroutines/rx2/RxFlowableKt {
40+
public static final fun rxFlowable (Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;)Lio/reactivex/Flowable;
3841
public static final fun rxFlowable (Lkotlinx/coroutines/CoroutineScope;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;)Lio/reactivex/Flowable;
42+
public static synthetic fun rxFlowable$default (Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lio/reactivex/Flowable;
3943
public static synthetic fun rxFlowable$default (Lkotlinx/coroutines/CoroutineScope;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lio/reactivex/Flowable;
4044
}
4145

4246
public final class kotlinx/coroutines/rx2/RxMaybeKt {
47+
public static final fun rxMaybe (Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;)Lio/reactivex/Maybe;
4348
public static final fun rxMaybe (Lkotlinx/coroutines/CoroutineScope;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;)Lio/reactivex/Maybe;
49+
public static synthetic fun rxMaybe$default (Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lio/reactivex/Maybe;
4450
public static synthetic fun rxMaybe$default (Lkotlinx/coroutines/CoroutineScope;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lio/reactivex/Maybe;
4551
}
4652

4753
public final class kotlinx/coroutines/rx2/RxObservableKt {
54+
public static final fun rxObservable (Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;)Lio/reactivex/Observable;
4855
public static final fun rxObservable (Lkotlinx/coroutines/CoroutineScope;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;)Lio/reactivex/Observable;
56+
public static synthetic fun rxObservable$default (Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lio/reactivex/Observable;
4957
public static synthetic fun rxObservable$default (Lkotlinx/coroutines/CoroutineScope;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lio/reactivex/Observable;
5058
}
5159

@@ -54,7 +62,9 @@ public final class kotlinx/coroutines/rx2/RxSchedulerKt {
5462
}
5563

5664
public final class kotlinx/coroutines/rx2/RxSingleKt {
65+
public static final fun rxSingle (Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;)Lio/reactivex/Single;
5766
public static final fun rxSingle (Lkotlinx/coroutines/CoroutineScope;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;)Lio/reactivex/Single;
67+
public static synthetic fun rxSingle$default (Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lio/reactivex/Single;
5868
public static synthetic fun rxSingle$default (Lkotlinx/coroutines/CoroutineScope;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lio/reactivex/Single;
5969
}
6070

kotlinx-coroutines-core/common/src/NonCancellable.kt

+5
Original file line numberDiff line numberDiff line change
@@ -115,4 +115,9 @@ public object NonCancellable : AbstractCoroutineContextElement(Job), Job {
115115
*/
116116
@InternalCoroutinesApi
117117
override fun attachChild(child: ChildJob): ChildHandle = NonDisposableHandle
118+
119+
/** @suppress */
120+
override fun toString(): String {
121+
return "NonCancellable"
122+
}
118123
}

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

+3
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import kotlinx.coroutines.scheduling.*
99
import org.junit.*
1010
import java.util.*
1111
import java.util.concurrent.atomic.*
12+
import kotlin.coroutines.*
1213
import kotlin.test.*
1314

1415
private val VERBOSE = systemProp("test.verbose", false)
@@ -213,4 +214,6 @@ public actual open class TestBase actual constructor() {
213214
assertTrue(result.exceptionOrNull() is T, "Expected ${T::class}, but had $result")
214215
return result.exceptionOrNull()!! as T
215216
}
217+
218+
protected suspend fun currentDispatcher() = coroutineContext[ContinuationInterceptor]!!
216219
}

reactive/coroutines-guide-reactive.md

+10-10
Original file line numberDiff line numberDiff line change
@@ -617,7 +617,7 @@ fun <T, R> Publisher<T>.fusedFilterMap(
617617
context: CoroutineContext, // the context to execute this coroutine in
618618
predicate: (T) -> Boolean, // the filter predicate
619619
mapper: (T) -> R // the mapper function
620-
) = GlobalScope.publish<R>(context) {
620+
) = publish<R>(context) {
621621
collect { // collect the source stream
622622
if (predicate(it)) // filter part
623623
send(mapper(it)) // map part
@@ -638,7 +638,7 @@ fun CoroutineScope.range(start: Int, count: Int) = publish<Int> {
638638
```kotlin
639639
fun main() = runBlocking<Unit> {
640640
range(1, 5)
641-
.fusedFilterMap(coroutineContext, { it % 2 == 0}, { "$it is even" })
641+
.fusedFilterMap(Dispatchers.Unconfined, { it % 2 == 0}, { "$it is even" })
642642
.collect { println(it) } // print all the resulting strings
643643
}
644644
```
@@ -673,7 +673,7 @@ import kotlin.coroutines.*
673673
-->
674674

675675
```kotlin
676-
fun <T, U> Publisher<T>.takeUntil(context: CoroutineContext, other: Publisher<U>) = GlobalScope.publish<T>(context) {
676+
fun <T, U> Publisher<T>.takeUntil(context: CoroutineContext, other: Publisher<U>) = publish<T>(context) {
677677
this@takeUntil.openSubscription().consume { // explicitly open channel to Publisher<T>
678678
val current = this
679679
other.openSubscription().consume { // explicitly open channel to Publisher<U>
@@ -711,7 +711,7 @@ The following code shows how `takeUntil` works:
711711
fun main() = runBlocking<Unit> {
712712
val slowNums = rangeWithInterval(200, 1, 10) // numbers with 200ms interval
713713
val stop = rangeWithInterval(500, 1, 10) // the first one after 500ms
714-
slowNums.takeUntil(coroutineContext, stop).collect { println(it) } // let's test it
714+
slowNums.takeUntil(Dispatchers.Unconfined, stop).collect { println(it) } // let's test it
715715
}
716716
```
717717

@@ -742,7 +742,7 @@ import kotlin.coroutines.*
742742
-->
743743

744744
```kotlin
745-
fun <T> Publisher<Publisher<T>>.merge(context: CoroutineContext) = GlobalScope.publish<T>(context) {
745+
fun <T> Publisher<Publisher<T>>.merge(context: CoroutineContext) = publish<T>(context) {
746746
collect { pub -> // for each publisher collected
747747
launch { // launch a child coroutine
748748
pub.collect { send(it) } // resend all element from this publisher
@@ -783,7 +783,7 @@ The test code is to use `merge` on `testPub` and to display the results:
783783

784784
```kotlin
785785
fun main() = runBlocking<Unit> {
786-
testPub().merge(coroutineContext).collect { println(it) } // print the whole stream
786+
testPub().merge(Dispatchers.Unconfined).collect { println(it) } // print the whole stream
787787
}
788788
```
789789

@@ -865,7 +865,7 @@ import kotlin.coroutines.CoroutineContext
865865
-->
866866

867867
```kotlin
868-
fun rangeWithInterval(context: CoroutineContext, time: Long, start: Int, count: Int) = GlobalScope.publish<Int>(context) {
868+
fun rangeWithInterval(context: CoroutineContext, time: Long, start: Int, count: Int) = publish<Int>(context) {
869869
for (x in start until start + count) {
870870
delay(time) // wait before sending each number
871871
send(x)
@@ -915,7 +915,7 @@ import kotlin.coroutines.CoroutineContext
915915
-->
916916

917917
```kotlin
918-
fun rangeWithInterval(context: CoroutineContext, time: Long, start: Int, count: Int) = GlobalScope.publish<Int>(context) {
918+
fun rangeWithInterval(context: CoroutineContext, time: Long, start: Int, count: Int) = publish<Int>(context) {
919919
for (x in start until start + count) {
920920
delay(time) // wait before sending each number
921921
send(x)
@@ -1067,12 +1067,12 @@ coroutines for complex pipelines with fan-in and fan-out between multiple worker
10671067
[whileSelect]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.selects/while-select.html
10681068
<!--- MODULE kotlinx-coroutines-reactive -->
10691069
<!--- INDEX kotlinx.coroutines.reactive -->
1070-
[publish]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-reactive/kotlinx.coroutines.reactive/kotlinx.coroutines.-coroutine-scope/publish.html
1070+
[publish]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-reactive/kotlinx.coroutines.reactive/publish.html
10711071
[org.reactivestreams.Publisher.collect]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-reactive/kotlinx.coroutines.reactive/org.reactivestreams.-publisher/collect.html
10721072
[org.reactivestreams.Publisher.openSubscription]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-reactive/kotlinx.coroutines.reactive/org.reactivestreams.-publisher/open-subscription.html
10731073
<!--- MODULE kotlinx-coroutines-rx2 -->
10741074
<!--- INDEX kotlinx.coroutines.rx2 -->
1075-
[rxFlowable]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-rx2/kotlinx.coroutines.rx2/kotlinx.coroutines.-coroutine-scope/rx-flowable.html
1075+
[rxFlowable]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-rx2/kotlinx.coroutines.rx2/rx-flowable.html
10761076
<!--- END -->
10771077

10781078

reactive/kotlinx-coroutines-reactive/README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ Conversion functions:
3333
[ReceiveChannel]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-receive-channel/index.html
3434
<!--- MODULE kotlinx-coroutines-reactive -->
3535
<!--- INDEX kotlinx.coroutines.reactive -->
36-
[publish]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-reactive/kotlinx.coroutines.reactive/kotlinx.coroutines.-coroutine-scope/publish.html
36+
[publish]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-reactive/kotlinx.coroutines.reactive/publish.html
3737
[org.reactivestreams.Publisher.awaitFirst]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-reactive/kotlinx.coroutines.reactive/org.reactivestreams.-publisher/await-first.html
3838
[org.reactivestreams.Publisher.awaitFirstOrDefault]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-reactive/kotlinx.coroutines.reactive/org.reactivestreams.-publisher/await-first-or-default.html
3939
[org.reactivestreams.Publisher.awaitFirstOrElse]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-reactive/kotlinx.coroutines.reactive/org.reactivestreams.-publisher/await-first-or-else.html

reactive/kotlinx-coroutines-reactive/src/Convert.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import kotlin.coroutines.*
2121
* @param context -- the coroutine context from which the resulting observable is going to be signalled
2222
*/
2323
@ObsoleteCoroutinesApi
24-
public fun <T> ReceiveChannel<T>.asPublisher(context: CoroutineContext = EmptyCoroutineContext): Publisher<T> = GlobalScope.publish(context) {
24+
public fun <T> ReceiveChannel<T>.asPublisher(context: CoroutineContext = EmptyCoroutineContext): Publisher<T> = publish(context) {
2525
for (t in this@asPublisher)
2626
send(t)
2727
}

reactive/kotlinx-coroutines-reactive/src/Publish.kt

+30-8
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
/*
2-
* Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
2+
* Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
33
*/
44

5+
@file:Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER")
6+
57
package kotlinx.coroutines.reactive
68

79
import kotlinx.atomicfu.*
@@ -11,6 +13,7 @@ import kotlinx.coroutines.selects.*
1113
import kotlinx.coroutines.sync.*
1214
import org.reactivestreams.*
1315
import kotlin.coroutines.*
16+
import kotlin.internal.LowPriorityInOverloadResolution
1417

1518
/**
1619
* Creates cold reactive [Publisher] that runs a given [block] in a coroutine.
@@ -26,25 +29,44 @@ import kotlin.coroutines.*
2629
* | Normal completion or `close` without cause | `onComplete`
2730
* | Failure with exception or `close` with cause | `onError`
2831
*
29-
* Coroutine context is inherited from a [CoroutineScope], additional context elements can be specified with [context] argument.
32+
* Coroutine context can be specified with [context] argument.
3033
* If the context does not have any dispatcher nor any other [ContinuationInterceptor], then [Dispatchers.Default] is used.
31-
* The parent job is inherited from a [CoroutineScope] as well, but it can also be overridden
32-
* with corresponding [coroutineContext] element.
34+
* Method throws [IllegalArgumentException] if provided [context] contains a [Job] instance.
3335
*
3436
* **Note: This is an experimental api.** Behaviour of publishers that work as children in a parent scope with respect
3537
* to cancellation and error handling may change in the future.
36-
*
37-
* @param context context of the coroutine.
38-
* @param block the coroutine code.
3938
*/
4039
@ExperimentalCoroutinesApi
40+
public fun <T> publish(
41+
context: CoroutineContext = EmptyCoroutineContext,
42+
@BuilderInference block: suspend ProducerScope<T>.() -> Unit
43+
): Publisher<T> {
44+
require(context[Job] === null) { "Publisher context cannot contain job in it." +
45+
"Its lifecycle should be managed via subscription. Had $context" }
46+
return publishInternal(GlobalScope, context, block)
47+
}
48+
49+
@Deprecated(
50+
message = "CoroutineScope.publish is deprecated in favour of top-level publish",
51+
level = DeprecationLevel.WARNING,
52+
replaceWith = ReplaceWith("publish(context, block)")
53+
) // Since 1.3.0, will be error in 1.3.1 and hidden in 1.4.0. Binary compatibility with Spring
54+
@LowPriorityInOverloadResolution
4155
public fun <T> CoroutineScope.publish(
4256
context: CoroutineContext = EmptyCoroutineContext,
4357
@BuilderInference block: suspend ProducerScope<T>.() -> Unit
58+
): Publisher<T> = publishInternal(this, context, block)
59+
60+
/** @suppress For internal use from other reactive integration modules only */
61+
@InternalCoroutinesApi
62+
public fun <T> publishInternal(
63+
scope: CoroutineScope, // support for legacy publish in scope
64+
context: CoroutineContext,
65+
block: suspend ProducerScope<T>.() -> Unit
4466
): Publisher<T> = Publisher { subscriber ->
4567
// specification requires NPE on null subscriber
4668
if (subscriber == null) throw NullPointerException("Subscriber cannot be null")
47-
val newContext = newCoroutineContext(context)
69+
val newContext = scope.newCoroutineContext(context)
4870
val coroutine = PublisherCoroutine(newContext, subscriber)
4971
subscriber.onSubscribe(coroutine) // do it first (before starting coroutine), to avoid unnecessary suspensions
5072
coroutine.start(CoroutineStart.DEFAULT, coroutine, block)

reactive/kotlinx-coroutines-reactive/test/IntegrationTest.kt

+4-5
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ class IntegrationTest(
2020
) : TestBase() {
2121

2222
enum class Ctx {
23-
MAIN { override fun invoke(context: CoroutineContext): CoroutineContext = context },
23+
MAIN { override fun invoke(context: CoroutineContext): CoroutineContext = context.minusKey(Job) },
2424
DEFAULT { override fun invoke(context: CoroutineContext): CoroutineContext = Dispatchers.Default },
2525
UNCONFINED { override fun invoke(context: CoroutineContext): CoroutineContext = Dispatchers.Unconfined };
2626

@@ -39,7 +39,7 @@ class IntegrationTest(
3939

4040
@Test
4141
fun testEmpty(): Unit = runBlocking {
42-
val pub = CoroutineScope(ctx(coroutineContext)).publish<String> {
42+
val pub = publish<String>(ctx(coroutineContext)) {
4343
if (delay) delay(1)
4444
// does not send anything
4545
}
@@ -77,7 +77,7 @@ class IntegrationTest(
7777
@Test
7878
fun testNumbers() = runBlocking<Unit> {
7979
val n = 100 * stressTestMultiplier
80-
val pub = CoroutineScope(ctx(coroutineContext)).publish {
80+
val pub = publish(ctx(coroutineContext)) {
8181
for (i in 1..n) {
8282
send(i)
8383
if (delay) delay(1)
@@ -99,8 +99,7 @@ class IntegrationTest(
9999
fun testCancelWithoutValue() = runTest {
100100
val job = launch(Job(), start = CoroutineStart.UNDISPATCHED) {
101101
publish<String> {
102-
yield()
103-
expectUnreached()
102+
hang {}
104103
}.awaitFirst()
105104
}
106105

0 commit comments

Comments
 (0)