aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndroid Build Coastguard Worker <android-build-coastguard-worker@google.com>2022-05-10 06:54:17 +0000
committerAndroid Build Coastguard Worker <android-build-coastguard-worker@google.com>2022-05-10 06:54:17 +0000
commit84cbf3f0562cff2482dadd00437bb3d816efdf76 (patch)
treedc67daa53a300a35d4109b4ece5c2ddc6b374c49
parent6967ebb9dfc2a7f8626fe2c67342dee02e051048 (diff)
parent710e5b908991f74c87135b51ab13b053277a8fdd (diff)
downloadkotlinx.coroutines-aml_cbr_331710020.tar.gz
Change-Id: Id7f65f8dbad1c458d3c1ecd31c66c8276da017ff
-rw-r--r--.gitignore1
-rw-r--r--.idea/dictionaries/shared.xml9
-rw-r--r--Android.bp70
-rw-r--r--CHANGES.md177
-rw-r--r--CONTRIBUTING.md8
-rw-r--r--METADATA6
-rw-r--r--README.md163
-rw-r--r--RELEASE.md13
-rw-r--r--benchmarks/build.gradle.kts2
-rw-r--r--benchmarks/src/jmh/java/benchmarks/flow/scrabble/RxJava2PlaysScrabble.java2
-rw-r--r--benchmarks/src/jmh/java/benchmarks/flow/scrabble/RxJava2PlaysScrabbleOpt.java2
-rw-r--r--benchmarks/src/jmh/java/benchmarks/flow/scrabble/optimizations/FlowableCharSequence.java2
-rw-r--r--benchmarks/src/jmh/java/benchmarks/flow/scrabble/optimizations/FlowableSplit.java2
-rw-r--r--benchmarks/src/jmh/java/benchmarks/flow/scrabble/optimizations/StringFlowable.java2
-rw-r--r--benchmarks/src/jmh/kotlin/benchmarks/ChannelProducerConsumerBenchmark.kt2
-rw-r--r--benchmarks/src/jmh/kotlin/benchmarks/ChannelSinkBenchmark.kt6
-rw-r--r--benchmarks/src/jmh/kotlin/benchmarks/ChannelSinkDepthBenchmark.kt91
-rw-r--r--benchmarks/src/jmh/kotlin/benchmarks/ParametrizedDispatcherBase.kt2
-rw-r--r--benchmarks/src/jmh/kotlin/benchmarks/SemaphoreBenchmark.kt2
-rw-r--r--benchmarks/src/jmh/kotlin/benchmarks/akka/PingPongAkkaBenchmark.kt2
-rw-r--r--benchmarks/src/jmh/kotlin/benchmarks/akka/StatefulActorAkkaBenchmark.kt2
-rw-r--r--benchmarks/src/jmh/kotlin/benchmarks/flow/CombineFlowsBenchmark.kt2
-rw-r--r--benchmarks/src/jmh/kotlin/benchmarks/flow/CombineTwoFlowsBenchmark.kt2
-rw-r--r--benchmarks/src/jmh/kotlin/benchmarks/flow/FlatMapMergeBenchmark.kt2
-rw-r--r--benchmarks/src/jmh/kotlin/benchmarks/flow/FlowFlattenMergeBenchmark.kt2
-rw-r--r--benchmarks/src/jmh/kotlin/benchmarks/flow/NumbersBenchmark.kt2
-rw-r--r--benchmarks/src/jmh/kotlin/benchmarks/flow/SafeFlowBenchmark.kt2
-rw-r--r--benchmarks/src/jmh/kotlin/benchmarks/flow/TakeBenchmark.kt2
-rw-r--r--benchmarks/src/jmh/kotlin/benchmarks/flow/TakeWhileBenchmark.kt2
-rw-r--r--benchmarks/src/jmh/kotlin/benchmarks/flow/scrabble/FlowPlaysScrabbleBase.kt2
-rw-r--r--benchmarks/src/jmh/kotlin/benchmarks/flow/scrabble/FlowPlaysScrabbleOpt.kt2
-rw-r--r--benchmarks/src/jmh/kotlin/benchmarks/flow/scrabble/IterableSpliterator.kt2
-rw-r--r--benchmarks/src/jmh/kotlin/benchmarks/flow/scrabble/ReactorPlaysScrabble.kt2
-rw-r--r--benchmarks/src/jmh/kotlin/benchmarks/flow/scrabble/SaneFlowPlaysScrabble.kt2
-rw-r--r--benchmarks/src/jmh/kotlin/benchmarks/flow/scrabble/SequencePlaysScrabble.kt2
-rw-r--r--benchmarks/src/jmh/kotlin/benchmarks/flow/scrabble/ShakespearePlaysScrabble.kt2
-rw-r--r--benchmarks/src/jmh/kotlin/benchmarks/scheduler/DispatchersContextSwitchBenchmark.kt2
-rw-r--r--benchmarks/src/jmh/kotlin/benchmarks/scheduler/ForkJoinBenchmark.kt2
-rw-r--r--benchmarks/src/jmh/kotlin/benchmarks/scheduler/LaunchBenchmark.kt2
-rw-r--r--benchmarks/src/jmh/kotlin/benchmarks/scheduler/StatefulAwaitsBenchmark.kt2
-rw-r--r--benchmarks/src/jmh/kotlin/benchmarks/scheduler/actors/ConcurrentStatefulActorBenchmark.kt2
-rw-r--r--benchmarks/src/jmh/kotlin/benchmarks/scheduler/actors/CycledActorsBenchmark.kt2
-rw-r--r--benchmarks/src/jmh/kotlin/benchmarks/scheduler/actors/PingPongActorBenchmark.kt2
-rw-r--r--benchmarks/src/jmh/kotlin/benchmarks/scheduler/actors/PingPongWithBlockingContext.kt2
-rw-r--r--benchmarks/src/jmh/kotlin/benchmarks/scheduler/actors/StatefulActorBenchmark.kt2
-rw-r--r--benchmarks/src/jmh/kotlin/benchmarks/tailcall/SimpleChannel.kt2
-rw-r--r--benchmarks/src/jmh/kotlin/benchmarks/tailcall/SimpleChannelBenchmark.kt2
-rw-r--r--benchmarks/src/main/kotlin/benchmarks/common/BenchmarkUtils.kt2
-rw-r--r--build.gradle146
-rw-r--r--buildSrc/build.gradle.kts9
-rw-r--r--buildSrc/settings.gradle.kts3
-rw-r--r--buildSrc/src/main/kotlin/CacheRedirector.kt38
-rw-r--r--buildSrc/src/main/kotlin/Dokka.kt26
-rw-r--r--buildSrc/src/main/kotlin/Idea.kt4
-rw-r--r--buildSrc/src/main/kotlin/MavenCentral.kt37
-rw-r--r--buildSrc/src/main/kotlin/Platform.kt4
-rw-r--r--buildSrc/src/main/kotlin/Projects.kt4
-rw-r--r--buildSrc/src/main/kotlin/Properties.kt2
-rw-r--r--buildSrc/src/main/kotlin/Publishing.kt75
-rw-r--r--buildSrc/src/main/kotlin/RunR8.kt52
-rw-r--r--buildSrc/src/main/kotlin/UnpackAar.kt4
-rw-r--r--buildSrc/src/main/kotlin/jdk-convention.gradle.kts4
-rw-r--r--buildSrc/src/main/kotlin/kotlin-js-conventions.gradle.kts40
-rw-r--r--buildSrc/src/main/kotlin/kotlin-jvm-conventions.gradle.kts40
-rwxr-xr-xbump-version.sh2
-rw-r--r--coroutines-guide.md132
-rw-r--r--docs/_nav.yml30
-rw-r--r--docs/basics.md411
-rw-r--r--docs/cancellation-and-timeouts.md483
-rw-r--r--docs/cfg/buildprofiles.xml10
-rw-r--r--docs/channels.md703
-rw-r--r--docs/compatibility.md126
-rw-r--r--docs/composing-suspending-functions.md436
-rw-r--r--docs/coroutine-context-and-dispatchers.md713
-rw-r--r--docs/coroutines-guide.md33
-rw-r--r--docs/debugging.md107
-rw-r--r--docs/exception-handling.md527
-rw-r--r--docs/flow.md1980
-rw-r--r--docs/images/coroutine-breakpoint.pngbin0 -> 106968 bytes
-rw-r--r--docs/images/coroutine-debug-1.pngbin0 -> 226219 bytes
-rw-r--r--docs/images/coroutine-debug-2.pngbin0 -> 209165 bytes
-rw-r--r--docs/images/coroutine-debug-3.pngbin0 -> 203521 bytes
-rw-r--r--docs/images/flow-breakpoint.pngbin0 -> 111598 bytes
-rw-r--r--docs/images/flow-build-project.pngbin0 -> 44472 bytes
-rw-r--r--docs/images/flow-debug-1.pngbin0 -> 313682 bytes
-rw-r--r--docs/images/flow-debug-2.pngbin0 -> 314131 bytes
-rw-r--r--docs/images/flow-debug-3.pngbin0 -> 224245 bytes
-rw-r--r--docs/images/flow-debug-4.pngbin0 -> 221360 bytes
-rw-r--r--docs/images/flow-debug-project.pngbin0 -> 44199 bytes
-rw-r--r--docs/images/flow-resume-debug.pngbin0 -> 101404 bytes
-rw-r--r--docs/images/new-gradle-project-jvm.pngbin0 -> 150223 bytes
-rw-r--r--docs/kc.tree25
-rw-r--r--docs/knit.properties10
-rw-r--r--docs/project.ihp14
-rw-r--r--docs/select-expression.md550
-rw-r--r--docs/shared-mutable-state-and-concurrency.md540
-rw-r--r--docs/topics/cancellation-and-timeouts.md461
-rw-r--r--docs/topics/channels.md649
-rw-r--r--docs/topics/compatibility.md129
-rw-r--r--docs/topics/composing-suspending-functions.md416
-rw-r--r--docs/topics/coroutine-context-and-dispatchers.md685
-rw-r--r--docs/topics/coroutines-basics.md288
-rw-r--r--docs/topics/coroutines-guide.md38
-rw-r--r--docs/topics/debug-coroutines-with-idea.md82
-rw-r--r--docs/topics/debug-flow-with-idea.md126
-rw-r--r--docs/topics/debugging.md115
-rw-r--r--docs/topics/exception-handling.md533
-rw-r--r--docs/topics/flow.md1896
-rw-r--r--docs/topics/knit.properties9
-rw-r--r--docs/topics/select-expression.md507
-rw-r--r--docs/topics/shared-mutable-state-and-concurrency.md513
-rw-r--r--gradle.properties32
-rw-r--r--gradle/compile-common.gradle2
-rw-r--r--gradle/compile-js-multiplatform.gradle13
-rw-r--r--gradle/compile-js.gradle32
-rw-r--r--gradle/compile-jvm-multiplatform.gradle6
-rw-r--r--gradle/compile-jvm.gradle34
-rw-r--r--gradle/compile-native-multiplatform.gradle7
-rw-r--r--gradle/dokka.gradle93
-rw-r--r--gradle/dokka.gradle.kts75
-rw-r--r--gradle/node-js.gradle2
-rw-r--r--gradle/opt-in.gradle (renamed from gradle/experimental.gradle)8
-rw-r--r--gradle/publish-mpp-root-module-in-platform.gradle55
-rw-r--r--gradle/publish-npm-js.gradle2
-rw-r--r--gradle/publish.gradle (renamed from gradle/publish-bintray.gradle)56
-rw-r--r--gradle/test-mocha-js.gradle2
-rw-r--r--gradle/wrapper/gradle-wrapper.jarbin56172 -> 59203 bytes
-rw-r--r--gradle/wrapper/gradle-wrapper.properties4
-rwxr-xr-xgradlew41
-rw-r--r--gradlew.bat43
-rw-r--r--integration-testing/build.gradle18
-rw-r--r--integration-testing/src/coreAgentTest/kotlin/CoreAgentTest.kt2
-rw-r--r--integration-testing/src/debugAgentTest/kotlin/DebugAgentTest.kt2
-rw-r--r--integration-testing/src/debugAgentTest/kotlin/DebugProbes.kt4
-rw-r--r--integration-testing/src/debugAgentTest/kotlin/PrecompiledDebugProbesTest.kt4
-rw-r--r--integration-testing/src/mavenTest/kotlin/MavenPublicationValidator.kt2
-rw-r--r--integration-testing/src/npmTest/kotlin/NpmPublicationValidator.kt2
-rw-r--r--integration/kotlinx-coroutines-guava/README.md15
-rw-r--r--integration/kotlinx-coroutines-guava/build.gradle.kts2
-rw-r--r--integration/kotlinx-coroutines-guava/src/ListenableFuture.kt321
-rw-r--r--integration/kotlinx-coroutines-guava/test/FutureAsDeferredUnhandledCompletionExceptionTest.kt46
-rw-r--r--integration/kotlinx-coroutines-guava/test/ListenableFutureTest.kt237
-rw-r--r--integration/kotlinx-coroutines-guava/test/ListenableFutureToStringTest.kt69
-rw-r--r--integration/kotlinx-coroutines-jdk8/README.md12
-rw-r--r--integration/kotlinx-coroutines-jdk8/build.gradle.kts2
-rw-r--r--integration/kotlinx-coroutines-jdk8/src/future/Future.kt56
-rw-r--r--integration/kotlinx-coroutines-jdk8/src/stream/Stream.kt2
-rw-r--r--integration/kotlinx-coroutines-jdk8/src/time/Time.kt2
-rw-r--r--integration/kotlinx-coroutines-jdk8/test/future/FutureAsDeferredUnhandledCompletionExceptionTest.kt38
-rw-r--r--integration/kotlinx-coroutines-jdk8/test/future/FutureTest.kt106
-rw-r--r--integration/kotlinx-coroutines-jdk8/test/time/FlowSampleTest.kt5
-rw-r--r--integration/kotlinx-coroutines-play-services/README.md10
-rw-r--r--integration/kotlinx-coroutines-play-services/api/kotlinx-coroutines-play-services.api2
-rw-r--r--integration/kotlinx-coroutines-play-services/build.gradle45
-rw-r--r--integration/kotlinx-coroutines-play-services/build.gradle.kts41
-rw-r--r--integration/kotlinx-coroutines-play-services/src/Tasks.kt101
-rw-r--r--integration/kotlinx-coroutines-play-services/test/TaskTest.kt265
-rw-r--r--integration/kotlinx-coroutines-slf4j/README.md2
-rw-r--r--integration/kotlinx-coroutines-slf4j/api/kotlinx-coroutines-slf4j.api4
-rw-r--r--integration/kotlinx-coroutines-slf4j/build.gradle.kts2
-rw-r--r--integration/kotlinx-coroutines-slf4j/src/MDCContext.kt2
-rw-r--r--js/example-frontend-js/build.gradle33
-rw-r--r--js/example-frontend-js/build.gradle.kts32
-rw-r--r--js/example-frontend-js/src/ExampleMain.kt2
-rw-r--r--js/example-frontend-js/src/main/web/style.css2
-rw-r--r--js/js-stub/README.md1
-rw-r--r--js/js-stub/build.gradle.kts11
-rw-r--r--js/js-stub/src/Performance.kt9
-rw-r--r--js/js-stub/src/Promise.kt7
-rw-r--r--js/js-stub/src/Window.kt7
-rw-r--r--knit.properties4
-rw-r--r--kotlinx-coroutines-bom/build.gradle2
-rw-r--r--kotlinx-coroutines-core/README.md133
-rw-r--r--kotlinx-coroutines-core/api/kotlinx-coroutines-core.api231
-rw-r--r--kotlinx-coroutines-core/build.gradle78
-rw-r--r--kotlinx-coroutines-core/common/README.md32
-rw-r--r--kotlinx-coroutines-core/common/src/AbstractCoroutine.kt70
-rw-r--r--kotlinx-coroutines-core/common/src/Annotations.kt20
-rw-r--r--kotlinx-coroutines-core/common/src/Await.kt7
-rw-r--r--kotlinx-coroutines-core/common/src/Builders.common.kt27
-rw-r--r--kotlinx-coroutines-core/common/src/CancellableContinuation.kt14
-rw-r--r--kotlinx-coroutines-core/common/src/CancellableContinuationImpl.kt136
-rw-r--r--kotlinx-coroutines-core/common/src/CompletableDeferred.kt4
-rw-r--r--kotlinx-coroutines-core/common/src/CompletableJob.kt4
-rw-r--r--kotlinx-coroutines-core/common/src/CompletionHandler.common.kt2
-rw-r--r--kotlinx-coroutines-core/common/src/CompletionState.kt2
-rw-r--r--kotlinx-coroutines-core/common/src/CoroutineContext.common.kt3
-rw-r--r--kotlinx-coroutines-core/common/src/CoroutineDispatcher.kt12
-rw-r--r--kotlinx-coroutines-core/common/src/CoroutineExceptionHandler.kt2
-rw-r--r--kotlinx-coroutines-core/common/src/CoroutineName.kt2
-rw-r--r--kotlinx-coroutines-core/common/src/CoroutineScope.kt98
-rw-r--r--kotlinx-coroutines-core/common/src/CoroutineStart.kt13
-rw-r--r--kotlinx-coroutines-core/common/src/Debug.common.kt8
-rw-r--r--kotlinx-coroutines-core/common/src/Deferred.kt2
-rw-r--r--kotlinx-coroutines-core/common/src/Delay.kt4
-rw-r--r--kotlinx-coroutines-core/common/src/Dispatchers.common.kt2
-rw-r--r--kotlinx-coroutines-core/common/src/EventLoop.common.kt2
-rw-r--r--kotlinx-coroutines-core/common/src/Exceptions.common.kt2
-rw-r--r--kotlinx-coroutines-core/common/src/Job.kt49
-rw-r--r--kotlinx-coroutines-core/common/src/JobSupport.kt134
-rw-r--r--kotlinx-coroutines-core/common/src/MainCoroutineDispatcher.kt2
-rw-r--r--kotlinx-coroutines-core/common/src/NonCancellable.kt38
-rw-r--r--kotlinx-coroutines-core/common/src/Runnable.common.kt2
-rw-r--r--kotlinx-coroutines-core/common/src/SchedulerTask.common.kt2
-rw-r--r--kotlinx-coroutines-core/common/src/Supervisor.kt12
-rw-r--r--kotlinx-coroutines-core/common/src/Timeout.kt2
-rw-r--r--kotlinx-coroutines-core/common/src/Unconfined.kt2
-rw-r--r--kotlinx-coroutines-core/common/src/Yield.kt12
-rw-r--r--kotlinx-coroutines-core/common/src/channels/AbstractChannel.kt136
-rw-r--r--kotlinx-coroutines-core/common/src/channels/ArrayBroadcastChannel.kt2
-rw-r--r--kotlinx-coroutines-core/common/src/channels/ArrayChannel.kt5
-rw-r--r--kotlinx-coroutines-core/common/src/channels/Broadcast.kt23
-rw-r--r--kotlinx-coroutines-core/common/src/channels/BroadcastChannel.kt14
-rw-r--r--kotlinx-coroutines-core/common/src/channels/BufferOverflow.kt2
-rw-r--r--kotlinx-coroutines-core/common/src/channels/Channel.kt455
-rw-r--r--kotlinx-coroutines-core/common/src/channels/ChannelCoroutine.kt7
-rw-r--r--kotlinx-coroutines-core/common/src/channels/Channels.common.kt2123
-rw-r--r--kotlinx-coroutines-core/common/src/channels/ConflatedBroadcastChannel.kt21
-rw-r--r--kotlinx-coroutines-core/common/src/channels/ConflatedChannel.kt9
-rw-r--r--kotlinx-coroutines-core/common/src/channels/Deprecated.kt478
-rw-r--r--kotlinx-coroutines-core/common/src/channels/LinkedListChannel.kt18
-rw-r--r--kotlinx-coroutines-core/common/src/channels/Produce.kt4
-rw-r--r--kotlinx-coroutines-core/common/src/channels/RendezvousChannel.kt2
-rw-r--r--kotlinx-coroutines-core/common/src/flow/Builders.kt19
-rw-r--r--kotlinx-coroutines-core/common/src/flow/Channels.kt59
-rw-r--r--kotlinx-coroutines-core/common/src/flow/Flow.kt4
-rw-r--r--kotlinx-coroutines-core/common/src/flow/FlowCollector.kt2
-rw-r--r--kotlinx-coroutines-core/common/src/flow/Migration.kt14
-rw-r--r--kotlinx-coroutines-core/common/src/flow/SharedFlow.kt52
-rw-r--r--kotlinx-coroutines-core/common/src/flow/SharingStarted.kt18
-rw-r--r--kotlinx-coroutines-core/common/src/flow/StateFlow.kt71
-rw-r--r--kotlinx-coroutines-core/common/src/flow/internal/AbstractSharedFlow.kt4
-rw-r--r--kotlinx-coroutines-core/common/src/flow/internal/ChannelFlow.kt20
-rw-r--r--kotlinx-coroutines-core/common/src/flow/internal/Combine.kt14
-rw-r--r--kotlinx-coroutines-core/common/src/flow/internal/FlowCoroutine.kt2
-rw-r--r--kotlinx-coroutines-core/common/src/flow/internal/FlowExceptions.common.kt2
-rw-r--r--kotlinx-coroutines-core/common/src/flow/internal/Merge.kt2
-rw-r--r--kotlinx-coroutines-core/common/src/flow/internal/NopCollector.kt2
-rw-r--r--kotlinx-coroutines-core/common/src/flow/internal/NullSurrogate.kt2
-rw-r--r--kotlinx-coroutines-core/common/src/flow/internal/SafeCollector.common.kt2
-rw-r--r--kotlinx-coroutines-core/common/src/flow/internal/SendingCollector.kt2
-rw-r--r--kotlinx-coroutines-core/common/src/flow/operators/Context.kt12
-rw-r--r--kotlinx-coroutines-core/common/src/flow/operators/Delay.kt40
-rw-r--r--kotlinx-coroutines-core/common/src/flow/operators/Distinct.kt4
-rw-r--r--kotlinx-coroutines-core/common/src/flow/operators/Emitters.kt12
-rw-r--r--kotlinx-coroutines-core/common/src/flow/operators/Errors.kt2
-rw-r--r--kotlinx-coroutines-core/common/src/flow/operators/Limit.kt2
-rw-r--r--kotlinx-coroutines-core/common/src/flow/operators/Lint.kt65
-rw-r--r--kotlinx-coroutines-core/common/src/flow/operators/Merge.kt15
-rw-r--r--kotlinx-coroutines-core/common/src/flow/operators/Share.kt29
-rw-r--r--kotlinx-coroutines-core/common/src/flow/operators/Transform.kt22
-rw-r--r--kotlinx-coroutines-core/common/src/flow/operators/Zip.kt4
-rw-r--r--kotlinx-coroutines-core/common/src/flow/terminal/Collect.kt14
-rw-r--r--kotlinx-coroutines-core/common/src/flow/terminal/Collection.kt2
-rw-r--r--kotlinx-coroutines-core/common/src/flow/terminal/Count.kt2
-rw-r--r--kotlinx-coroutines-core/common/src/flow/terminal/Reduce.kt27
-rw-r--r--kotlinx-coroutines-core/common/src/internal/ArrayQueue.kt2
-rw-r--r--kotlinx-coroutines-core/common/src/internal/Atomic.kt2
-rw-r--r--kotlinx-coroutines-core/common/src/internal/Concurrent.common.kt2
-rw-r--r--kotlinx-coroutines-core/common/src/internal/ConcurrentLinkedList.kt9
-rw-r--r--kotlinx-coroutines-core/common/src/internal/DispatchedContinuation.kt73
-rw-r--r--kotlinx-coroutines-core/common/src/internal/DispatchedTask.kt12
-rw-r--r--kotlinx-coroutines-core/common/src/internal/InlineList.kt10
-rw-r--r--kotlinx-coroutines-core/common/src/internal/LocalAtomics.common.kt2
-rw-r--r--kotlinx-coroutines-core/common/src/internal/LockFreeLinkedList.common.kt2
-rw-r--r--kotlinx-coroutines-core/common/src/internal/LockFreeTaskQueue.kt2
-rw-r--r--kotlinx-coroutines-core/common/src/internal/MainDispatcherFactory.kt2
-rw-r--r--kotlinx-coroutines-core/common/src/internal/OnUndeliveredElement.kt2
-rw-r--r--kotlinx-coroutines-core/common/src/internal/ProbesSupport.common.kt2
-rw-r--r--kotlinx-coroutines-core/common/src/internal/Scopes.kt11
-rw-r--r--kotlinx-coroutines-core/common/src/internal/StackTraceRecovery.common.kt3
-rw-r--r--kotlinx-coroutines-core/common/src/internal/Symbol.kt4
-rw-r--r--kotlinx-coroutines-core/common/src/internal/Synchronized.common.kt2
-rw-r--r--kotlinx-coroutines-core/common/src/internal/SystemProps.common.kt2
-rw-r--r--kotlinx-coroutines-core/common/src/internal/ThreadContext.common.kt2
-rw-r--r--kotlinx-coroutines-core/common/src/internal/ThreadLocal.common.kt2
-rw-r--r--kotlinx-coroutines-core/common/src/internal/ThreadSafeHeap.kt2
-rw-r--r--kotlinx-coroutines-core/common/src/intrinsics/Cancellable.kt17
-rw-r--r--kotlinx-coroutines-core/common/src/intrinsics/Undispatched.kt10
-rw-r--r--kotlinx-coroutines-core/common/src/selects/Select.kt29
-rw-r--r--kotlinx-coroutines-core/common/src/selects/SelectUnbiased.kt4
-rw-r--r--kotlinx-coroutines-core/common/src/selects/WhileSelect.kt2
-rw-r--r--kotlinx-coroutines-core/common/src/sync/Mutex.kt75
-rw-r--r--kotlinx-coroutines-core/common/src/sync/Semaphore.kt9
-rw-r--r--kotlinx-coroutines-core/common/test/AbstractCoroutineTest.kt8
-rw-r--r--kotlinx-coroutines-core/common/test/AsyncLazyTest.kt8
-rw-r--r--kotlinx-coroutines-core/common/test/AsyncTest.kt38
-rw-r--r--kotlinx-coroutines-core/common/test/AtomicCancellationCommonTest.kt38
-rw-r--r--kotlinx-coroutines-core/common/test/AwaitTest.kt28
-rw-r--r--kotlinx-coroutines-core/common/test/BuilderContractsTest.kt20
-rw-r--r--kotlinx-coroutines-core/common/test/CancellableResumeTest.kt4
-rw-r--r--kotlinx-coroutines-core/common/test/CancelledParentAttachTest.kt115
-rw-r--r--kotlinx-coroutines-core/common/test/CoroutineDispatcherOperatorFunInvokeTest.kt1
-rw-r--r--kotlinx-coroutines-core/common/test/TestBase.common.kt17
-rw-r--r--kotlinx-coroutines-core/common/test/UndispatchedResultTest.kt2
-rw-r--r--kotlinx-coroutines-core/common/test/WithTimeoutDurationTest.kt28
-rw-r--r--kotlinx-coroutines-core/common/test/WithTimeoutOrNullDurationTest.kt43
-rw-r--r--kotlinx-coroutines-core/common/test/WithTimeoutOrNullTest.kt3
-rw-r--r--kotlinx-coroutines-core/common/test/channels/ArrayBroadcastChannelTest.kt16
-rw-r--r--kotlinx-coroutines-core/common/test/channels/ArrayChannelTest.kt34
-rw-r--r--kotlinx-coroutines-core/common/test/channels/BasicOperationsTest.kt114
-rw-r--r--kotlinx-coroutines-core/common/test/channels/ChannelBufferOverflowTest.kt26
-rw-r--r--kotlinx-coroutines-core/common/test/channels/ChannelReceiveCatchingTest.kt146
-rw-r--r--kotlinx-coroutines-core/common/test/channels/ChannelReceiveOrClosedTest.kt135
-rw-r--r--kotlinx-coroutines-core/common/test/channels/ChannelUndeliveredElementFailureTest.kt36
-rw-r--r--kotlinx-coroutines-core/common/test/channels/ChannelUndeliveredElementTest.kt77
-rw-r--r--kotlinx-coroutines-core/common/test/channels/ConflatedBroadcastChannelTest.kt6
-rw-r--r--kotlinx-coroutines-core/common/test/channels/ConflatedChannelTest.kt24
-rw-r--r--kotlinx-coroutines-core/common/test/channels/LinkedListChannelTest.kt14
-rw-r--r--kotlinx-coroutines-core/common/test/channels/ProduceTest.kt29
-rw-r--r--kotlinx-coroutines-core/common/test/channels/RendezvousChannelTest.kt28
-rw-r--r--kotlinx-coroutines-core/common/test/channels/TestChannelKind.kt15
-rw-r--r--kotlinx-coroutines-core/common/test/flow/VirtualTime.kt3
-rw-r--r--kotlinx-coroutines-core/common/test/flow/channels/ChannelBuildersFlowTest.kt139
-rw-r--r--kotlinx-coroutines-core/common/test/flow/channels/ChannelFlowTest.kt37
-rw-r--r--kotlinx-coroutines-core/common/test/flow/internal/FlowScopeTest.kt4
-rw-r--r--kotlinx-coroutines-core/common/test/flow/operators/CatchTest.kt2
-rw-r--r--kotlinx-coroutines-core/common/test/flow/operators/DebounceTest.kt22
-rw-r--r--kotlinx-coroutines-core/common/test/flow/operators/FlatMapMergeBaseTest.kt2
-rw-r--r--kotlinx-coroutines-core/common/test/flow/operators/OnCompletionTest.kt23
-rw-r--r--kotlinx-coroutines-core/common/test/flow/operators/OnEachTest.kt1
-rw-r--r--kotlinx-coroutines-core/common/test/flow/operators/SampleTest.kt11
-rw-r--r--kotlinx-coroutines-core/common/test/flow/operators/ScanTest.kt7
-rw-r--r--kotlinx-coroutines-core/common/test/flow/sharing/ShareInFusionTest.kt4
-rw-r--r--kotlinx-coroutines-core/common/test/flow/sharing/ShareInTest.kt14
-rw-r--r--kotlinx-coroutines-core/common/test/flow/sharing/SharedFlowTest.kt9
-rw-r--r--kotlinx-coroutines-core/common/test/flow/sharing/SharingStartedWhileSubscribedTest.kt6
-rw-r--r--kotlinx-coroutines-core/common/test/flow/sharing/StateFlowTest.kt26
-rw-r--r--kotlinx-coroutines-core/common/test/flow/terminal/FoldTest.kt1
-rw-r--r--kotlinx-coroutines-core/common/test/flow/terminal/LastTest.kt45
-rw-r--r--kotlinx-coroutines-core/common/test/flow/terminal/ReduceTest.kt3
-rw-r--r--kotlinx-coroutines-core/common/test/selects/SelectArrayChannelTest.kt28
-rw-r--r--kotlinx-coroutines-core/common/test/selects/SelectLoopTest.kt6
-rw-r--r--kotlinx-coroutines-core/common/test/selects/SelectRendezvousChannelTest.kt32
-rw-r--r--kotlinx-coroutines-core/common/test/selects/SelectTimeoutDurationTest.kt16
-rw-r--r--kotlinx-coroutines-core/common/test/sync/MutexTest.kt3
-rw-r--r--kotlinx-coroutines-core/js/src/CompletionHandler.kt2
-rw-r--r--kotlinx-coroutines-core/js/src/CoroutineContext.kt21
-rw-r--r--kotlinx-coroutines-core/js/src/CoroutineExceptionHandlerImpl.kt2
-rw-r--r--kotlinx-coroutines-core/js/src/Debug.kt2
-rw-r--r--kotlinx-coroutines-core/js/src/Dispatchers.kt2
-rw-r--r--kotlinx-coroutines-core/js/src/EventLoop.kt2
-rw-r--r--kotlinx-coroutines-core/js/src/Exceptions.kt9
-rw-r--r--kotlinx-coroutines-core/js/src/JSDispatcher.kt2
-rw-r--r--kotlinx-coroutines-core/js/src/Promise.kt2
-rw-r--r--kotlinx-coroutines-core/js/src/Runnable.kt2
-rw-r--r--kotlinx-coroutines-core/js/src/SchedulerTask.kt2
-rw-r--r--kotlinx-coroutines-core/js/src/Window.kt2
-rw-r--r--kotlinx-coroutines-core/js/src/flow/internal/FlowExceptions.kt2
-rw-r--r--kotlinx-coroutines-core/js/src/flow/internal/SafeCollector.kt2
-rw-r--r--kotlinx-coroutines-core/js/src/internal/Concurrent.kt2
-rw-r--r--kotlinx-coroutines-core/js/src/internal/CopyOnWriteList.kt2
-rw-r--r--kotlinx-coroutines-core/js/src/internal/LinkedList.kt17
-rw-r--r--kotlinx-coroutines-core/js/src/internal/LocalAtomics.kt2
-rw-r--r--kotlinx-coroutines-core/js/src/internal/ProbesSupport.kt2
-rw-r--r--kotlinx-coroutines-core/js/src/internal/StackTraceRecovery.kt2
-rw-r--r--kotlinx-coroutines-core/js/src/internal/Synchronized.kt2
-rw-r--r--kotlinx-coroutines-core/js/src/internal/SystemProps.kt2
-rw-r--r--kotlinx-coroutines-core/js/src/internal/ThreadContext.kt2
-rw-r--r--kotlinx-coroutines-core/js/src/internal/ThreadLocal.kt2
-rw-r--r--kotlinx-coroutines-core/js/test/PromiseTest.kt14
-rw-r--r--kotlinx-coroutines-core/js/test/TestBase.kt34
-rw-r--r--kotlinx-coroutines-core/jvm/resources/DebugProbesKt.binbin1728 -> 1714 bytes
-rw-r--r--kotlinx-coroutines-core/jvm/resources/META-INF/proguard/coroutines.pro4
-rw-r--r--kotlinx-coroutines-core/jvm/src/AbstractTimeSource.kt (renamed from kotlinx-coroutines-core/jvm/src/TimeSource.kt)24
-rw-r--r--kotlinx-coroutines-core/jvm/src/Builders.kt7
-rw-r--r--kotlinx-coroutines-core/jvm/src/CommonPool.kt6
-rw-r--r--kotlinx-coroutines-core/jvm/src/CompletionHandler.kt2
-rw-r--r--kotlinx-coroutines-core/jvm/src/CoroutineContext.kt100
-rw-r--r--kotlinx-coroutines-core/jvm/src/CoroutineExceptionHandlerImpl.kt2
-rw-r--r--kotlinx-coroutines-core/jvm/src/Debug.kt2
-rw-r--r--kotlinx-coroutines-core/jvm/src/DebugStrings.kt2
-rw-r--r--kotlinx-coroutines-core/jvm/src/DefaultExecutor.kt2
-rw-r--r--kotlinx-coroutines-core/jvm/src/Dispatchers.kt10
-rw-r--r--kotlinx-coroutines-core/jvm/src/EventLoop.kt2
-rw-r--r--kotlinx-coroutines-core/jvm/src/Exceptions.kt2
-rw-r--r--kotlinx-coroutines-core/jvm/src/Executors.kt82
-rw-r--r--kotlinx-coroutines-core/jvm/src/Future.kt16
-rw-r--r--kotlinx-coroutines-core/jvm/src/Interruptible.kt2
-rw-r--r--kotlinx-coroutines-core/jvm/src/Runnable.kt2
-rw-r--r--kotlinx-coroutines-core/jvm/src/SchedulerTask.kt2
-rw-r--r--kotlinx-coroutines-core/jvm/src/ThreadContextElement.kt2
-rw-r--r--kotlinx-coroutines-core/jvm/src/ThreadPoolDispatcher.kt45
-rw-r--r--kotlinx-coroutines-core/jvm/src/channels/Actor.kt14
-rw-r--r--kotlinx-coroutines-core/jvm/src/channels/Channels.kt81
-rw-r--r--kotlinx-coroutines-core/jvm/src/channels/TickerChannels.kt10
-rw-r--r--kotlinx-coroutines-core/jvm/src/debug/AgentPremain.kt2
-rw-r--r--kotlinx-coroutines-core/jvm/src/debug/internal/ConcurrentWeakMap.kt2
-rw-r--r--kotlinx-coroutines-core/jvm/src/debug/internal/DebugCoroutineInfo.kt4
-rw-r--r--kotlinx-coroutines-core/jvm/src/debug/internal/DebugCoroutineInfoImpl.kt4
-rw-r--r--kotlinx-coroutines-core/jvm/src/debug/internal/DebugProbes.kt22
-rw-r--r--kotlinx-coroutines-core/jvm/src/debug/internal/DebugProbesImpl.kt68
-rw-r--r--kotlinx-coroutines-core/jvm/src/debug/internal/DebuggerInfo.kt2
-rw-r--r--kotlinx-coroutines-core/jvm/src/debug/internal/StackTraceFrame.kt4
-rw-r--r--kotlinx-coroutines-core/jvm/src/flow/internal/FlowExceptions.kt2
-rw-r--r--kotlinx-coroutines-core/jvm/src/flow/internal/SafeCollector.kt4
-rw-r--r--kotlinx-coroutines-core/jvm/src/internal/Concurrent.kt4
-rw-r--r--kotlinx-coroutines-core/jvm/src/internal/ExceptionsConstuctor.kt2
-rw-r--r--kotlinx-coroutines-core/jvm/src/internal/FastServiceLoader.kt2
-rw-r--r--kotlinx-coroutines-core/jvm/src/internal/LocalAtomics.kt2
-rw-r--r--kotlinx-coroutines-core/jvm/src/internal/LockFreeLinkedList.kt12
-rw-r--r--kotlinx-coroutines-core/jvm/src/internal/MainDispatchers.kt9
-rw-r--r--kotlinx-coroutines-core/jvm/src/internal/ProbesSupport.kt2
-rw-r--r--kotlinx-coroutines-core/jvm/src/internal/StackTraceRecovery.kt19
-rw-r--r--kotlinx-coroutines-core/jvm/src/internal/Synchronized.kt2
-rw-r--r--kotlinx-coroutines-core/jvm/src/internal/SystemProps.kt2
-rw-r--r--kotlinx-coroutines-core/jvm/src/internal/ThreadContext.kt45
-rw-r--r--kotlinx-coroutines-core/jvm/src/internal/ThreadLocal.kt2
-rw-r--r--kotlinx-coroutines-core/jvm/src/scheduling/CoroutineScheduler.kt30
-rw-r--r--kotlinx-coroutines-core/jvm/src/scheduling/Dispatcher.kt2
-rw-r--r--kotlinx-coroutines-core/jvm/src/scheduling/Tasks.kt9
-rw-r--r--kotlinx-coroutines-core/jvm/src/scheduling/WorkQueue.kt2
-rw-r--r--kotlinx-coroutines-core/jvm/src/test_/TestCoroutineContext.kt2
-rw-r--r--kotlinx-coroutines-core/jvm/test-resources/stacktraces/channels/testCancelledOffer.txt10
-rw-r--r--kotlinx-coroutines-core/jvm/test-resources/stacktraces/channels/testReceiveOrNullFromClosedChannel.txt8
-rw-r--r--kotlinx-coroutines-core/jvm/test-resources/stacktraces/select/testSelectJoin.txt8
-rw-r--r--kotlinx-coroutines-core/jvm/test/AbstractLincheckTest.kt41
-rw-r--r--kotlinx-coroutines-core/jvm/test/AsyncJvmTest.kt5
-rw-r--r--kotlinx-coroutines-core/jvm/test/CancelledAwaitStressTest.kt4
-rw-r--r--kotlinx-coroutines-core/jvm/test/ExecutorAsCoroutineDispatcherDelayTest.kt44
-rw-r--r--kotlinx-coroutines-core/jvm/test/FailFastOnStartTest.kt12
-rw-r--r--kotlinx-coroutines-core/jvm/test/FailingCoroutinesMachineryTest.kt14
-rw-r--r--kotlinx-coroutines-core/jvm/test/FieldWalker.kt11
-rw-r--r--kotlinx-coroutines-core/jvm/test/IntellijIdeaDebuggerEvaluatorCompatibilityTest.kt56
-rw-r--r--kotlinx-coroutines-core/jvm/test/JoinStressTest.kt (renamed from kotlinx-coroutines-core/jvm/test/JoinStrTest.kt)2
-rw-r--r--kotlinx-coroutines-core/jvm/test/LCStressOptionsDefault.kt20
-rw-r--r--kotlinx-coroutines-core/jvm/test/ReusableCancellableContinuationLeakStressTest.kt41
-rw-r--r--kotlinx-coroutines-core/jvm/test/ReusableCancellableContinuationTest.kt2
-rw-r--r--kotlinx-coroutines-core/jvm/test/ReusableContinuationStressTest.kt41
-rw-r--r--kotlinx-coroutines-core/jvm/test/RunBlockingTest.kt13
-rw-r--r--kotlinx-coroutines-core/jvm/test/RunInterruptibleTest.kt2
-rw-r--r--kotlinx-coroutines-core/jvm/test/TestBase.kt47
-rw-r--r--kotlinx-coroutines-core/jvm/test/ThreadContextElementRestoreTest.kt198
-rw-r--r--kotlinx-coroutines-core/jvm/test/ThreadContextOrderTest.kt65
-rw-r--r--kotlinx-coroutines-core/jvm/test/VirtualTimeSource.kt6
-rw-r--r--kotlinx-coroutines-core/jvm/test/channels/ActorLazyTest.kt14
-rw-r--r--kotlinx-coroutines-core/jvm/test/channels/ActorTest.kt31
-rw-r--r--kotlinx-coroutines-core/jvm/test/channels/BroadcastChannelMultiReceiveStressTest.kt16
-rw-r--r--kotlinx-coroutines-core/jvm/test/channels/ChannelCancelUndeliveredElementStressTest.kt36
-rw-r--r--kotlinx-coroutines-core/jvm/test/channels/ChannelLFStressTest.kt107
-rw-r--r--kotlinx-coroutines-core/jvm/test/channels/ChannelSendReceiveStressTest.kt16
-rw-r--r--kotlinx-coroutines-core/jvm/test/channels/ChannelUndeliveredElementStressTest.kt10
-rw-r--r--kotlinx-coroutines-core/jvm/test/channels/ChannelsJvmTest.kt31
-rw-r--r--kotlinx-coroutines-core/jvm/test/channels/ConflatedBroadcastChannelNotifyStressTest.kt6
-rw-r--r--kotlinx-coroutines-core/jvm/test/channels/ConflatedChannelCloseStressTest.kt20
-rw-r--r--kotlinx-coroutines-core/jvm/test/channels/TickerChannelCommonTest.kt14
-rw-r--r--kotlinx-coroutines-core/jvm/test/exceptions/StackTraceRecoveryChannelsTest.kt31
-rw-r--r--kotlinx-coroutines-core/jvm/test/exceptions/StackTraceRecoveryCustomExceptionsTest.kt53
-rw-r--r--kotlinx-coroutines-core/jvm/test/exceptions/StackTraceRecoveryNestedScopesTest.kt1
-rw-r--r--kotlinx-coroutines-core/jvm/test/exceptions/StackTraceRecoveryNestedTest.kt2
-rw-r--r--kotlinx-coroutines-core/jvm/test/exceptions/StackTraceRecoverySelectTest.kt4
-rw-r--r--kotlinx-coroutines-core/jvm/test/exceptions/StackTraceRecoveryTest.kt38
-rw-r--r--kotlinx-coroutines-core/jvm/test/exceptions/SuppressionTests.kt8
-rw-r--r--kotlinx-coroutines-core/jvm/test/flow/CallbackFlowTest.kt6
-rw-r--r--kotlinx-coroutines-core/jvm/test/flow/SharingReferenceTest.kt61
-rw-r--r--kotlinx-coroutines-core/jvm/test/flow/StateFlowStressTest.kt4
-rw-r--r--kotlinx-coroutines-core/jvm/test/flow/StateFlowUpdateStressTest.kt44
-rw-r--r--kotlinx-coroutines-core/jvm/test/guide/example-basic-01.kt9
-rw-r--r--kotlinx-coroutines-core/jvm/test/guide/example-basic-02.kt20
-rw-r--r--kotlinx-coroutines-core/jvm/test/guide/example-basic-03.kt13
-rw-r--r--kotlinx-coroutines-core/jvm/test/guide/example-basic-04.kt20
-rw-r--r--kotlinx-coroutines-core/jvm/test/guide/example-basic-05.kt10
-rw-r--r--kotlinx-coroutines-core/jvm/test/guide/example-basic-06.kt20
-rw-r--r--kotlinx-coroutines-core/jvm/test/guide/example-basic-07.kt19
-rw-r--r--kotlinx-coroutines-core/jvm/test/guide/example-basic-08.kt17
-rw-r--r--kotlinx-coroutines-core/jvm/test/guide/example-basic-09.kt18
-rw-r--r--kotlinx-coroutines-core/jvm/test/guide/example-compose-04.kt2
-rw-r--r--kotlinx-coroutines-core/jvm/test/guide/example-context-06.kt6
-rw-r--r--kotlinx-coroutines-core/jvm/test/guide/example-exceptions-01.kt1
-rw-r--r--kotlinx-coroutines-core/jvm/test/guide/example-exceptions-02.kt1
-rw-r--r--kotlinx-coroutines-core/jvm/test/guide/example-exceptions-04.kt1
-rw-r--r--kotlinx-coroutines-core/jvm/test/guide/example-exceptions-05.kt1
-rw-r--r--kotlinx-coroutines-core/jvm/test/guide/example-exceptions-06.kt1
-rw-r--r--kotlinx-coroutines-core/jvm/test/guide/example-select-02.kt20
-rw-r--r--kotlinx-coroutines-core/jvm/test/guide/example-select-05.kt8
-rw-r--r--kotlinx-coroutines-core/jvm/test/guide/test/BasicsGuideTest.kt48
-rw-r--r--kotlinx-coroutines-core/jvm/test/guide/test/DispatcherGuideTest.kt2
-rw-r--r--kotlinx-coroutines-core/jvm/test/internal/LockFreeLinkedListAtomicLFStressTest.kt169
-rw-r--r--kotlinx-coroutines-core/jvm/test/knit/ClosedAfterGuideTestExecutor.kt51
-rw-r--r--kotlinx-coroutines-core/jvm/test/knit/TestUtil.kt6
-rw-r--r--kotlinx-coroutines-core/jvm/test/lincheck/ChannelsLincheckTest.kt (renamed from kotlinx-coroutines-core/jvm/test/linearizability/ChannelsLCStressTest.kt)62
-rw-r--r--kotlinx-coroutines-core/jvm/test/lincheck/LockFreeListLincheckTest.kt (renamed from kotlinx-coroutines-core/jvm/test/linearizability/LockFreeListLCStressTest.kt)13
-rw-r--r--kotlinx-coroutines-core/jvm/test/lincheck/LockFreeTaskQueueLincheckTest.kt (renamed from kotlinx-coroutines-core/jvm/test/linearizability/LockFreeTaskQueueLCStressTest.kt)34
-rw-r--r--kotlinx-coroutines-core/jvm/test/lincheck/MutexLincheckTest.kt32
-rw-r--r--kotlinx-coroutines-core/jvm/test/lincheck/SegmentListRemoveLincheckTest.kt (renamed from kotlinx-coroutines-core/jvm/test/linearizability/SegmentListRemoveLCStressTest.kt)20
-rw-r--r--kotlinx-coroutines-core/jvm/test/lincheck/SegmentQueueLincheckTest.kt (renamed from kotlinx-coroutines-core/jvm/test/linearizability/SegmentQueueLCStressTest.kt)11
-rw-r--r--kotlinx-coroutines-core/jvm/test/lincheck/SemaphoreLincheckTest.kt35
-rw-r--r--kotlinx-coroutines-core/jvm/test/linearizability/MutexLCStressTest.kt31
-rw-r--r--kotlinx-coroutines-core/jvm/test/linearizability/SemaphoreLCStressTest.kt34
-rw-r--r--kotlinx-coroutines-core/jvm/test/scheduling/BlockingCoroutineDispatcherMixedStealingStressTest.kt2
-rw-r--r--kotlinx-coroutines-core/jvm/test/scheduling/BlockingCoroutineDispatcherTest.kt4
-rw-r--r--kotlinx-coroutines-core/jvm/test/scheduling/SchedulerTestBase.kt12
-rw-r--r--kotlinx-coroutines-core/jvm/test/scheduling/TestTimeSource.kt4
-rw-r--r--kotlinx-coroutines-core/jvm/test/selects/SelectDeadlockLFStressTest.kt101
-rw-r--r--kotlinx-coroutines-core/jvm/test/sync/MutexStressTest.kt19
-rw-r--r--kotlinx-coroutines-core/jvm/test/sync/SemaphoreStressTest.kt19
-rw-r--r--kotlinx-coroutines-core/knit.properties2
-rw-r--r--kotlinx-coroutines-core/native/src/Builders.kt4
-rw-r--r--kotlinx-coroutines-core/native/src/CompletionHandler.kt2
-rw-r--r--kotlinx-coroutines-core/native/src/CoroutineContext.kt11
-rw-r--r--kotlinx-coroutines-core/native/src/CoroutineExceptionHandlerImpl.kt2
-rw-r--r--kotlinx-coroutines-core/native/src/Debug.kt8
-rw-r--r--kotlinx-coroutines-core/native/src/Dispatchers.kt2
-rw-r--r--kotlinx-coroutines-core/native/src/EventLoop.kt2
-rw-r--r--kotlinx-coroutines-core/native/src/Exceptions.kt9
-rw-r--r--kotlinx-coroutines-core/native/src/Runnable.kt2
-rw-r--r--kotlinx-coroutines-core/native/src/SchedulerTask.kt2
-rw-r--r--kotlinx-coroutines-core/native/src/WorkerMain.native.kt2
-rw-r--r--kotlinx-coroutines-core/native/src/flow/internal/FlowExceptions.kt2
-rw-r--r--kotlinx-coroutines-core/native/src/flow/internal/SafeCollector.kt2
-rw-r--r--kotlinx-coroutines-core/native/src/internal/Concurrent.kt2
-rw-r--r--kotlinx-coroutines-core/native/src/internal/CopyOnWriteList.kt2
-rw-r--r--kotlinx-coroutines-core/native/src/internal/LinkedList.kt2
-rw-r--r--kotlinx-coroutines-core/native/src/internal/LocalAtomics.kt2
-rw-r--r--kotlinx-coroutines-core/native/src/internal/ProbesSupport.kt2
-rw-r--r--kotlinx-coroutines-core/native/src/internal/StackTraceRecovery.kt2
-rw-r--r--kotlinx-coroutines-core/native/src/internal/Synchronized.kt2
-rw-r--r--kotlinx-coroutines-core/native/src/internal/SystemProps.kt2
-rw-r--r--kotlinx-coroutines-core/native/src/internal/ThreadContext.kt2
-rw-r--r--kotlinx-coroutines-core/native/src/internal/ThreadLocal.kt2
-rw-r--r--kotlinx-coroutines-core/native/test/TestBase.kt6
-rw-r--r--kotlinx-coroutines-core/nativeDarwin/src/WorkerMain.kt2
-rw-r--r--kotlinx-coroutines-core/nativeOther/src/WorkerMain.kt2
-rw-r--r--kotlinx-coroutines-debug/README.md12
-rw-r--r--kotlinx-coroutines-debug/api/kotlinx-coroutines-debug.api5
-rw-r--r--kotlinx-coroutines-debug/build.gradle13
-rw-r--r--kotlinx-coroutines-debug/src/CoroutineInfo.kt4
-rw-r--r--kotlinx-coroutines-debug/src/CoroutinesBlockHoundIntegration.kt16
-rw-r--r--kotlinx-coroutines-debug/src/DebugProbes.kt9
-rw-r--r--kotlinx-coroutines-debug/src/internal/Attach.kt4
-rw-r--r--kotlinx-coroutines-debug/src/internal/NoOpProbes.kt2
-rw-r--r--kotlinx-coroutines-debug/src/junit/CoroutinesTimeoutImpl.kt81
-rw-r--r--kotlinx-coroutines-debug/src/junit/junit4/CoroutinesTimeout.kt (renamed from kotlinx-coroutines-debug/src/junit4/CoroutinesTimeout.kt)2
-rw-r--r--kotlinx-coroutines-debug/src/junit/junit4/CoroutinesTimeoutStatement.kt30
-rw-r--r--kotlinx-coroutines-debug/src/junit/junit5/CoroutinesTimeout.kt63
-rw-r--r--kotlinx-coroutines-debug/src/junit/junit5/CoroutinesTimeoutExtension.kt279
-rw-r--r--kotlinx-coroutines-debug/src/junit4/CoroutinesTimeoutStatement.kt87
-rw-r--r--kotlinx-coroutines-debug/test/DebugProbesTest.kt5
-rw-r--r--kotlinx-coroutines-debug/test/SanitizedProbesTest.kt5
-rw-r--r--kotlinx-coroutines-debug/test/junit4/CoroutinesTimeoutDisabledTracesTest.kt2
-rw-r--r--kotlinx-coroutines-debug/test/junit4/CoroutinesTimeoutEagerTest.kt2
-rw-r--r--kotlinx-coroutines-debug/test/junit4/CoroutinesTimeoutTest.kt2
-rw-r--r--kotlinx-coroutines-debug/test/junit4/TestFailureValidation.kt4
-rw-r--r--kotlinx-coroutines-debug/test/junit5/CoroutinesTimeoutExtensionTest.kt121
-rw-r--r--kotlinx-coroutines-debug/test/junit5/CoroutinesTimeoutInheritanceTest.kt60
-rw-r--r--kotlinx-coroutines-debug/test/junit5/CoroutinesTimeoutMethodTest.kt44
-rw-r--r--kotlinx-coroutines-debug/test/junit5/CoroutinesTimeoutNestedTest.kt29
-rw-r--r--kotlinx-coroutines-debug/test/junit5/CoroutinesTimeoutSimpleTest.kt61
-rw-r--r--kotlinx-coroutines-debug/test/junit5/CoroutinesTimeoutTest.kt170
-rw-r--r--kotlinx-coroutines-debug/test/junit5/RegisterExtensionExample.kt20
-rw-r--r--kotlinx-coroutines-test/README.md8
-rw-r--r--kotlinx-coroutines-test/build.gradle.kts4
-rw-r--r--kotlinx-coroutines-test/resources/META-INF/proguard/coroutines.pro2
-rw-r--r--kotlinx-coroutines-test/src/DelayController.kt2
-rw-r--r--kotlinx-coroutines-test/src/TestBuilders.kt2
-rw-r--r--kotlinx-coroutines-test/src/TestCoroutineDispatcher.kt2
-rw-r--r--kotlinx-coroutines-test/src/TestCoroutineExceptionHandler.kt2
-rw-r--r--kotlinx-coroutines-test/src/TestCoroutineScope.kt2
-rw-r--r--kotlinx-coroutines-test/src/TestDispatchers.kt2
-rw-r--r--kotlinx-coroutines-test/src/internal/MainTestDispatcher.kt9
-rw-r--r--reactive/knit.properties4
-rw-r--r--reactive/kotlinx-coroutines-jdk9/build.gradle.kts5
-rw-r--r--reactive/kotlinx-coroutines-jdk9/src/Await.kt74
-rw-r--r--reactive/kotlinx-coroutines-jdk9/src/Publish.kt30
-rw-r--r--reactive/kotlinx-coroutines-jdk9/src/ReactiveFlow.kt38
-rw-r--r--reactive/kotlinx-coroutines-jdk9/test/AwaitTest.kt43
-rw-r--r--reactive/kotlinx-coroutines-jdk9/test/FlowAsPublisherTest.kt21
-rw-r--r--reactive/kotlinx-coroutines-jdk9/test/IntegrationTest.kt20
-rw-r--r--reactive/kotlinx-coroutines-jdk9/test/PublishTest.kt158
-rw-r--r--reactive/kotlinx-coroutines-jdk9/test/PublisherAsFlowTest.kt2
-rw-r--r--reactive/kotlinx-coroutines-jdk9/test/PublisherCollectTest.kt146
-rw-r--r--reactive/kotlinx-coroutines-reactive/README.md20
-rw-r--r--reactive/kotlinx-coroutines-reactive/api/kotlinx-coroutines-reactive.api12
-rw-r--r--reactive/kotlinx-coroutines-reactive/build.gradle.kts2
-rw-r--r--reactive/kotlinx-coroutines-reactive/src/Await.kt243
-rw-r--r--reactive/kotlinx-coroutines-reactive/src/Channel.kt34
-rw-r--r--reactive/kotlinx-coroutines-reactive/src/ContextInjector.kt2
-rw-r--r--reactive/kotlinx-coroutines-reactive/src/Convert.kt6
-rw-r--r--reactive/kotlinx-coroutines-reactive/src/Migration.kt11
-rw-r--r--reactive/kotlinx-coroutines-reactive/src/Publish.kt207
-rw-r--r--reactive/kotlinx-coroutines-reactive/src/ReactiveFlow.kt71
-rw-r--r--reactive/kotlinx-coroutines-reactive/test/AwaitTest.kt43
-rw-r--r--reactive/kotlinx-coroutines-reactive/test/CancelledParentAttachTest.kt21
-rw-r--r--reactive/kotlinx-coroutines-reactive/test/FlowAsPublisherTest.kt24
-rw-r--r--reactive/kotlinx-coroutines-reactive/test/IntegrationTest.kt173
-rw-r--r--reactive/kotlinx-coroutines-reactive/test/IterableFlowTckTest.kt12
-rw-r--r--reactive/kotlinx-coroutines-reactive/test/PublishTest.kt158
-rw-r--r--reactive/kotlinx-coroutines-reactive/test/PublisherAsFlowTest.kt14
-rw-r--r--reactive/kotlinx-coroutines-reactive/test/PublisherCollectTest.kt144
-rw-r--r--reactive/kotlinx-coroutines-reactive/test/PublisherRequestStressTest.kt14
-rw-r--r--reactive/kotlinx-coroutines-reactive/test/PublisherSubscriptionSelectTest.kt25
-rw-r--r--reactive/kotlinx-coroutines-reactor/README.md16
-rw-r--r--reactive/kotlinx-coroutines-reactor/api/kotlinx-coroutines-reactor.api18
-rw-r--r--reactive/kotlinx-coroutines-reactor/build.gradle.kts6
-rw-r--r--reactive/kotlinx-coroutines-reactor/src/Convert.kt13
-rw-r--r--reactive/kotlinx-coroutines-reactor/src/Flux.kt88
-rw-r--r--reactive/kotlinx-coroutines-reactor/src/Migration.kt7
-rw-r--r--reactive/kotlinx-coroutines-reactor/src/Mono.kt223
-rw-r--r--reactive/kotlinx-coroutines-reactor/src/ReactorContext.kt56
-rw-r--r--reactive/kotlinx-coroutines-reactor/src/ReactorContextInjector.kt6
-rw-r--r--reactive/kotlinx-coroutines-reactor/src/ReactorFlow.kt2
-rw-r--r--reactive/kotlinx-coroutines-reactor/src/Scheduler.kt2
-rw-r--r--reactive/kotlinx-coroutines-reactor/test/ConvertTest.kt7
-rw-r--r--reactive/kotlinx-coroutines-reactor/test/FlowAsFluxTest.kt12
-rw-r--r--reactive/kotlinx-coroutines-reactor/test/FluxTest.kt37
-rw-r--r--reactive/kotlinx-coroutines-reactor/test/MonoTest.kt152
-rw-r--r--reactive/kotlinx-coroutines-reactor/test/ReactorContextTest.kt22
-rw-r--r--reactive/kotlinx-coroutines-rx2/README.md49
-rw-r--r--reactive/kotlinx-coroutines-rx2/api/kotlinx-coroutines-rx2.api16
-rw-r--r--reactive/kotlinx-coroutines-rx2/build.gradle21
-rw-r--r--reactive/kotlinx-coroutines-rx2/src/RxAwait.kt144
-rw-r--r--reactive/kotlinx-coroutines-rx2/src/RxCancellable.kt3
-rw-r--r--reactive/kotlinx-coroutines-rx2/src/RxChannel.kt51
-rw-r--r--reactive/kotlinx-coroutines-rx2/src/RxCompletable.kt38
-rw-r--r--reactive/kotlinx-coroutines-rx2/src/RxConvert.kt20
-rw-r--r--reactive/kotlinx-coroutines-rx2/src/RxFlowable.kt6
-rw-r--r--reactive/kotlinx-coroutines-rx2/src/RxMaybe.kt36
-rw-r--r--reactive/kotlinx-coroutines-rx2/src/RxObservable.kt142
-rw-r--r--reactive/kotlinx-coroutines-rx2/src/RxScheduler.kt2
-rw-r--r--reactive/kotlinx-coroutines-rx2/src/RxSingle.kt36
-rw-r--r--reactive/kotlinx-coroutines-rx2/test/CompletableTest.kt30
-rw-r--r--reactive/kotlinx-coroutines-rx2/test/FlowableExceptionHandlingTest.kt16
-rw-r--r--reactive/kotlinx-coroutines-rx2/test/IntegrationTest.kt18
-rw-r--r--reactive/kotlinx-coroutines-rx2/test/MaybeTest.kt96
-rw-r--r--reactive/kotlinx-coroutines-rx2/test/ObservableCollectTest.kt69
-rw-r--r--reactive/kotlinx-coroutines-rx2/test/ObservableExceptionHandlingTest.kt37
-rw-r--r--reactive/kotlinx-coroutines-rx2/test/ObservableSingleTest.kt30
-rw-r--r--reactive/kotlinx-coroutines-rx2/test/ObservableSourceAsFlowStressTest.kt2
-rw-r--r--reactive/kotlinx-coroutines-rx2/test/ObservableSubscriptionSelectTest.kt26
-rw-r--r--reactive/kotlinx-coroutines-rx2/test/SingleTest.kt28
-rw-r--r--reactive/kotlinx-coroutines-rx3/README.md42
-rw-r--r--reactive/kotlinx-coroutines-rx3/api/kotlinx-coroutines-rx3.api2
-rw-r--r--reactive/kotlinx-coroutines-rx3/build.gradle22
-rw-r--r--reactive/kotlinx-coroutines-rx3/src/RxAwait.kt147
-rw-r--r--reactive/kotlinx-coroutines-rx3/src/RxCancellable.kt3
-rw-r--r--reactive/kotlinx-coroutines-rx3/src/RxChannel.kt16
-rw-r--r--reactive/kotlinx-coroutines-rx3/src/RxCompletable.kt11
-rw-r--r--reactive/kotlinx-coroutines-rx3/src/RxConvert.kt20
-rw-r--r--reactive/kotlinx-coroutines-rx3/src/RxFlowable.kt3
-rw-r--r--reactive/kotlinx-coroutines-rx3/src/RxMaybe.kt11
-rw-r--r--reactive/kotlinx-coroutines-rx3/src/RxObservable.kt118
-rw-r--r--reactive/kotlinx-coroutines-rx3/src/RxScheduler.kt2
-rw-r--r--reactive/kotlinx-coroutines-rx3/src/RxSingle.kt11
-rw-r--r--reactive/kotlinx-coroutines-rx3/test/CompletableTest.kt30
-rw-r--r--reactive/kotlinx-coroutines-rx3/test/FlowableExceptionHandlingTest.kt16
-rw-r--r--reactive/kotlinx-coroutines-rx3/test/IntegrationTest.kt15
-rw-r--r--reactive/kotlinx-coroutines-rx3/test/MaybeTest.kt98
-rw-r--r--reactive/kotlinx-coroutines-rx3/test/ObservableCollectTest.kt68
-rw-r--r--reactive/kotlinx-coroutines-rx3/test/ObservableExceptionHandlingTest.kt37
-rw-r--r--reactive/kotlinx-coroutines-rx3/test/ObservableSingleTest.kt30
-rw-r--r--reactive/kotlinx-coroutines-rx3/test/ObservableSourceAsFlowStressTest.kt2
-rw-r--r--reactive/kotlinx-coroutines-rx3/test/ObservableSubscriptionSelectTest.kt20
-rw-r--r--reactive/kotlinx-coroutines-rx3/test/SingleTest.kt28
-rw-r--r--settings.gradle10
-rw-r--r--site/README.md15
-rw-r--r--site/build.gradle.kts65
-rwxr-xr-xsite/deploy.sh33
-rw-r--r--site/docs/_config.yml14
-rw-r--r--site/docs/_includes/footer.html3
-rw-r--r--site/docs/_includes/head.html19
-rw-r--r--site/docs/_includes/header.html5
-rw-r--r--site/docs/_layouts/api.html14
-rw-r--r--site/docs/_sass/_api.scss225
-rw-r--r--site/docs/_sass/_base.scss97
-rw-r--r--site/docs/_sass/_layout.scss37
-rw-r--r--site/docs/_sass/_minima.scss35
-rw-r--r--site/docs/assets/js/api.js20
-rw-r--r--site/docs/assets/main.scss36
-rw-r--r--site/docs/index.md32
-rw-r--r--stdlib-stubs/README.md1
-rw-r--r--stdlib-stubs/build.gradle.kts11
-rw-r--r--stdlib-stubs/src/Continuation.kt10
-rw-r--r--stdlib-stubs/src/ContinuationInterceptor.kt15
-rw-r--r--stdlib-stubs/src/CoroutineContext.kt38
-rw-r--r--stdlib-stubs/src/Result.kt14
-rw-r--r--ui/coroutines-guide-ui.md40
-rw-r--r--ui/knit.properties4
-rw-r--r--ui/kotlinx-coroutines-android/android-unit-tests/build.gradle.kts3
-rw-r--r--ui/kotlinx-coroutines-android/android-unit-tests/src/EmptyCoroutineScopeImpl.kt2
-rw-r--r--ui/kotlinx-coroutines-android/animation-app/app/build.gradle.kts35
-rw-r--r--ui/kotlinx-coroutines-android/animation-app/app/src/main/AndroidManifest.xml27
-rw-r--r--ui/kotlinx-coroutines-android/animation-app/app/src/main/java/org/jetbrains/kotlinx/animation/Animation.kt159
-rw-r--r--ui/kotlinx-coroutines-android/animation-app/app/src/main/java/org/jetbrains/kotlinx/animation/MainActivity.kt30
-rw-r--r--ui/kotlinx-coroutines-android/animation-app/app/src/main/res/drawable-v24/ic_launcher_foreground.xml34
-rw-r--r--ui/kotlinx-coroutines-android/animation-app/app/src/main/res/drawable/ic_launcher_background.xml170
-rw-r--r--ui/kotlinx-coroutines-android/animation-app/app/src/main/res/layout/activity_main.xml47
-rw-r--r--ui/kotlinx-coroutines-android/animation-app/app/src/main/res/layout/content_main.xml24
-rw-r--r--ui/kotlinx-coroutines-android/animation-app/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml9
-rw-r--r--ui/kotlinx-coroutines-android/animation-app/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml9
-rw-r--r--ui/kotlinx-coroutines-android/animation-app/app/src/main/res/mipmap-hdpi/ic_launcher.pngbin3056 -> 0 bytes
-rw-r--r--ui/kotlinx-coroutines-android/animation-app/app/src/main/res/mipmap-hdpi/ic_launcher_round.pngbin5024 -> 0 bytes
-rw-r--r--ui/kotlinx-coroutines-android/animation-app/app/src/main/res/mipmap-mdpi/ic_launcher.pngbin2096 -> 0 bytes
-rw-r--r--ui/kotlinx-coroutines-android/animation-app/app/src/main/res/mipmap-mdpi/ic_launcher_round.pngbin2858 -> 0 bytes
-rw-r--r--ui/kotlinx-coroutines-android/animation-app/app/src/main/res/mipmap-xhdpi/ic_launcher.pngbin4569 -> 0 bytes
-rw-r--r--ui/kotlinx-coroutines-android/animation-app/app/src/main/res/mipmap-xhdpi/ic_launcher_round.pngbin7098 -> 0 bytes
-rw-r--r--ui/kotlinx-coroutines-android/animation-app/app/src/main/res/mipmap-xxhdpi/ic_launcher.pngbin6464 -> 0 bytes
-rw-r--r--ui/kotlinx-coroutines-android/animation-app/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.pngbin10676 -> 0 bytes
-rw-r--r--ui/kotlinx-coroutines-android/animation-app/app/src/main/res/mipmap-xxxhdpi/ic_launcher.pngbin9250 -> 0 bytes
-rw-r--r--ui/kotlinx-coroutines-android/animation-app/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.pngbin15523 -> 0 bytes
-rw-r--r--ui/kotlinx-coroutines-android/animation-app/app/src/main/res/values/colors.xml6
-rw-r--r--ui/kotlinx-coroutines-android/animation-app/app/src/main/res/values/dimens.xml3
-rw-r--r--ui/kotlinx-coroutines-android/animation-app/app/src/main/res/values/strings.xml4
-rw-r--r--ui/kotlinx-coroutines-android/animation-app/app/src/main/res/values/styles.xml20
-rw-r--r--ui/kotlinx-coroutines-android/animation-app/build.gradle.kts30
-rw-r--r--ui/kotlinx-coroutines-android/animation-app/gradle.properties28
-rw-r--r--ui/kotlinx-coroutines-android/animation-app/gradle/wrapper/gradle-wrapper.jarbin58694 -> 0 bytes
-rw-r--r--ui/kotlinx-coroutines-android/animation-app/gradle/wrapper/gradle-wrapper.properties5
-rwxr-xr-xui/kotlinx-coroutines-android/animation-app/gradlew183
-rw-r--r--ui/kotlinx-coroutines-android/animation-app/gradlew.bat103
-rw-r--r--ui/kotlinx-coroutines-android/animation-app/settings.gradle.kts5
-rw-r--r--ui/kotlinx-coroutines-android/build.gradle.kts55
-rw-r--r--ui/kotlinx-coroutines-android/example-app/.gitignore7
-rw-r--r--ui/kotlinx-coroutines-android/example-app/app/build.gradle.kts34
-rw-r--r--ui/kotlinx-coroutines-android/example-app/app/src/main/AndroidManifest.xml28
-rw-r--r--ui/kotlinx-coroutines-android/example-app/app/src/main/java/com/example/app/MainActivity.kt43
-rw-r--r--ui/kotlinx-coroutines-android/example-app/app/src/main/res/layout/activity_main.xml37
-rw-r--r--ui/kotlinx-coroutines-android/example-app/app/src/main/res/layout/content_main.xml25
-rw-r--r--ui/kotlinx-coroutines-android/example-app/app/src/main/res/menu/menu_main.xml10
-rw-r--r--ui/kotlinx-coroutines-android/example-app/app/src/main/res/mipmap-hdpi/ic_launcher.pngbin3418 -> 0 bytes
-rw-r--r--ui/kotlinx-coroutines-android/example-app/app/src/main/res/mipmap-hdpi/ic_launcher_round.pngbin4208 -> 0 bytes
-rw-r--r--ui/kotlinx-coroutines-android/example-app/app/src/main/res/mipmap-mdpi/ic_launcher.pngbin2206 -> 0 bytes
-rw-r--r--ui/kotlinx-coroutines-android/example-app/app/src/main/res/mipmap-mdpi/ic_launcher_round.pngbin2555 -> 0 bytes
-rw-r--r--ui/kotlinx-coroutines-android/example-app/app/src/main/res/mipmap-xhdpi/ic_launcher.pngbin4842 -> 0 bytes
-rw-r--r--ui/kotlinx-coroutines-android/example-app/app/src/main/res/mipmap-xhdpi/ic_launcher_round.pngbin6114 -> 0 bytes
-rw-r--r--ui/kotlinx-coroutines-android/example-app/app/src/main/res/mipmap-xxhdpi/ic_launcher.pngbin7718 -> 0 bytes
-rw-r--r--ui/kotlinx-coroutines-android/example-app/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.pngbin10056 -> 0 bytes
-rw-r--r--ui/kotlinx-coroutines-android/example-app/app/src/main/res/mipmap-xxxhdpi/ic_launcher.pngbin10486 -> 0 bytes
-rw-r--r--ui/kotlinx-coroutines-android/example-app/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.pngbin14696 -> 0 bytes
-rw-r--r--ui/kotlinx-coroutines-android/example-app/app/src/main/res/values/colors.xml6
-rw-r--r--ui/kotlinx-coroutines-android/example-app/app/src/main/res/values/dimens.xml3
-rw-r--r--ui/kotlinx-coroutines-android/example-app/app/src/main/res/values/strings.xml4
-rw-r--r--ui/kotlinx-coroutines-android/example-app/app/src/main/res/values/styles.xml20
-rw-r--r--ui/kotlinx-coroutines-android/example-app/build.gradle.kts30
-rw-r--r--ui/kotlinx-coroutines-android/example-app/gradle.properties28
-rw-r--r--ui/kotlinx-coroutines-android/example-app/gradle/wrapper/gradle-wrapper.jarbin58694 -> 0 bytes
-rw-r--r--ui/kotlinx-coroutines-android/example-app/gradle/wrapper/gradle-wrapper.properties5
-rwxr-xr-xui/kotlinx-coroutines-android/example-app/gradlew183
-rw-r--r--ui/kotlinx-coroutines-android/example-app/gradlew.bat103
-rw-r--r--ui/kotlinx-coroutines-android/example-app/settings.gradle.kts1
-rw-r--r--ui/kotlinx-coroutines-android/src/AndroidExceptionPreHandler.kt2
-rw-r--r--ui/kotlinx-coroutines-android/src/HandlerDispatcher.kt42
-rw-r--r--ui/kotlinx-coroutines-android/test/DisabledHandlerTest.kt64
-rw-r--r--ui/kotlinx-coroutines-android/test/HandlerDispatcherTest.kt10
-rw-r--r--ui/kotlinx-coroutines-android/test/R8ServiceLoaderOptimizationTest.kt1
-rw-r--r--ui/kotlinx-coroutines-javafx/build.gradle.kts13
-rw-r--r--ui/kotlinx-coroutines-javafx/src/JavaFxConvert.kt17
-rw-r--r--ui/kotlinx-coroutines-javafx/src/JavaFxDispatcher.kt2
-rw-r--r--ui/kotlinx-coroutines-javafx/test/JavaFxObservableAsFlowTest.kt16
-rw-r--r--ui/kotlinx-coroutines-javafx/test/guide/example-ui-actor-02.kt2
-rw-r--r--ui/kotlinx-coroutines-javafx/test/guide/example-ui-actor-03.kt4
-rw-r--r--ui/kotlinx-coroutines-javafx/test/guide/example-ui-blocking-01.kt2
-rw-r--r--ui/kotlinx-coroutines-javafx/test/guide/example-ui-blocking-02.kt2
-rw-r--r--ui/kotlinx-coroutines-javafx/test/guide/example-ui-blocking-03.kt2
-rw-r--r--ui/kotlinx-coroutines-swing/build.gradle.kts2
-rw-r--r--ui/kotlinx-coroutines-swing/src/SwingDispatcher.kt2
-rw-r--r--ui/kotlinx-coroutines-swing/test/examples/swing-example.kt39
756 files changed, 17735 insertions, 16343 deletions
diff --git a/.gitignore b/.gitignore
index aed71032..52843ca5 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,6 +4,7 @@
!/.idea/copyright
!/.idea/codeStyleSettings.xml
!/.idea/codeStyles
+!/.idea/dictionaries
*.iml
.gradle
.gradletasknamecache
diff --git a/.idea/dictionaries/shared.xml b/.idea/dictionaries/shared.xml
new file mode 100644
index 00000000..3da8e229
--- /dev/null
+++ b/.idea/dictionaries/shared.xml
@@ -0,0 +1,9 @@
+<component name="ProjectDictionaryState">
+ <dictionary name="shared">
+ <words>
+ <w>kotlinx</w>
+ <w>lincheck</w>
+ <w>redirector</w>
+ </words>
+ </dictionary>
+</component> \ No newline at end of file
diff --git a/Android.bp b/Android.bp
index ce531ac3..5225d8c3 100644
--- a/Android.bp
+++ b/Android.bp
@@ -48,6 +48,7 @@ java_library {
"kotlinx-coroutines-core/jvm/src/debug/**/*.kt",
"kotlinx-coroutines-core/jvm/src/test_/TestCoroutineContext.kt",
],
+ java_resource_dirs: ["kotlinx-coroutines-core/jvm/resources"],
static_libs: [
"kotlinx_atomicfu",
],
@@ -61,12 +62,17 @@ java_library {
"-Xuse-experimental=kotlinx.coroutines.ObsoleteCoroutinesApi",
"-Xuse-experimental=kotlinx.coroutines.InternalCoroutinesApi",
],
+ apex_available: [
+ "//apex_available:platform",
+ "//apex_available:anyapex"
+ ],
}
java_library {
name: "kotlinx_coroutines_android",
sdk_version: "28",
srcs: ["ui/kotlinx-coroutines-android/src/**/*.kt"],
+ java_resource_dirs: ["ui/kotlinx-coroutines-android/resources"],
kotlincflags: [
"-Xuse-experimental=kotlinx.coroutines.InternalCoroutinesApi",
"-Xuse-experimental=kotlinx.coroutines.ExperimentalCoroutinesApi",
@@ -75,4 +81,68 @@ java_library {
"kotlinx_coroutines",
"androidx.annotation_annotation",
],
+ apex_available: [
+ "//apex_available:platform",
+ "//apex_available:anyapex"
+ ],
+}
+
+java_library {
+ name: "kotlinx_coroutines_test",
+ host_supported: true,
+ srcs: ["kotlinx-coroutines-test/src/**/*.kt"],
+ java_resource_dirs: ["kotlinx-coroutines-test/resources"],
+ kotlincflags: [
+ "-Xuse-experimental=kotlinx.coroutines.InternalCoroutinesApi",
+ "-Xuse-experimental=kotlinx.coroutines.ExperimentalCoroutinesApi",
+ ],
+ libs: [
+ "kotlinx_coroutines",
+ ],
+ apex_available: [
+ "//apex_available:platform",
+ "//apex_available:anyapex"
+ ],
+}
+
+// Temporary aliases for kotlinx-coroutines-core, kotlinx-coroutines-core-jvm and kotlinx-coroutines-android
+
+java_library {
+ name: "kotlinx-coroutines-core",
+ host_supported: true,
+ sdk_version: "28",
+ static_libs: ["kotlinx_coroutines"],
+ apex_available: [
+ "//apex_available:platform",
+ "//apex_available:anyapex"
+ ],
+}
+
+java_library {
+ name: "kotlinx-coroutines-core-jvm",
+ host_supported: true,
+ sdk_version: "28",
+ static_libs: ["kotlinx_coroutines"],
+ apex_available: [
+ "//apex_available:platform",
+ "//apex_available:anyapex"
+ ],
+}
+
+java_library {
+ name: "kotlinx-coroutines-android",
+ sdk_version: "28",
+ static_libs: [
+ "kotlinx_coroutines_android",
+ "kotlinx_coroutines",
+ ],
+ apex_available: [
+ "//apex_available:platform",
+ "//apex_available:anyapex"
+ ],
+}
+
+filegroup {
+ name: "kotlinx-coroutines-play-services",
+ srcs: ["integration/kotlinx-coroutines-play-services/src/**/*.kt"],
}
diff --git a/CHANGES.md b/CHANGES.md
index baee6c43..611e9c9c 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -1,5 +1,146 @@
# Change log for kotlinx.coroutines
+## Version 1.5.2
+
+* Kotlin is updated to 1.5.30.
+* New native targets for Apple Silicon are introduced.
+* Fixed a bug when `onUndeliveredElement` was incorrectly called on a properly received elements on JS (#2826).
+* Fixed `Dispatchers.Default` on React Native, it now fully relies on `setTimeout` instead of stub `process.nextTick`. Thanks to @Legion2 (#2843).
+* Optimizations of `Mutex` implementation (#2581).
+* `Mutex` implementation is made completely lock-free as stated (#2590).
+* Various documentation and guides improvements. Thanks to @MasoodFallahpoor and @Pihanya.
+
+## Version 1.5.1
+
+* Atomic `update`, `getAndUpdate`, and `updateAndGet` operations of `MutableStateFlow` (#2720).
+* `Executor.asCoroutineDispatcher` implementation improvements (#2601):
+ * If the target executor is `ScheduledExecutorService`, then its `schedule` API is used for time-related coroutine operations.
+ * `RemoveOnCancelPolicy` is now part of the public contract.
+* Introduced overloads for `Task.asDeferred` and `Task.await` that accept `CancellationTokenSource` for bidirectional cancellation (#2527).
+* Reactive streams are updated to `1.0.3` (#2740).
+* `CopyableThrowable` is allowed to modify the exception message during stacktrace recovery (#1931).
+* `CoroutineDispatcher.releaseInterceptedContinuation` is now a `final` method (#2785).
+* Closing a Handler underlying `Handler.asCoroutineDispatcher` now causes the dispatched coroutines to be canceled on `Dispatchers.IO (#2778)`.
+* Kotlin is updated to 1.5.20.
+* Fixed a spurious `ClassCastException` in `releaseInterceptedContinuation` and `IllegalStateException` from `tryReleaseClaimedContinuation` (#2736, #2768).
+* Fixed inconsistent exception message during stacktrace recovery for non-suspending channel iterators (#2749).
+* Fixed linear stack usage for `CompletableFuture.asDeferred` when the target future has a long chain of listeners (#2730).
+* Any exceptions from `CoroutineDispatcher.isDispatchNeeded` are now considered as fatal and are propagated to the caller (#2733).
+* Internal `DebugProbesKt` (used in the debugger implementation) are moved from `debug` to `core` module.
+
+## Version 1.5.0
+
+Note that this is a full changelog relative to 1.4.3 version. Changelog relative to 1.5.0-RC can be found in the end.
+
+### Channels API
+
+* Major channels API rework (#330, #974). Existing `offer`, `poll`, and `sendBlocking` methods are deprecated, internal `receiveCatching` and `onReceiveCatching` removed, `receiveOrNull` and `onReceiveOrNull` are completely deprecated. Previously deprecated `SendChannel.isFull` declaration is removed. Channel operators deprecated with `ERROR` are now `HIDDEN`.
+* New methods `receiveCatching`, `onReceiveCatching` `trySend`, `tryReceive`, and `trySendBlocking` along with the new result type `ChannelResult` are introduced. They provide better type safety, are less error-prone, and have a consistent future-proof naming scheme. The full rationale behind this change can be found [here](https://github.com/Kotlin/kotlinx.coroutines/issues/974#issuecomment-806569582).
+* `BroadcastChannel` and `ConflatedBroadcastChannel` are marked as `ObsoleteCoroutinesApi` in the favor or `SharedFlow` and `StateFlow`. The migration scheme can be found in their documentation. These classes will be deprecated in the next major release.
+* `callbackFlow` and `channelFlow` are promoted to stable API.
+
+### Reactive integrations
+
+* All existing API in modules `kotlinx-coroutines-rx2`, `kotlinx-coroutines-rx3`, `kotlinx-coroutines-reactive`, `kotlinx-coroutines-reactor`, and `kotlinx-coroutines-jdk9` were revisited and promoted to stable (#2545).
+* `publish` is no longer allowed to emit `null` values (#2646).
+* Misleading `awaitSingleOr*` functions on `Publisher` type are deprecated (#2591).
+* `MaybeSource.await` is deprecated in the favor of `awaitSingle`, additional lint functions for `Mono` are added in order to prevent ambiguous `Publisher` usages (#2628, #1587).
+* `ContextView` support in `kotlinx-coroutines-reactor` (#2575).
+* All reactive builders no longer ignore inner cancellation exceptions preventing their completion (#2262, #2646).
+* `MaybeSource.collect` and `Maybe.collect` properly finish when they are completed without a value (#2617).
+* All exceptions are now consistently handled according to reactive specification, whether they are considered 'fatal' or not by reactive frameworks (#2646).
+
+### Other improvements
+
+* Kotlin version is upgraded to 1.5.0 and JVM target is updated to 1.8.
+* `Flow.last` and `Flow.lastOrNull` operators (#2246).
+* `Flow.runningFold` operator (#2641).
+* `CoroutinesTimeout` rule for JUnit5 (#2197).
+* Internals of `Job` and `AbstractCoroutine` was reworked, resulting in smaller code size, less memory footprint, and better performance (#2513, #2512).
+* `CancellationException` from Kotlin standard library is used for cancellation on Koltin/JS and Kotlin/Native (#2638).
+* Introduced new `DelicateCoroutinesApi` annotation that warns users about potential target API pitfalls and suggests studying API's documentation first. The only delicate API right now is `GlobalScope` (#2637).
+* Fixed bug introduced in `1.4.3` when `kotlinx-coroutines-core.jar` triggered IDEA debugger failure (#2619).
+* Fixed memory leak of `ChildHandlerNode` with reusable continuations (#2564).
+* Various documentation improvements (#2555, #2589, #2592, #2583, #2437, #2616, #2633, #2560).
+
+### Changelog relative to version 1.5.0-RC
+
+* Fail-fast during `emitAll` called from cancelled `onCompletion` operator (#2700).
+* Flows returned by `stateIn`/`shareIn` keep strong reference to sharing job (#2557).
+* Rename internal `TimeSource` to `AbstractTimeSource` due to import issues (#2691).
+* Reverted the change that triggered IDEA coroutines debugger crash (#2695, reverted #2291).
+* `watchosX64` target support for Kotlin/Native (#2524).
+* Various documentation fixes and improvements.
+
+## Version 1.5.0-RC
+
+### Channels API
+
+* Major channels API rework (#330, #974). Existing `offer`, `poll`, and `sendBlocking` methods are deprecated, internal `receiveCatching` and `onReceiveCatching` removed, `receiveOrNull` and `onReceiveOrNull` are completely deprecated. Previously deprecated `SendChannel.isFull` declaration is removed. Channel operators deprecated with `ERROR` are now `HIDDEN`.
+* New methods `receiveCatching`, `onReceiveCatching` `trySend`, `tryReceive`, and `trySendBlocking` along with the new result type `ChannelResult` are introduced. They provide better type safety, are less error-prone, and have a consistent future-proof naming scheme. The full rationale behind this change can be found [here](https://github.com/Kotlin/kotlinx.coroutines/issues/974#issuecomment-806569582).
+* `BroadcastChannel` and `ConflatedBroadcastChannel` are marked as `ObsoleteCoroutinesApi` in the favor or `SharedFlow` and `StateFlow`. The migration scheme can be found in their documentation. These classes will be deprecated in the next major release.
+* `callbackFlow` and `channelFlow` are promoted to stable API.
+
+### Reactive integrations
+
+* All existing API in modules `kotlinx-coroutines-rx2`, `kotlinx-coroutines-rx3`, `kotlinx-coroutines-reactive`, `kotlinx-coroutines-reactor`, and `kotlinx-coroutines-jdk9` were revisited and promoted to stable (#2545).
+* `publish` is no longer allowed to emit `null` values (#2646).
+* Misleading `awaitSingleOr*` functions on `Publisher` type are deprecated (#2591).
+* `MaybeSource.await` is deprecated in the favor of `awaitSingle`, additional lint functions for `Mono` are added in order to prevent ambiguous `Publisher` usages (#2628, #1587).
+* `ContextView` support in `kotlinx-coroutines-reactor` (#2575).
+* All reactive builders no longer ignore inner cancellation exceptions preventing their completion (#2262, #2646).
+* `MaybeSource.collect` and `Maybe.collect` properly finish when they are completed without a value (#2617).
+* All exceptions are now consistently handled according to reactive specification, whether they are considered 'fatal' or not by reactive frameworks (#2646).
+
+### Other improvements
+
+* `Flow.last` and `Flow.lastOrNull` operators (#2246).
+* `Flow.runningFold` operator (#2641).
+* `CoroutinesTimeout` rule for JUnit5 (#2197).
+* Internals of `Job` and `AbstractCoroutine` was reworked, resulting in smaller code size, less memory footprint, and better performance (#2513, #2512).
+* `CancellationException` from Kotlin standard library is used for cancellation on Koltin/JS and Kotlin/Native (#2638).
+* Introduced new `DelicateCoroutineApi` annotation that warns users about potential target API pitfalls and suggests studying API's documentation first. The only delicate API right now is `GlobalScope` (#2637).
+* Fixed bug introduced in `1.4.3` when `kotlinx-coroutines-core.jar` triggered IDEA debugger failure (#2619).
+* Fixed memory leak of `ChildHandlerNode` with reusable continuations (#2564).
+* Various documentation improvements (#2555, #2589, #2592, #2583, #2437, #2616, #2633, #2560).
+
+## Version 1.4.3
+
+### General changes
+
+* Thread context is properly preserved and restored for coroutines without `ThreadContextElement` (#985)
+* `ThreadContextElement`s are now restored in the opposite order from update (#2195)
+* Improved performance of combine with 4 parameters, thanks to @alexvanyo (#2419)
+* Debug agent sanitizer leaves at least one frame with source location (#1437)
+* Update Reactor version in `kotlinx-coroutines-reactor` to `3.4.1`, thanks to @sokomishalov (#2432)
+* `callInPlace` contract added to `ReceiveChannel.consume` (#941)
+* `CoroutineStart.UNDISPATCHED` promoted to stable API (#1393)
+* Kotlin updated to 1.4.30
+* `kotlinx.coroutines` are now released directly to MavenCentral
+* Reduced the size of `DispatchedCoroutine` by a field
+* Internal class `TimeSource` renamed to `SchedulerTimeSource` to prevent wildcard import issues (#2537)
+
+### Bug fixes
+
+* Fixed the problem that prevented implementation via delegation for `Job` interface (#2423)
+* Fixed incorrect ProGuard rules that allowed shrinking volatile felds (#1564)
+* Fixed `await`/`asDeferred` for `MinimalStage` implementations in jdk8 module (#2456)
+* Fixed bug when `onUndeliveredElement` wasn't called for unlimited channels (#2435)
+* Fixed a bug when `ListenableFuture.isCancelled` returned from `asListenableFuture` could have thrown an exception, thanks to @vadimsemenov (#2421)
+* Coroutine in `callbackFlow` and `produce` is properly cancelled when the channel was closed separately (#2506)
+
+## Version 1.4.2
+
+* Fixed `StackOverflowError` in `Job.toString` when `Job` is observed in its intermediate state (#2371).
+* Improved liveness and latency of `Dispatchers.Default` and `Dispatchers.IO` in low-loaded mode (#2381).
+* Improved performance of consecutive `Channel.cancel` invocations (#2384).
+* `SharingStarted` is now `fun` interface (#2397).
+* Additional lint settings for `SharedFlow` to catch programmatic errors early (#2376).
+* Fixed bug when mutex and semaphore were not released during cancellation (#2390, thanks to @Tilps for reproducing).
+* Some corner cases in cancellation propagation between coroutines and listenable futures are repaired (#1442, thanks to @vadimsemenov).
+* Fixed unconditional cast to `CoroutineStackFrame` in exception recovery that triggered failures of instrumented code (#2386).
+* Platform-specific dependencies are removed from `kotlinx-coroutines-javafx` (#2360).
+
## Version 1.4.1
This is a patch release with an important fix to the `SharedFlow` implementation.
@@ -225,12 +366,12 @@ Gradle version 5.3 or later to use this version of kotlinx.coroutines in your Ko
This version is the first stable release with [`Flow`](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/-flow/index.html) API.
All `Flow` API not marked with `@FlowPreview` or `@ExperimentalCoroutinesApi` annotations are stable and here to stay.
-Flow declarations marked with `@ExperimentalCoroutinesApi` have [the same guarantees](/docs/compatibility.md#experimental-api) as regular experimental API.
-Please note that API marked with `@FlowPreview` have [weak guarantees](/docs/compatibility.md#flow-preview-api) on source, binary and semantic compatibility.
+Flow declarations marked with `@ExperimentalCoroutinesApi` have [the same guarantees](/docs/topics/compatibility.md#experimental-api) as regular experimental API.
+Please note that API marked with `@FlowPreview` have [weak guarantees](/docs/topics/compatibility.md#flow-preview-api) on source, binary and semantic compatibility.
### Changelog
-* A new [guide section](/docs/flow.md) about Flow.
+* A new [guide section](/docs/topics/flow.md) about Flow.
* `CoroutineDispatcher.asExecutor` extension (#1450).
* Fixed bug when `select` statement could report the same exception twice (#1433).
* Fixed context preservation in `flatMapMerge` in a case when collected values were immediately emitted to another flow (#1440).
@@ -371,11 +512,11 @@ A lot of `Flow` improvements:
* `flatMap`, `merge` and `concatenate` are replaced with `flattenConcat`, `flattenMerge`, `flatMapConcat` and `flatMapMerge`.
* Various documentation improvements and minor bug fixes.
-Note that `Flow` **is not** leaving its [preview status](/docs/compatibility.md#flow-preview-api).
+Note that `Flow` **is not** leaving its [preview status](/docs/topics/compatibility.md#flow-preview-api).
## Version 1.2.0-alpha-2
-This release contains major [feature preview](/docs/compatibility.md#flow-preview-api): cold streams aka `Flow` (#254).
+This release contains major [feature preview](/docs/topics/compatibility.md#flow-preview-api): cold streams aka `Flow` (#254).
Performance:
* Performance of `Dispatcher.Main` initialization is significantly improved (#878).
@@ -498,7 +639,7 @@ Maintenance release:
* `Job()` wih parent now also cancels parent on failure consistently with other scopes.
* All coroutine builders and `Job` implementations propagate failure to the parent unless it is a `CancellationException`.
* Note, "scoping" builders don't "cancel the parent" verbatim, but rethrow the corresponding exception to the caller for handling.
- * `SupervisorJob()` and `supervisorScope { ... }` are introduced, allowing for a flexible implementation of custom exception-handling policies, see a [new section in the guide on supervision](docs/exception-handling.md#supervision).
+ * `SupervisorJob()` and `supervisorScope { ... }` are introduced, allowing for a flexible implementation of custom exception-handling policies, see a [new section in the guide on supervision](docs/topics/exception-handling.md#supervision).
* Got rid of `awaitAll` in documentation and rewrote `currentScope` section (see #624).
* **[Major]** Coroutine scheduler is used for `Dispatchers.Default` by default instead of deprecated `CommonPool`.
* "`DefaultDispatcher`" is used as a public name of the default impl (you'll see it thread names and in the guide).
@@ -557,7 +698,7 @@ Visible consequences of include more robust exception handling for large corouti
* `currentScope` and `coroutineScope` builders are introduced to extract and provide `CoroutineScope`.
* Factory methods to create `CoroutineScope` from `CoroutineContext` are introduced.
* `CoroutineScope.isActive` became an extension property.
- * New sections about structured concurrency in core guide: ["Structured concurrency"](docs/coroutines-guide.md#structured-concurrency), ["Scope builder"](docs/coroutines-guide.md#scope-builder) and ["Structured concurrency with async"](docs/coroutines-guide.md#structured-concurrency-with-async).
+ * New sections about structured concurrency in core guide: ["Structured concurrency"](docs/topics/coroutines-guide.md#structured-concurrency), ["Scope builder"](docs/topics/coroutines-guide.md#scope-builder) and ["Structured concurrency with async"](docs/topics/coroutines-guide.md#structured-concurrency-with-async).
* New section in UI guide with Android example: ["Structured concurrency, lifecycle and coroutine parent-child hierarchy"](ui/coroutines-guide-ui.md#structured-concurrency,-lifecycle-and-coroutine-parent-child-hierarchy).
* Deprecated reactive API is removed.
* Dispatchers are renamed and grouped in the Dispatchers object (see #41 and #533):
@@ -577,7 +718,7 @@ Visible consequences of include more robust exception handling for large corouti
## Version 0.25.0
* Major rework on exception-handling and cancellation in coroutines (see #333, #452 and #451):
- * New ["Exception Handling" section in the guide](docs/coroutines-guide.md#exception-handling) explains exceptions in coroutines.
+ * New ["Exception Handling" section in the guide](docs/topics/coroutines-guide.md#exception-handling) explains exceptions in coroutines.
* Semantics of `Job.cancel` resulting `Boolean` value changed &mdash; `true` means exception was handled by the job, caller shall handle otherwise.
* Exceptions are properly propagated from children to parents.
* Installed `CoroutineExceptionHandler` for a family of coroutines receives one aggregated exception in case of failure.
@@ -586,7 +727,7 @@ Visible consequences of include more robust exception handling for large corouti
* Introduced support for thread-local elements in coroutines context (see #119):
* `ThreadContextElement` API for custom thread-context sensitive context elements.
* `ThreadLocal.asContextElement()` extension function to convert an arbitrary thread-local into coroutine context element.
- * New ["Thread-local data" subsection in the guide](docs/coroutines-guide.md#thread-local-data) with examples.
+ * New ["Thread-local data" subsection in the guide](docs/topics/coroutines-guide.md#thread-local-data) with examples.
* SLF4J Mapped Diagnostic Context (MDC) integration is provided via `MDCContext` element defined in [`kotlinx-coroutines-slf4j`](integration/kotlinx-coroutines-slf4j/README.md) integration module.
* Introduced IO dispatcher to offload blocking I/O-intensive tasks (see #79).
* Introduced `ExecutorCoroutineDispatcher` instead of `CloseableCoroutineDispatcher` (see #385).
@@ -819,7 +960,7 @@ Visible consequences of include more robust exception handling for large corouti
* Fixed `actor` and `produce` so that a cancellation of a Job cancels the underlying channel (closes and removes all the pending messages).
* Fixed sporadic failure of `example-context-06` (see #160)
* Fixed hang of `Job.start` on lazy coroutine with attached `invokeOnCompletion` handler.
-* A more gradual introduction to `runBlocking` and coroutines in the [guide](docs/coroutines-guide.md) (see #166).
+* A more gradual introduction to `runBlocking` and coroutines in the [guide](docs/topics/coroutines-guide.md) (see #166).
## Version 0.19.3
@@ -870,7 +1011,7 @@ Visible consequences of include more robust exception handling for large corouti
* When a context is explicitly specified, `newCoroutineContext` function checks if there is any
interceptor/dispatcher defined in the context and uses `DefaultDispatcher` if there is none.
* `DefaultDispatcher` is currently defined to be equal to `CommonPool`.
- * Examples in the [guide](docs/coroutines-guide.md) now start with `launch { ... }` code and explanation on the nature
+ * Examples in the [guide](docs/topics/coroutines-guide.md) now start with `launch { ... }` code and explanation on the nature
and the need for coroutine context starts in "Coroutine context and dispatchers" section.
* Parent coroutines now wait for their children (see #125):
* Job _completing_ state is introduced in documentation as a state in which parent coroutine waits for its children.
@@ -930,7 +1071,7 @@ Visible consequences of include more robust exception handling for large corouti
## Version 0.17
* `CompletableDeferred` is introduced as a set-once event-like communication primitive (see #70).
- * [Coroutines guide](docs/coroutines-guide.md) uses it in a section on actors.
+ * [Coroutines guide](docs/topics/coroutines-guide.md) uses it in a section on actors.
* `CompletableDeferred` is an interface with private impl (courtesy of @fvasco, see #86).
* It extends `Deferred` interface with `complete` and `completeExceptionally` functions.
* `Job.join` and `Deferred.await` wait until a cancelled coroutine stops execution (see #64).
@@ -1076,7 +1217,7 @@ Visible consequences of include more robust exception handling for large corouti
* `actor` coroutine builder.
* Couple more examples for "Shared mutable state and concurrency" section and
"Channels are fair" section with ping-pong table example
- in [coroutines guide](docs/coroutines-guide.md).
+ in [coroutines guide](docs/topics/coroutines-guide.md).
## Version 0.11-rc
@@ -1084,7 +1225,7 @@ Visible consequences of include more robust exception handling for large corouti
* `Mutex` is moved to `kotlinx.coroutines.sync` package.
* `ClosedSendChannelException` is a subclass of `CancellationException` now.
* New sections on "Shared mutable state and concurrency" and "Select expression"
- in [coroutines guide](docs/coroutines-guide.md).
+ in [coroutines guide](docs/topics/coroutines-guide.md).
## Version 0.10-rc
@@ -1112,7 +1253,7 @@ Visible consequences of include more robust exception handling for large corouti
So, lazy coroutines do not need a separate state variable to track their started/not-started (new/active) status.
* Exception transparency in `Job.cancel` (original cause is rethrown).
* Clarified possible states for `Job`/`CancellableContinuation`/`Deferred` in docs.
-* Example on async-style functions and links to API reference site from [coroutines guide](docs/coroutines-guide.md).
+* Example on async-style functions and links to API reference site from [coroutines guide](docs/topics/coroutines-guide.md).
## Version 0.7-beta
@@ -1120,12 +1261,12 @@ Visible consequences of include more robust exception handling for large corouti
`RendezvousChannel` and `ArrayChannel` implementations, `Channel()` factory function and `buildChannel{}`
coroutines builder.
* `Here` context is renamed to `Unconfined` (the old name is deprecated).
-* A [guide on coroutines](docs/coroutines-guide.md) is expanded: sections on contexts and channels.
+* A [guide on coroutines](docs/topics/coroutines-guide.md) is expanded: sections on contexts and channels.
## Version 0.6-beta
* Switched to Kotlin version 1.1.0-beta-37.
-* A [guide on coroutines](docs/coroutines-guide.md) is expanded.
+* A [guide on coroutines](docs/topics/coroutines-guide.md) is expanded.
## Version 0.5-beta
@@ -1138,7 +1279,7 @@ Visible consequences of include more robust exception handling for large corouti
has a default implementation that returns `true`.
* `NonCancellable` context is introduced.
* Performance optimizations for cancellable continuations (fewer objects created).
-* A [guide on coroutines](docs/coroutines-guide.md) is added.
+* A [guide on coroutines](docs/topics/coroutines-guide.md) is added.
## Version 0.4-beta
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 7737062f..7d6e32d8 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -52,6 +52,10 @@ so do familiarize yourself with the following guidelines.
* Follow the style of writing tests that is used in this project:
name test functions as `testXxx`. Don't use backticks in test names.
* If you introduce any new public APIs:
+ * Comment on the existing issue if you want to work on it or create one beforehand.
+ Ensure that the issue not only describes a problem, but also describes a solution that had received a positive feedback. Propose a solution if there isn't any.
+ PRs with new API, but without a corresponding issue with a positive feedback about the proposed implementation are unlikely to
+ be approved or reviewed.
* All new APIs must come with documentation and tests.
* All new APIs are initially released with `@ExperimentalCoroutineApi` annotation and are graduated later.
* [Update the public API dumps](#updating-the-public-api-dump) and commit the resulting changes as well.
@@ -59,8 +63,6 @@ so do familiarize yourself with the following guidelines.
* If you plan large API additions, then please start by submitting an issue with the proposed API design
to gather community feedback.
* [Contact the maintainers](#contacting-maintainers) to coordinate any big piece of work in advance.
-* Comment on the existing issue if you want to work on it. Ensure that the issue not only describes a problem,
- but also describes a solution that had received a positive feedback. Propose a solution if there isn't any.
* Steps for contributing new integration modules are explained [here](integration/README.md#Contributing).
## Building
@@ -92,7 +94,7 @@ export JDK_18="$JAVA_HOME"
### Running the Knit tool
-* Use [Knit](https://github.com/Kotlin/kotlinx-knit/blob/master/README.md) for updates to documentation:
+* Use [Knit](https://github.com/Kotlin/kotlinx-knit/blob/main/README.md) for updates to documentation:
* Run `./gradlew knit` to update example files, links, tables of content.
* Commit updated documents and examples together with other changes.
diff --git a/METADATA b/METADATA
index 9859a06a..798558c5 100644
--- a/METADATA
+++ b/METADATA
@@ -5,11 +5,11 @@ third_party {
type: GIT
value: "https://github.com/Kotlin/kotlinx.coroutines"
}
- version: "1.4.1"
+ version: "1.5.2"
license_type: NOTICE
last_upgrade_date {
- year: 2020
+ year: 2021
month: 11
- day: 25
+ day: 01
}
}
diff --git a/README.md b/README.md
index 7bd8e5a7..6a13f07a 100644
--- a/README.md
+++ b/README.md
@@ -2,12 +2,12 @@
[![official JetBrains project](https://jb.gg/badges/official.svg)](https://confluence.jetbrains.com/display/ALL/JetBrains+on+GitHub)
[![GitHub license](https://img.shields.io/badge/license-Apache%20License%202.0-blue.svg?style=flat)](https://www.apache.org/licenses/LICENSE-2.0)
-[![Download](https://api.bintray.com/packages/kotlin/kotlinx/kotlinx.coroutines/images/download.svg?version=1.4.1) ](https://bintray.com/kotlin/kotlinx/kotlinx.coroutines/1.4.1)
-[![Kotlin](https://img.shields.io/badge/kotlin-1.4.0-blue.svg?logo=kotlin)](http://kotlinlang.org)
+[![Download](https://img.shields.io/maven-central/v/org.jetbrains.kotlinx/kotlinx-coroutines-core/1.5.2)](https://search.maven.org/artifact/org.jetbrains.kotlinx/kotlinx-coroutines-core/1.5.2/pom)
+[![Kotlin](https://img.shields.io/badge/kotlin-1.5.30-blue.svg?logo=kotlin)](http://kotlinlang.org)
[![Slack channel](https://img.shields.io/badge/chat-slack-green.svg?logo=slack)](https://kotlinlang.slack.com/messages/coroutines/)
Library support for Kotlin coroutines with [multiplatform](#multiplatform) support.
-This is a companion version for Kotlin `1.4.0` release.
+This is a companion version for the Kotlin `1.5.30` release.
```kotlin
suspend fun main() = coroutineScope {
@@ -35,7 +35,7 @@ suspend fun main() = coroutineScope {
* [select] expression support and more.
* [core/jvm](kotlinx-coroutines-core/jvm/) &mdash; additional core features available on Kotlin/JVM:
* [Dispatchers.IO] dispatcher for blocking coroutines;
- * [Executor.asCoroutineDispatcher] extension, custom thread pools, and more.
+ * [Executor.asCoroutineDispatcher][asCoroutineDispatcher] extension, custom thread pools, and more.
* [core/js](kotlinx-coroutines-core/js/) &mdash; additional core features available on Kotlin/JS:
* Integration with `Promise` via [Promise.await] and [promise] builder;
* Integration with `Window` via [Window.asCoroutineDispatcher], etc.
@@ -45,6 +45,7 @@ suspend fun main() = coroutineScope {
* [debug](kotlinx-coroutines-debug/README.md) &mdash; debug utilities for coroutines:
* [DebugProbes] API to probe, keep track of, print and dump active coroutines;
* [CoroutinesTimeout] test rule to automatically dump coroutines on test timeout.
+ * Automatic integration with [BlockHound](https://github.com/reactor/BlockHound).
* [reactive](reactive/README.md) &mdash; modules that provide builders and iteration support for various reactive streams libraries:
* Reactive Streams ([Publisher.collect], [Publisher.awaitSingle], [kotlinx.coroutines.reactive.publish], etc),
* Flow (JDK 9) (the same interface as for Reactive Streams),
@@ -64,20 +65,16 @@ suspend fun main() = coroutineScope {
* [Deep dive into Coroutines](https://www.youtube.com/watch?v=YrrUCSi72E8) (Roman Elizarov at KotlinConf 2017, [slides](https://www.slideshare.net/elizarov/deep-dive-into-coroutines-on-jvm-kotlinconf-2017))
* [Kotlin Coroutines in Practice](https://www.youtube.com/watch?v=a3agLJQ6vt8) (Roman Elizarov at KotlinConf 2018, [slides](https://www.slideshare.net/elizarov/kotlin-coroutines-in-practice-kotlinconf-2018))
* Guides and manuals:
- * [Guide to kotlinx.coroutines by example](https://kotlinlang.org/docs/reference/coroutines/coroutines-guide.html) (**read it first**)
+ * [Guide to kotlinx.coroutines by example](https://kotlinlang.org/docs/coroutines-guide.html) (**read it first**)
* [Guide to UI programming with coroutines](ui/coroutines-guide-ui.md)
- * [Debugging capabilities in kotlinx.coroutines](docs/debugging.md)
-* [Compatibility policy and experimental annotations](docs/compatibility.md)
+ * [Debugging capabilities in kotlinx.coroutines](docs/topics/debugging.md)
+* [Compatibility policy and experimental annotations](docs/topics/compatibility.md)
* [Change log for kotlinx.coroutines](CHANGES.md)
* [Coroutines design document (KEEP)](https://github.com/Kotlin/KEEP/blob/master/proposals/coroutines.md)
* [Full kotlinx.coroutines API reference](https://kotlin.github.io/kotlinx.coroutines)
## Using in your projects
-The libraries are published to [kotlinx](https://bintray.com/kotlin/kotlinx/kotlinx.coroutines) bintray repository,
-linked to [JCenter](https://bintray.com/bintray/jcenter?filterByPkgName=kotlinx.coroutines) and
-pushed to [Maven Central](https://search.maven.org/#search%7Cga%7C1%7Cg%3Aorg.jetbrains.kotlinx%20a%3Akotlinx-coroutines*).
-
### Maven
Add dependencies (you can also add other modules that you need):
@@ -86,7 +83,7 @@ Add dependencies (you can also add other modules that you need):
<dependency>
<groupId>org.jetbrains.kotlinx</groupId>
<artifactId>kotlinx-coroutines-core</artifactId>
- <version>1.4.1</version>
+ <version>1.5.2</version>
</dependency>
```
@@ -94,7 +91,7 @@ And make sure that you use the latest Kotlin version:
```xml
<properties>
- <kotlin.version>1.4.0</kotlin.version>
+ <kotlin.version>1.5.30</kotlin.version>
</properties>
```
@@ -104,7 +101,7 @@ Add dependencies (you can also add other modules that you need):
```groovy
dependencies {
- implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.1'
+ implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2'
}
```
@@ -112,15 +109,15 @@ And make sure that you use the latest Kotlin version:
```groovy
buildscript {
- ext.kotlin_version = '1.4.0'
+ ext.kotlin_version = '1.5.30'
}
```
-Make sure that you have either `jcenter()` or `mavenCentral()` in the list of repositories:
+Make sure that you have `mavenCentral()` in the list of repositories:
```
repository {
- jcenter()
+ mavenCentral()
}
```
@@ -130,7 +127,7 @@ Add dependencies (you can also add other modules that you need):
```groovy
dependencies {
- implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.1")
+ implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2")
}
```
@@ -138,79 +135,79 @@ And make sure that you use the latest Kotlin version:
```groovy
plugins {
- kotlin("jvm") version "1.4.0"
+ kotlin("jvm") version "1.5.20"
}
```
-Make sure that you have either `jcenter()` or `mavenCentral()` in the list of repositories.
-
-### Multiplatform
-
-Core modules of `kotlinx.coroutines` are also available for
-[Kotlin/JS](#js) and [Kotlin/Native](#native).
-In common code that should get compiled for different platforms, you can add dependency to `kotlinx-coroutines-core` right to the `commonMain` source set:
-```groovy
-commonMain {
- dependencies {
- implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.1")
- }
-}
-```
+Make sure that you have `mavenCentral()` in the list of repositories.
### Android
Add [`kotlinx-coroutines-android`](ui/kotlinx-coroutines-android)
-module as dependency when using `kotlinx.coroutines` on Android:
+module as a dependency when using `kotlinx.coroutines` on Android:
```groovy
-implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.1'
+implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.2'
```
-This gives you access to Android [Dispatchers.Main]
-coroutine dispatcher and also makes sure that in case of crashed coroutine with unhandled exception this
-exception is logged before crashing Android application, similarly to the way uncaught exceptions in
-threads are handled by Android runtime.
+This gives you access to the Android [Dispatchers.Main]
+coroutine dispatcher and also makes sure that in case of a crashed coroutine with an unhandled exception that
+this exception is logged before crashing the Android application, similarly to the way uncaught exceptions in
+threads are handled by the Android runtime.
#### R8 and ProGuard
R8 and ProGuard rules are bundled into the [`kotlinx-coroutines-android`](ui/kotlinx-coroutines-android) module.
-For more details see ["Optimization" section for Android](ui/kotlinx-coroutines-android/README.md#optimization).
+For more details see ["Optimization" section for Android](ui/kotlinx-coroutines-android/README.md#optimization).
#### Avoiding including the debug infrastructure in the resulting APK
The `kotlinx-coroutines-core` artifact contains a resource file that is not required for the coroutines to operate
normally and is only used by the debugger. To exclude it at no loss of functionality, add the following snippet to the
-`android` block in your gradle file for the application subproject:
+`android` block in your Gradle file for the application subproject:
```groovy
packagingOptions {
- exclude "DebugProbesKt.bin"
+ resources.excludes += "DebugProbesKt.bin"
}
```
-### JS
+### Multiplatform
-[Kotlin/JS](https://kotlinlang.org/docs/reference/js-overview.html) version of `kotlinx.coroutines` is published as
-[`kotlinx-coroutines-core-js`](https://search.maven.org/artifact/org.jetbrains.kotlinx/kotlinx-coroutines-core-js/1.4.1/jar)
-(follow the link to get the dependency declaration snippet).
-
-You can also use [`kotlinx-coroutines-core`](https://www.npmjs.com/package/kotlinx-coroutines-core) package via NPM.
+Core modules of `kotlinx.coroutines` are also available for
+[Kotlin/JS](https://kotlinlang.org/docs/reference/js-overview.html) and [Kotlin/Native](https://kotlinlang.org/docs/reference/native-overview.html).
-### Native
+In common code that should get compiled for different platforms, you can add a dependency to `kotlinx-coroutines-core` right to the `commonMain` source set:
+```groovy
+commonMain {
+ dependencies {
+ implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2")
+ }
+}
+```
-[Kotlin/Native](https://kotlinlang.org/docs/reference/native-overview.html) version of `kotlinx.coroutines` is published as
-[`kotlinx-coroutines-core-native`](https://search.maven.org/artifact/org.jetbrains.kotlinx/kotlinx-coroutines-core-native/1.4.1/jar)
-(follow the link to get the dependency declaration snippet).
+No more additional dependencies are needed, platform-specific artifacts will be resolved automatically via Gradle metadata available since Gradle 5.3.
-Only single-threaded code (JS-style) on Kotlin/Native is currently supported.
-Kotlin/Native supports only Gradle version 4.10 and you need to enable Gradle metadata in your
-`settings.gradle` file:
+Platform-specific dependencies are recommended to be used only for non-multiplatform projects that are compiled only for target platform.
-```groovy
-enableFeaturePreview('GRADLE_METADATA')
-```
+#### JS
+
+Kotlin/JS version of `kotlinx.coroutines` is published as
+[`kotlinx-coroutines-core-js`](https://search.maven.org/artifact/org.jetbrains.kotlinx/kotlinx-coroutines-core-js/1.5.2/jar)
+(follow the link to get the dependency declaration snippet) and as [`kotlinx-coroutines-core`](https://www.npmjs.com/package/kotlinx-coroutines-core) NPM package.
+
+#### Native
+
+Kotlin/Native version of `kotlinx.coroutines` is published as
+[`kotlinx-coroutines-core-$platform`](https://mvnrepository.com/search?q=kotlinx-coroutines-core-) where `$platform` is
+the target Kotlin/Native platform. [List of currently supported targets](https://github.com/Kotlin/kotlinx.coroutines/blob/master/gradle/compile-native-multiplatform.gradle#L16).
+
+
+Only single-threaded code (JS-style) on Kotlin/Native is supported in stable versions.
+Additionally, a special `-native-mt` version is released on a regular basis, for the state of multi-threaded coroutines support
+please follow the [corresponding issue](https://github.com/Kotlin/kotlinx.coroutines/issues/462) for the additional details.
Since Kotlin/Native does not generally provide binary compatibility between versions,
-you should use the same version of Kotlin/Native compiler as was used to build `kotlinx.coroutines`.
+you should use the same version of the Kotlin/Native compiler as was used to build `kotlinx.coroutines`.
## Building and Contributing
@@ -218,6 +215,7 @@ See [Contributing Guidelines](CONTRIBUTING.md).
<!--- MODULE kotlinx-coroutines-core -->
<!--- INDEX kotlinx.coroutines -->
+
[launch]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/launch.html
[async]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/async.html
[Job]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job/index.html
@@ -235,56 +233,85 @@ See [Contributing Guidelines](CONTRIBUTING.md).
[SupervisorJob()]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-supervisor-job.html
[CoroutineExceptionHandler]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-exception-handler/index.html
[Dispatchers.IO]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-dispatchers/-i-o.html
-[Executor.asCoroutineDispatcher]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/java.util.concurrent.-executor/as-coroutine-dispatcher.html
-[Promise.await]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/kotlin.js.-promise/await.html
+[asCoroutineDispatcher]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/as-coroutine-dispatcher.html
+[Promise.await]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/await.html
[promise]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/promise.html
-[Window.asCoroutineDispatcher]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/org.w3c.dom.-window/as-coroutine-dispatcher.html
+[Window.asCoroutineDispatcher]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/as-coroutine-dispatcher.html
+
<!--- INDEX kotlinx.coroutines.flow -->
+
[Flow]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/-flow/index.html
[_flow]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/flow.html
[filter]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/filter.html
[map]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/map.html
+
<!--- INDEX kotlinx.coroutines.channels -->
+
[Channel]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-channel/index.html
+
<!--- INDEX kotlinx.coroutines.selects -->
+
[select]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.selects/select.html
+
<!--- INDEX kotlinx.coroutines.sync -->
+
[Mutex]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.sync/-mutex/index.html
[Semaphore]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.sync/-semaphore/index.html
+
<!--- MODULE kotlinx-coroutines-test -->
<!--- INDEX kotlinx.coroutines.test -->
-[Dispatchers.setMain]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-test/kotlinx.coroutines.test/kotlinx.coroutines.-dispatchers/set-main.html
+
+[Dispatchers.setMain]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-test/kotlinx.coroutines.test/set-main.html
[TestCoroutineScope]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-test/kotlinx.coroutines.test/-test-coroutine-scope/index.html
+
<!--- MODULE kotlinx-coroutines-debug -->
<!--- INDEX kotlinx.coroutines.debug -->
+
[DebugProbes]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-debug/kotlinx.coroutines.debug/-debug-probes/index.html
+
<!--- INDEX kotlinx.coroutines.debug.junit4 -->
+
[CoroutinesTimeout]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-debug/kotlinx.coroutines.debug.junit4/-coroutines-timeout/index.html
+
<!--- MODULE kotlinx-coroutines-slf4j -->
<!--- INDEX kotlinx.coroutines.slf4j -->
+
[MDCContext]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-slf4j/kotlinx.coroutines.slf4j/-m-d-c-context/index.html
+
<!--- MODULE kotlinx-coroutines-jdk8 -->
<!--- INDEX kotlinx.coroutines.future -->
-[CompletionStage.await]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-jdk8/kotlinx.coroutines.future/java.util.concurrent.-completion-stage/await.html
+
+[CompletionStage.await]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-jdk8/kotlinx.coroutines.future/await.html
+
<!--- MODULE kotlinx-coroutines-guava -->
<!--- INDEX kotlinx.coroutines.guava -->
-[ListenableFuture.await]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-guava/kotlinx.coroutines.guava/com.google.common.util.concurrent.-listenable-future/await.html
+
+[ListenableFuture.await]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-guava/kotlinx.coroutines.guava/await.html
+
<!--- MODULE kotlinx-coroutines-play-services -->
<!--- INDEX kotlinx.coroutines.tasks -->
-[Task.await]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-play-services/kotlinx.coroutines.tasks/com.google.android.gms.tasks.-task/await.html
+
+[Task.await]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-play-services/kotlinx.coroutines.tasks/await.html
+
<!--- MODULE kotlinx-coroutines-reactive -->
<!--- INDEX kotlinx.coroutines.reactive -->
-[Publisher.collect]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-reactive/kotlinx.coroutines.reactive/org.reactivestreams.-publisher/collect.html
-[Publisher.awaitSingle]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-reactive/kotlinx.coroutines.reactive/org.reactivestreams.-publisher/await-single.html
+
+[Publisher.collect]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-reactive/kotlinx.coroutines.reactive/collect.html
+[Publisher.awaitSingle]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-reactive/kotlinx.coroutines.reactive/await-single.html
[kotlinx.coroutines.reactive.publish]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-reactive/kotlinx.coroutines.reactive/publish.html
+
<!--- MODULE kotlinx-coroutines-rx2 -->
<!--- INDEX kotlinx.coroutines.rx2 -->
+
[rxFlowable]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-rx2/kotlinx.coroutines.rx2/rx-flowable.html
[rxSingle]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-rx2/kotlinx.coroutines.rx2/rx-single.html
+
<!--- MODULE kotlinx-coroutines-rx2 -->
<!--- INDEX kotlinx.coroutines.rx2 -->
<!--- MODULE kotlinx-coroutines-reactor -->
<!--- INDEX kotlinx.coroutines.reactor -->
+
[flux]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-reactor/kotlinx.coroutines.reactor/flux.html
[mono]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-reactor/kotlinx.coroutines.reactor/mono.html
+
<!--- END -->
diff --git a/RELEASE.md b/RELEASE.md
index b2a08b67..edc7726a 100644
--- a/RELEASE.md
+++ b/RELEASE.md
@@ -18,9 +18,7 @@ To release new `<version>` of `kotlinx-coroutines`:
* [`kotlinx-coroutines-test/README.md`](kotlinx-coroutines-test/README.md)
* [`coroutines-guide-ui.md`](ui/coroutines-guide-ui.md)
* Properties
- * [`gradle.properties`](gradle.properties)
- * [`ui/kotlinx-coroutines-android/example-app/gradle.properties`](ui/kotlinx-coroutines-android/example-app/gradle.properties)
- * [`ui/kotlinx-coroutines-android/animation-app/gradle.properties`](ui/kotlinx-coroutines-android/animation-app/gradle.properties)
+ * [`gradle.properties`](gradle.properties)
* Make sure to **exclude** `CHANGES.md` from replacements.
As an alternative approach you can use `./bump-version.sh old_version new_version`
@@ -36,7 +34,7 @@ To release new `<version>` of `kotlinx-coroutines`:
7. Commit updated files to a new version branch:<br>
`git commit -a -m "Version <version>"`
-8. Push new version into the branch:<br>
+8. Push the new version into the branch:<br>
`git push -u origin version-<version>`
9. Create Pull-Request on GitHub from `version-<version>` branch into `master`:
@@ -61,10 +59,9 @@ To release new `<version>` of `kotlinx-coroutines`:
(make sure you have [Docker](https://www.docker.com/) installed first): <br>
`site/deploy.sh <version> push`
-4. In [Bintray](https://bintray.com/kotlin/kotlinx/kotlinx.coroutines) admin interface:
- * Publish artifacts of the new version.
- * Wait until newly published version becomes the most recent.
- * Sync to Maven Central.
+4. In [Nexus](https://oss.sonatype.org/#stagingRepositories) admin interface:
+ * Close the repository and wait for it to verify.
+ * Release the repository.
5. Announce new release in [Slack](https://kotlinlang.slack.com)
diff --git a/benchmarks/build.gradle.kts b/benchmarks/build.gradle.kts
index b60dcbc8..ce0bff1c 100644
--- a/benchmarks/build.gradle.kts
+++ b/benchmarks/build.gradle.kts
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
@file:Suppress("UnstableApiUsage")
diff --git a/benchmarks/src/jmh/java/benchmarks/flow/scrabble/RxJava2PlaysScrabble.java b/benchmarks/src/jmh/java/benchmarks/flow/scrabble/RxJava2PlaysScrabble.java
index 04f72103..6d9169d3 100644
--- a/benchmarks/src/jmh/java/benchmarks/flow/scrabble/RxJava2PlaysScrabble.java
+++ b/benchmarks/src/jmh/java/benchmarks/flow/scrabble/RxJava2PlaysScrabble.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package benchmarks.flow.scrabble;
diff --git a/benchmarks/src/jmh/java/benchmarks/flow/scrabble/RxJava2PlaysScrabbleOpt.java b/benchmarks/src/jmh/java/benchmarks/flow/scrabble/RxJava2PlaysScrabbleOpt.java
index 71c7604d..2d900cad 100644
--- a/benchmarks/src/jmh/java/benchmarks/flow/scrabble/RxJava2PlaysScrabbleOpt.java
+++ b/benchmarks/src/jmh/java/benchmarks/flow/scrabble/RxJava2PlaysScrabbleOpt.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package benchmarks.flow.scrabble;
diff --git a/benchmarks/src/jmh/java/benchmarks/flow/scrabble/optimizations/FlowableCharSequence.java b/benchmarks/src/jmh/java/benchmarks/flow/scrabble/optimizations/FlowableCharSequence.java
index 5f93b4ee..0acf98d0 100644
--- a/benchmarks/src/jmh/java/benchmarks/flow/scrabble/optimizations/FlowableCharSequence.java
+++ b/benchmarks/src/jmh/java/benchmarks/flow/scrabble/optimizations/FlowableCharSequence.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package benchmarks.flow.scrabble.optimizations;
diff --git a/benchmarks/src/jmh/java/benchmarks/flow/scrabble/optimizations/FlowableSplit.java b/benchmarks/src/jmh/java/benchmarks/flow/scrabble/optimizations/FlowableSplit.java
index af8696c8..1c78f5ec 100644
--- a/benchmarks/src/jmh/java/benchmarks/flow/scrabble/optimizations/FlowableSplit.java
+++ b/benchmarks/src/jmh/java/benchmarks/flow/scrabble/optimizations/FlowableSplit.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package benchmarks.flow.scrabble.optimizations;
diff --git a/benchmarks/src/jmh/java/benchmarks/flow/scrabble/optimizations/StringFlowable.java b/benchmarks/src/jmh/java/benchmarks/flow/scrabble/optimizations/StringFlowable.java
index cf6cc79b..f51112b4 100644
--- a/benchmarks/src/jmh/java/benchmarks/flow/scrabble/optimizations/StringFlowable.java
+++ b/benchmarks/src/jmh/java/benchmarks/flow/scrabble/optimizations/StringFlowable.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package benchmarks.flow.scrabble.optimizations;
diff --git a/benchmarks/src/jmh/kotlin/benchmarks/ChannelProducerConsumerBenchmark.kt b/benchmarks/src/jmh/kotlin/benchmarks/ChannelProducerConsumerBenchmark.kt
index deeea77a..0fa50489 100644
--- a/benchmarks/src/jmh/kotlin/benchmarks/ChannelProducerConsumerBenchmark.kt
+++ b/benchmarks/src/jmh/kotlin/benchmarks/ChannelProducerConsumerBenchmark.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package benchmarks
diff --git a/benchmarks/src/jmh/kotlin/benchmarks/ChannelSinkBenchmark.kt b/benchmarks/src/jmh/kotlin/benchmarks/ChannelSinkBenchmark.kt
index 6c5b6231..f706d3aa 100644
--- a/benchmarks/src/jmh/kotlin/benchmarks/ChannelSinkBenchmark.kt
+++ b/benchmarks/src/jmh/kotlin/benchmarks/ChannelSinkBenchmark.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package benchmarks
@@ -10,7 +10,7 @@ import org.openjdk.jmh.annotations.*
import java.util.concurrent.*
import kotlin.coroutines.*
-@Warmup(iterations = 5, time = 1)
+@Warmup(iterations = 7, time = 1)
@Measurement(iterations = 5, time = 1)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@@ -41,7 +41,7 @@ open class ChannelSinkBenchmark {
private suspend inline fun run(context: CoroutineContext): Int {
return Channel
- .range(1, 1_000_000, context)
+ .range(1, 10_000, context)
.filter(context) { it % 4 == 0 }
.fold(0) { a, b -> a + b }
}
diff --git a/benchmarks/src/jmh/kotlin/benchmarks/ChannelSinkDepthBenchmark.kt b/benchmarks/src/jmh/kotlin/benchmarks/ChannelSinkDepthBenchmark.kt
new file mode 100644
index 00000000..d3f6be67
--- /dev/null
+++ b/benchmarks/src/jmh/kotlin/benchmarks/ChannelSinkDepthBenchmark.kt
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package benchmarks
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.channels.*
+import org.openjdk.jmh.annotations.*
+import java.util.concurrent.*
+import kotlin.coroutines.*
+
+@Warmup(iterations = 7, time = 1)
+@Measurement(iterations = 5, time = 1)
+@BenchmarkMode(Mode.AverageTime)
+@OutputTimeUnit(TimeUnit.MILLISECONDS)
+@State(Scope.Benchmark)
+@Fork(2)
+open class ChannelSinkDepthBenchmark {
+ private val tl = ThreadLocal.withInitial({ 42 })
+
+ private val unconfinedOneElement = Dispatchers.Unconfined + tl.asContextElement()
+
+ @Benchmark
+ fun depth1(): Int = runBlocking {
+ run(1, unconfinedOneElement)
+ }
+
+ @Benchmark
+ fun depth10(): Int = runBlocking {
+ run(10, unconfinedOneElement)
+ }
+
+ @Benchmark
+ fun depth100(): Int = runBlocking {
+ run(100, unconfinedOneElement)
+ }
+
+ @Benchmark
+ fun depth1000(): Int = runBlocking {
+ run(1000, unconfinedOneElement)
+ }
+
+ private suspend inline fun run(callTraceDepth: Int, context: CoroutineContext): Int {
+ return Channel
+ .range(1, 10_000, context)
+ .filter(callTraceDepth, context) { it % 4 == 0 }
+ .fold(0) { a, b -> a + b }
+ }
+
+ private fun Channel.Factory.range(start: Int, count: Int, context: CoroutineContext) =
+ GlobalScope.produce(context) {
+ for (i in start until (start + count))
+ send(i)
+ }
+
+ // Migrated from deprecated operators, are good only for stressing channels
+
+ private fun ReceiveChannel<Int>.filter(
+ callTraceDepth: Int,
+ context: CoroutineContext = Dispatchers.Unconfined,
+ predicate: suspend (Int) -> Boolean
+ ): ReceiveChannel<Int> =
+ GlobalScope.produce(context, onCompletion = { cancel() }) {
+ deeplyNestedFilter(this, callTraceDepth, predicate)
+ }
+
+ private suspend fun ReceiveChannel<Int>.deeplyNestedFilter(
+ sink: ProducerScope<Int>,
+ depth: Int,
+ predicate: suspend (Int) -> Boolean
+ ) {
+ if (depth <= 1) {
+ for (e in this) {
+ if (predicate(e)) sink.send(e)
+ }
+ } else {
+ deeplyNestedFilter(sink, depth - 1, predicate)
+ require(true) // tail-call
+ }
+ }
+
+ private suspend inline fun <E, R> ReceiveChannel<E>.fold(initial: R, operation: (acc: R, E) -> R): R {
+ var accumulator = initial
+ consumeEach {
+ accumulator = operation(accumulator, it)
+ }
+ return accumulator
+ }
+}
+
diff --git a/benchmarks/src/jmh/kotlin/benchmarks/ParametrizedDispatcherBase.kt b/benchmarks/src/jmh/kotlin/benchmarks/ParametrizedDispatcherBase.kt
index b635d1ef..9948a371 100644
--- a/benchmarks/src/jmh/kotlin/benchmarks/ParametrizedDispatcherBase.kt
+++ b/benchmarks/src/jmh/kotlin/benchmarks/ParametrizedDispatcherBase.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package benchmarks
diff --git a/benchmarks/src/jmh/kotlin/benchmarks/SemaphoreBenchmark.kt b/benchmarks/src/jmh/kotlin/benchmarks/SemaphoreBenchmark.kt
index 5da5dc89..40ddc8ec 100644
--- a/benchmarks/src/jmh/kotlin/benchmarks/SemaphoreBenchmark.kt
+++ b/benchmarks/src/jmh/kotlin/benchmarks/SemaphoreBenchmark.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package benchmarks
diff --git a/benchmarks/src/jmh/kotlin/benchmarks/akka/PingPongAkkaBenchmark.kt b/benchmarks/src/jmh/kotlin/benchmarks/akka/PingPongAkkaBenchmark.kt
index ea9aeca9..df940752 100644
--- a/benchmarks/src/jmh/kotlin/benchmarks/akka/PingPongAkkaBenchmark.kt
+++ b/benchmarks/src/jmh/kotlin/benchmarks/akka/PingPongAkkaBenchmark.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package benchmarks.akka
diff --git a/benchmarks/src/jmh/kotlin/benchmarks/akka/StatefulActorAkkaBenchmark.kt b/benchmarks/src/jmh/kotlin/benchmarks/akka/StatefulActorAkkaBenchmark.kt
index 5cfb86dd..fef641ae 100644
--- a/benchmarks/src/jmh/kotlin/benchmarks/akka/StatefulActorAkkaBenchmark.kt
+++ b/benchmarks/src/jmh/kotlin/benchmarks/akka/StatefulActorAkkaBenchmark.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package benchmarks.akka
diff --git a/benchmarks/src/jmh/kotlin/benchmarks/flow/CombineFlowsBenchmark.kt b/benchmarks/src/jmh/kotlin/benchmarks/flow/CombineFlowsBenchmark.kt
index 4725ceda..be371548 100644
--- a/benchmarks/src/jmh/kotlin/benchmarks/flow/CombineFlowsBenchmark.kt
+++ b/benchmarks/src/jmh/kotlin/benchmarks/flow/CombineFlowsBenchmark.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package benchmarks.flow
diff --git a/benchmarks/src/jmh/kotlin/benchmarks/flow/CombineTwoFlowsBenchmark.kt b/benchmarks/src/jmh/kotlin/benchmarks/flow/CombineTwoFlowsBenchmark.kt
index f7fbc6cf..bc8d8687 100644
--- a/benchmarks/src/jmh/kotlin/benchmarks/flow/CombineTwoFlowsBenchmark.kt
+++ b/benchmarks/src/jmh/kotlin/benchmarks/flow/CombineTwoFlowsBenchmark.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package benchmarks.flow
diff --git a/benchmarks/src/jmh/kotlin/benchmarks/flow/FlatMapMergeBenchmark.kt b/benchmarks/src/jmh/kotlin/benchmarks/flow/FlatMapMergeBenchmark.kt
index f3b2082a..32d35475 100644
--- a/benchmarks/src/jmh/kotlin/benchmarks/flow/FlatMapMergeBenchmark.kt
+++ b/benchmarks/src/jmh/kotlin/benchmarks/flow/FlatMapMergeBenchmark.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package benchmarks.flow
diff --git a/benchmarks/src/jmh/kotlin/benchmarks/flow/FlowFlattenMergeBenchmark.kt b/benchmarks/src/jmh/kotlin/benchmarks/flow/FlowFlattenMergeBenchmark.kt
index 14989888..09d841eb 100644
--- a/benchmarks/src/jmh/kotlin/benchmarks/flow/FlowFlattenMergeBenchmark.kt
+++ b/benchmarks/src/jmh/kotlin/benchmarks/flow/FlowFlattenMergeBenchmark.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package benchmarks.flow
diff --git a/benchmarks/src/jmh/kotlin/benchmarks/flow/NumbersBenchmark.kt b/benchmarks/src/jmh/kotlin/benchmarks/flow/NumbersBenchmark.kt
index 8453f5c7..636b334d 100644
--- a/benchmarks/src/jmh/kotlin/benchmarks/flow/NumbersBenchmark.kt
+++ b/benchmarks/src/jmh/kotlin/benchmarks/flow/NumbersBenchmark.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
diff --git a/benchmarks/src/jmh/kotlin/benchmarks/flow/SafeFlowBenchmark.kt b/benchmarks/src/jmh/kotlin/benchmarks/flow/SafeFlowBenchmark.kt
index 258df9b0..d957bdbe 100644
--- a/benchmarks/src/jmh/kotlin/benchmarks/flow/SafeFlowBenchmark.kt
+++ b/benchmarks/src/jmh/kotlin/benchmarks/flow/SafeFlowBenchmark.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package benchmarks.flow
diff --git a/benchmarks/src/jmh/kotlin/benchmarks/flow/TakeBenchmark.kt b/benchmarks/src/jmh/kotlin/benchmarks/flow/TakeBenchmark.kt
index 1e12e2c3..a0a2decc 100644
--- a/benchmarks/src/jmh/kotlin/benchmarks/flow/TakeBenchmark.kt
+++ b/benchmarks/src/jmh/kotlin/benchmarks/flow/TakeBenchmark.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package benchmarks.flow
diff --git a/benchmarks/src/jmh/kotlin/benchmarks/flow/TakeWhileBenchmark.kt b/benchmarks/src/jmh/kotlin/benchmarks/flow/TakeWhileBenchmark.kt
index fd3d3cdb..7501e2c4 100644
--- a/benchmarks/src/jmh/kotlin/benchmarks/flow/TakeWhileBenchmark.kt
+++ b/benchmarks/src/jmh/kotlin/benchmarks/flow/TakeWhileBenchmark.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
@file:Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER")
diff --git a/benchmarks/src/jmh/kotlin/benchmarks/flow/scrabble/FlowPlaysScrabbleBase.kt b/benchmarks/src/jmh/kotlin/benchmarks/flow/scrabble/FlowPlaysScrabbleBase.kt
index 3501bdfe..b8e1f450 100644
--- a/benchmarks/src/jmh/kotlin/benchmarks/flow/scrabble/FlowPlaysScrabbleBase.kt
+++ b/benchmarks/src/jmh/kotlin/benchmarks/flow/scrabble/FlowPlaysScrabbleBase.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package benchmarks.flow.scrabble
diff --git a/benchmarks/src/jmh/kotlin/benchmarks/flow/scrabble/FlowPlaysScrabbleOpt.kt b/benchmarks/src/jmh/kotlin/benchmarks/flow/scrabble/FlowPlaysScrabbleOpt.kt
index 2573d30d..cd27cca0 100644
--- a/benchmarks/src/jmh/kotlin/benchmarks/flow/scrabble/FlowPlaysScrabbleOpt.kt
+++ b/benchmarks/src/jmh/kotlin/benchmarks/flow/scrabble/FlowPlaysScrabbleOpt.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package benchmarks.flow.scrabble
diff --git a/benchmarks/src/jmh/kotlin/benchmarks/flow/scrabble/IterableSpliterator.kt b/benchmarks/src/jmh/kotlin/benchmarks/flow/scrabble/IterableSpliterator.kt
index 32c0d4c8..e78d7bd0 100644
--- a/benchmarks/src/jmh/kotlin/benchmarks/flow/scrabble/IterableSpliterator.kt
+++ b/benchmarks/src/jmh/kotlin/benchmarks/flow/scrabble/IterableSpliterator.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package benchmarks.flow.scrabble
diff --git a/benchmarks/src/jmh/kotlin/benchmarks/flow/scrabble/ReactorPlaysScrabble.kt b/benchmarks/src/jmh/kotlin/benchmarks/flow/scrabble/ReactorPlaysScrabble.kt
index 2283d6c3..1db4dae0 100644
--- a/benchmarks/src/jmh/kotlin/benchmarks/flow/scrabble/ReactorPlaysScrabble.kt
+++ b/benchmarks/src/jmh/kotlin/benchmarks/flow/scrabble/ReactorPlaysScrabble.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package benchmarks.flow.scrabble
diff --git a/benchmarks/src/jmh/kotlin/benchmarks/flow/scrabble/SaneFlowPlaysScrabble.kt b/benchmarks/src/jmh/kotlin/benchmarks/flow/scrabble/SaneFlowPlaysScrabble.kt
index ad97dfa3..533bc5e6 100644
--- a/benchmarks/src/jmh/kotlin/benchmarks/flow/scrabble/SaneFlowPlaysScrabble.kt
+++ b/benchmarks/src/jmh/kotlin/benchmarks/flow/scrabble/SaneFlowPlaysScrabble.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package benchmarks.flow.scrabble
diff --git a/benchmarks/src/jmh/kotlin/benchmarks/flow/scrabble/SequencePlaysScrabble.kt b/benchmarks/src/jmh/kotlin/benchmarks/flow/scrabble/SequencePlaysScrabble.kt
index fa944fac..e7bd1f5f 100644
--- a/benchmarks/src/jmh/kotlin/benchmarks/flow/scrabble/SequencePlaysScrabble.kt
+++ b/benchmarks/src/jmh/kotlin/benchmarks/flow/scrabble/SequencePlaysScrabble.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package benchmarks.flow.scrabble
diff --git a/benchmarks/src/jmh/kotlin/benchmarks/flow/scrabble/ShakespearePlaysScrabble.kt b/benchmarks/src/jmh/kotlin/benchmarks/flow/scrabble/ShakespearePlaysScrabble.kt
index 7beb54cc..006d36c0 100644
--- a/benchmarks/src/jmh/kotlin/benchmarks/flow/scrabble/ShakespearePlaysScrabble.kt
+++ b/benchmarks/src/jmh/kotlin/benchmarks/flow/scrabble/ShakespearePlaysScrabble.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package benchmarks.flow.scrabble
diff --git a/benchmarks/src/jmh/kotlin/benchmarks/scheduler/DispatchersContextSwitchBenchmark.kt b/benchmarks/src/jmh/kotlin/benchmarks/scheduler/DispatchersContextSwitchBenchmark.kt
index 3012b917..1fe70926 100644
--- a/benchmarks/src/jmh/kotlin/benchmarks/scheduler/DispatchersContextSwitchBenchmark.kt
+++ b/benchmarks/src/jmh/kotlin/benchmarks/scheduler/DispatchersContextSwitchBenchmark.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package benchmarks.scheduler
diff --git a/benchmarks/src/jmh/kotlin/benchmarks/scheduler/ForkJoinBenchmark.kt b/benchmarks/src/jmh/kotlin/benchmarks/scheduler/ForkJoinBenchmark.kt
index 724a5909..20bdfa34 100644
--- a/benchmarks/src/jmh/kotlin/benchmarks/scheduler/ForkJoinBenchmark.kt
+++ b/benchmarks/src/jmh/kotlin/benchmarks/scheduler/ForkJoinBenchmark.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package benchmarks.scheduler
diff --git a/benchmarks/src/jmh/kotlin/benchmarks/scheduler/LaunchBenchmark.kt b/benchmarks/src/jmh/kotlin/benchmarks/scheduler/LaunchBenchmark.kt
index a0de6016..d64fdd2f 100644
--- a/benchmarks/src/jmh/kotlin/benchmarks/scheduler/LaunchBenchmark.kt
+++ b/benchmarks/src/jmh/kotlin/benchmarks/scheduler/LaunchBenchmark.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package benchmarks.scheduler
diff --git a/benchmarks/src/jmh/kotlin/benchmarks/scheduler/StatefulAwaitsBenchmark.kt b/benchmarks/src/jmh/kotlin/benchmarks/scheduler/StatefulAwaitsBenchmark.kt
index f829573c..c5b34eda 100644
--- a/benchmarks/src/jmh/kotlin/benchmarks/scheduler/StatefulAwaitsBenchmark.kt
+++ b/benchmarks/src/jmh/kotlin/benchmarks/scheduler/StatefulAwaitsBenchmark.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package benchmarks.scheduler
diff --git a/benchmarks/src/jmh/kotlin/benchmarks/scheduler/actors/ConcurrentStatefulActorBenchmark.kt b/benchmarks/src/jmh/kotlin/benchmarks/scheduler/actors/ConcurrentStatefulActorBenchmark.kt
index 6ac97ad3..1ffb5207 100644
--- a/benchmarks/src/jmh/kotlin/benchmarks/scheduler/actors/ConcurrentStatefulActorBenchmark.kt
+++ b/benchmarks/src/jmh/kotlin/benchmarks/scheduler/actors/ConcurrentStatefulActorBenchmark.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package benchmarks.scheduler.actors
diff --git a/benchmarks/src/jmh/kotlin/benchmarks/scheduler/actors/CycledActorsBenchmark.kt b/benchmarks/src/jmh/kotlin/benchmarks/scheduler/actors/CycledActorsBenchmark.kt
index 71018abc..cd212cc1 100644
--- a/benchmarks/src/jmh/kotlin/benchmarks/scheduler/actors/CycledActorsBenchmark.kt
+++ b/benchmarks/src/jmh/kotlin/benchmarks/scheduler/actors/CycledActorsBenchmark.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package benchmarks.scheduler.actors
diff --git a/benchmarks/src/jmh/kotlin/benchmarks/scheduler/actors/PingPongActorBenchmark.kt b/benchmarks/src/jmh/kotlin/benchmarks/scheduler/actors/PingPongActorBenchmark.kt
index 4c6ae775..d696c64f 100644
--- a/benchmarks/src/jmh/kotlin/benchmarks/scheduler/actors/PingPongActorBenchmark.kt
+++ b/benchmarks/src/jmh/kotlin/benchmarks/scheduler/actors/PingPongActorBenchmark.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package benchmarks.scheduler.actors
diff --git a/benchmarks/src/jmh/kotlin/benchmarks/scheduler/actors/PingPongWithBlockingContext.kt b/benchmarks/src/jmh/kotlin/benchmarks/scheduler/actors/PingPongWithBlockingContext.kt
index dcbda090..a6f0a473 100644
--- a/benchmarks/src/jmh/kotlin/benchmarks/scheduler/actors/PingPongWithBlockingContext.kt
+++ b/benchmarks/src/jmh/kotlin/benchmarks/scheduler/actors/PingPongWithBlockingContext.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package benchmarks.scheduler.actors
diff --git a/benchmarks/src/jmh/kotlin/benchmarks/scheduler/actors/StatefulActorBenchmark.kt b/benchmarks/src/jmh/kotlin/benchmarks/scheduler/actors/StatefulActorBenchmark.kt
index 01691a2d..eebcec01 100644
--- a/benchmarks/src/jmh/kotlin/benchmarks/scheduler/actors/StatefulActorBenchmark.kt
+++ b/benchmarks/src/jmh/kotlin/benchmarks/scheduler/actors/StatefulActorBenchmark.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package benchmarks.scheduler.actors
diff --git a/benchmarks/src/jmh/kotlin/benchmarks/tailcall/SimpleChannel.kt b/benchmarks/src/jmh/kotlin/benchmarks/tailcall/SimpleChannel.kt
index d961dab8..1f71d8dc 100644
--- a/benchmarks/src/jmh/kotlin/benchmarks/tailcall/SimpleChannel.kt
+++ b/benchmarks/src/jmh/kotlin/benchmarks/tailcall/SimpleChannel.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package benchmarks.tailcall
diff --git a/benchmarks/src/jmh/kotlin/benchmarks/tailcall/SimpleChannelBenchmark.kt b/benchmarks/src/jmh/kotlin/benchmarks/tailcall/SimpleChannelBenchmark.kt
index 7bb962b3..9654b6da 100644
--- a/benchmarks/src/jmh/kotlin/benchmarks/tailcall/SimpleChannelBenchmark.kt
+++ b/benchmarks/src/jmh/kotlin/benchmarks/tailcall/SimpleChannelBenchmark.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package benchmarks.tailcall
diff --git a/benchmarks/src/main/kotlin/benchmarks/common/BenchmarkUtils.kt b/benchmarks/src/main/kotlin/benchmarks/common/BenchmarkUtils.kt
index 27bc6b7d..858ecfad 100644
--- a/benchmarks/src/main/kotlin/benchmarks/common/BenchmarkUtils.kt
+++ b/benchmarks/src/main/kotlin/benchmarks/common/BenchmarkUtils.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package benchmarks.common
diff --git a/build.gradle b/build.gradle
index 79c7f355..e4b12ff3 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,17 +1,20 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
+
+import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType
import org.jetbrains.kotlin.konan.target.HostManager
import org.gradle.util.VersionNumber
+import org.jetbrains.dokka.gradle.DokkaTaskPartial
+import org.jetbrains.dokka.gradle.DokkaMultiModuleTask
apply plugin: 'jdk-convention'
-apply from: rootProject.file("gradle/experimental.gradle")
+apply from: rootProject.file("gradle/opt-in.gradle")
-def rootModule = "kotlinx.coroutines"
def coreModule = "kotlinx-coroutines-core"
// Not applicable for Kotlin plugin
-def sourceless = ['kotlinx.coroutines', 'site', 'kotlinx-coroutines-bom', 'integration-testing']
-def internal = ['kotlinx.coroutines', 'site', 'benchmarks', 'js-stub', 'stdlib-stubs', 'integration-testing']
+def sourceless = ['kotlinx.coroutines', 'kotlinx-coroutines-bom', 'integration-testing']
+def internal = ['kotlinx.coroutines', 'benchmarks', 'integration-testing']
// Not published
def unpublished = internal + ['example-frontend-js', 'android-unit-tests']
@@ -33,13 +36,14 @@ buildscript {
throw new IllegalArgumentException("'kotlin_snapshot_version' should be defined when building with snapshot compiler")
}
}
+ ext.native_targets_enabled = rootProject.properties['disable_native_targets'] == null
// Determine if any project dependency is using a snapshot version
ext.using_snapshot_version = build_snapshot_train
rootProject.properties.each { key, value ->
if (key.endsWith("_version") && value instanceof String && value.endsWith("-SNAPSHOT")) {
println("NOTE: USING SNAPSHOT VERSION: $key=$value")
- ext.using_snapshot_version=true
+ ext.using_snapshot_version = true
}
}
@@ -50,26 +54,10 @@ buildscript {
}
repositories {
- jcenter()
- maven {
- url "https://kotlin.bintray.com/kotlinx"
- credentials {
- username = project.hasProperty('bintrayUser') ? project.property('bintrayUser') : System.getenv('BINTRAY_USER') ?: ""
- password = project.hasProperty('bintrayApiKey') ? project.property('bintrayApiKey') : System.getenv('BINTRAY_API_KEY') ?: ""
- }
- }
- // Future replacement for kotlin-dev, with cache redirector
- maven { url "https://cache-redirector.jetbrains.com/maven.pkg.jetbrains.space/kotlin/p/kotlin/dev" }
- maven {
- url "https://kotlin.bintray.com/kotlin-dev"
- credentials {
- username = project.hasProperty('bintrayUser') ? project.property('bintrayUser') : System.getenv('BINTRAY_USER') ?: ""
- password = project.hasProperty('bintrayApiKey') ? project.property('bintrayApiKey') : System.getenv('BINTRAY_API_KEY') ?: ""
- }
- }
- maven { url "https://kotlin.bintray.com/kotlin-eap" }
- maven { url "https://jetbrains.bintray.com/kotlin-native-dependencies" }
+ mavenCentral()
maven { url "https://plugins.gradle.org/m2/" }
+ maven { url "https://maven.pkg.jetbrains.space/kotlin/p/kotlin/dev" }
+ mavenLocal()
}
dependencies {
@@ -86,9 +74,6 @@ buildscript {
CacheRedirector.configureBuildScript(buildscript, rootProject)
}
-
-import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType
-
// todo:KLUDGE: Hierarchical project structures are not fully supported in IDEA, enable only for a regular built
if (!Idea.active) {
ext.set("kotlin.mpp.enableGranularSourceSetsMetadata", "true")
@@ -139,7 +124,6 @@ apply plugin: "binary-compatibility-validator"
apiValidation {
ignoredProjects += unpublished + ["kotlinx-coroutines-bom"]
if (build_snapshot_train) {
- ignoredProjects.remove("site")
ignoredProjects.remove("example-frontend-js")
ignoredProjects.add("kotlinx-coroutines-core")
}
@@ -154,25 +138,7 @@ allprojects {
* transitive dependencies was removed from jcenter, thus breaking gradle dependency resolution
*/
google()
- jcenter()
- // Future replacement for kotlin-dev, with cache redirector
- maven { url "https://cache-redirector.jetbrains.com/maven.pkg.jetbrains.space/kotlin/p/kotlin/dev" }
- maven {
- url "https://kotlin.bintray.com/kotlin-dev"
- credentials {
- username = project.hasProperty('bintrayUser') ? project.property('bintrayUser') : System.getenv('BINTRAY_USER') ?: ""
- password = project.hasProperty('bintrayApiKey') ? project.property('bintrayApiKey') : System.getenv('BINTRAY_API_KEY') ?: ""
- }
- }
- maven { url "https://kotlin.bintray.com/kotlin-eap" }
- maven {
- url "https://kotlin.bintray.com/kotlinx"
- credentials {
- username = project.hasProperty('bintrayUser') ? project.property('bintrayUser') : System.getenv('BINTRAY_USER') ?: ""
- password = project.hasProperty('bintrayApiKey') ? project.property('bintrayApiKey') : System.getenv('BINTRAY_API_KEY') ?: ""
- }
- }
- mavenLocal()
+ mavenCentral()
}
}
@@ -180,12 +146,12 @@ allprojects {
configure(subprojects.findAll { !sourceless.contains(it.name) && it.name != coreModule }) {
evaluationDependsOn(":$coreModule")
def platform = PlatformKt.platformOf(it)
- apply from: rootProject.file("gradle/compile-${platform}.gradle")
+ apply plugin: "kotlin-${platform}-conventions"
dependencies {
// See comment below for rationale, it will be replaced with "project" dependency
- compile "org.jetbrains.kotlinx:kotlinx-coroutines-core:$version"
+ api project(":$coreModule")
// the only way IDEA can resolve test classes
- testCompile project(":$coreModule").kotlin.targets.jvm.compilations.test.output.allOutputs
+ testImplementation project(":$coreModule").kotlin.targets.jvm.compilations.test.output.allOutputs
}
}
@@ -196,9 +162,10 @@ configure(subprojects.findAll { !sourceless.contains(it.name) }) {
// Configure options for all Kotlin compilation tasks
tasks.withType(org.jetbrains.kotlin.gradle.tasks.AbstractKotlinCompile).all {
- kotlinOptions.freeCompilerArgs += experimentalAnnotations.collect { "-Xuse-experimental=" + it }
+ kotlinOptions.freeCompilerArgs += optInAnnotations.collect { "-Xopt-in=" + it }
kotlinOptions.freeCompilerArgs += "-progressive"
- kotlinOptions.freeCompilerArgs += "-XXLanguage:+InlineClasses"
+ // Disable KT-36770 for RxJava2 integration
+ kotlinOptions.freeCompilerArgs += "-XXLanguage:-ProhibitUsingNullableTypeParameterAgainstNotNullAnnotated"
// Remove null assertions to get smaller bytecode on Android
kotlinOptions.freeCompilerArgs += ["-Xno-param-assertions", "-Xno-receiver-assertions", "-Xno-call-assertions"]
}
@@ -237,22 +204,6 @@ if (build_snapshot_train) {
}
}
-/*
- * Hack to trick nmpp plugin: we are renaming artifacts in order to provide backward compatibility for dependencies,
- * but publishing plugin does not re-read artifact names for kotlin-jvm projects, so renaming is not applied in pom files
- * for JVM-only projects.
- *
- * We artificially replace "project" dependency with "module" one to have proper names in pom files, but then substitute it
- * to have out "project" dependency back.
- */
-configure(subprojects.findAll { it.name != coreModule && it.name != rootModule }) {
- configurations.all {
- resolutionStrategy.dependencySubstitution {
- substitute module("org.jetbrains.kotlinx:kotlinx-coroutines-core:$version") with project(':kotlinx-coroutines-core')
- }
- }
-}
-
// Redefine source sets because we are not using 'kotlin/main/fqn' folder convention
configure(subprojects.findAll {
!sourceless.contains(it.name) &&
@@ -271,23 +222,25 @@ configure(subprojects.findAll {
}
def core_docs_url = "https://kotlin.github.io/kotlinx.coroutines/$coreModule/"
-def core_docs_file = "$projectDir/kotlinx-coroutines-core/build/dokka/kotlinx-coroutines-core/package-list"
+def core_docs_file = "$projectDir/kotlinx-coroutines-core/build/dokka/htmlPartial/package-list"
+apply plugin: "org.jetbrains.dokka"
configure(subprojects.findAll { !unpublished.contains(it.name) }) {
if (it.name != 'kotlinx-coroutines-bom') {
- apply from: rootProject.file('gradle/dokka.gradle')
+ apply from: rootProject.file('gradle/dokka.gradle.kts')
}
- apply from: rootProject.file('gradle/publish-bintray.gradle')
+ apply from: rootProject.file('gradle/publish.gradle')
}
configure(subprojects.findAll { !unpublished.contains(it.name) }) {
if (it.name != "kotlinx-coroutines-bom") {
if (it.name != coreModule) {
- dokka.dependsOn project(":$coreModule").dokka
- tasks.withType(dokka.getClass()) {
- externalDocumentationLink {
- url = new URL(core_docs_url)
- packageListUrl = new File(core_docs_file).toURI().toURL()
+ tasks.withType(DokkaTaskPartial.class) {
+ dokkaSourceSets.configureEach {
+ externalDocumentationLink {
+ url.set(new URL(core_docs_url))
+ packageListUrl.set(new File(core_docs_file).toURI().toURL())
+ }
}
}
}
@@ -320,6 +273,43 @@ apply plugin: 'kotlinx-knit'
knit {
siteRoot = "https://kotlin.github.io/kotlinx.coroutines"
moduleRoots = [".", "integration", "reactive", "ui"]
+ moduleDocs = "build/dokka/htmlPartial"
+ dokkaMultiModuleRoot = "build/dokka/htmlMultiModule/"
+}
+
+knitPrepare.dependsOn getTasksByName("dokkaHtmlMultiModule", true)
+
+dependencies {
+ dokkaHtmlMultiModulePlugin("org.jetbrains.kotlinx:dokka-pathsaver-plugin:$knit_version")
+}
+
+// Opt-in for build scan in order to troubleshoot Gradle on TC
+if (hasProperty('buildScan')) {
+ buildScan {
+ termsOfServiceUrl = 'https://gradle.com/terms-of-service'
+ termsOfServiceAgree = 'yes'
+ }
}
-knitPrepare.dependsOn getTasksByName("dokka", true)
+/*
+ * kotlinx-coroutines-core dependency leaks into test runtime classpath via kotlin-compiler-embeddable
+ * and conflicts with our own test/runtime incompatibilities (e.g. when class is moved from a main to test),
+ * so we do substitution here
+ */
+allprojects { subProject ->
+ subProject
+ .configurations
+ .matching {
+ // Excluding substituted project itself because of circular dependencies, but still do it
+ // for "*Test*" configurations
+ subProject.name != "kotlinx-coroutines-core" || it.name.contains("Test")
+ }
+ .configureEach { conf ->
+ conf.resolutionStrategy.dependencySubstitution {
+ substitute(module("org.jetbrains.kotlinx:kotlinx-coroutines-core"))
+ .using(project(":kotlinx-coroutines-core"))
+ .because("Because Kotlin compiler embeddable leaks coroutines into the runtime classpath, " +
+ "triggering all sort of incompatible class changes errors")
+ }
+ }
+}
diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts
index adcbd90f..c54e226a 100644
--- a/buildSrc/build.gradle.kts
+++ b/buildSrc/build.gradle.kts
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
import java.util.*
@@ -12,15 +12,13 @@ val cacheRedirectorEnabled = System.getenv("CACHE_REDIRECTOR")?.toBoolean() == t
val buildSnapshotTrain = properties["build_snapshot_train"]?.toString()?.toBoolean() == true
repositories {
+ mavenCentral()
if (cacheRedirectorEnabled) {
maven("https://cache-redirector.jetbrains.com/plugins.gradle.org/m2")
- maven("https://cache-redirector.jetbrains.com/dl.bintray.com/kotlin/kotlin-eap")
- maven("https://cache-redirector.jetbrains.com/dl.bintray.com/kotlin/kotlin-dev")
} else {
maven("https://plugins.gradle.org/m2")
- maven("https://dl.bintray.com/kotlin/kotlin-eap")
- maven("https://dl.bintray.com/kotlin/kotlin-dev")
}
+ maven("https://maven.pkg.jetbrains.space/kotlin/p/kotlin/dev")
if (buildSnapshotTrain) {
mavenLocal()
@@ -47,4 +45,5 @@ fun version(target: String): String {
dependencies {
implementation(kotlin("gradle-plugin", version("kotlin")))
implementation("org.jetbrains.dokka:dokka-gradle-plugin:${version("dokka")}")
+ implementation("org.jetbrains.dokka:dokka-core:${version("dokka")}")
}
diff --git a/buildSrc/settings.gradle.kts b/buildSrc/settings.gradle.kts
index a6da8fdb..e30c3ee5 100644
--- a/buildSrc/settings.gradle.kts
+++ b/buildSrc/settings.gradle.kts
@@ -1,9 +1,10 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
pluginManagement {
val build_snapshot_train: String? by settings
repositories {
+ maven(url = "https://maven.pkg.jetbrains.space/kotlin/p/dokka/dev/")
val cacheRedirectorEnabled = System.getenv("CACHE_REDIRECTOR")?.toBoolean() == true
if (cacheRedirectorEnabled) {
println("Redirecting repositories for buildSrc buildscript")
diff --git a/buildSrc/src/main/kotlin/CacheRedirector.kt b/buildSrc/src/main/kotlin/CacheRedirector.kt
index 7cf01d8e..bcadd736 100644
--- a/buildSrc/src/main/kotlin/CacheRedirector.kt
+++ b/buildSrc/src/main/kotlin/CacheRedirector.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
import org.gradle.api.*
@@ -21,46 +21,15 @@ private val cacheRedirectorEnabled = System.getenv("CACHE_REDIRECTOR")?.toBoolea
private val mirroredUrls = listOf(
"https://cdn.azul.com/zulu/bin",
"https://clojars.org/repo",
- "https://dl.bintray.com/d10xa/maven",
- "https://dl.bintray.com/groovy/maven",
- "https://dl.bintray.com/jetbrains/maven-patched",
- "https://dl.bintray.com/jetbrains/scala-plugin-deps",
- "https://dl.bintray.com/kodein-framework/Kodein-DI",
- "https://dl.bintray.com/konsoletyper/teavm",
- "https://dl.bintray.com/kotlin/kotlin-dev",
- "https://dl.bintray.com/kotlin/kotlin-eap",
- "https://dl.bintray.com/kotlin/kotlinx.html",
- "https://dl.bintray.com/kotlin/kotlinx",
- "https://dl.bintray.com/kotlin/ktor",
- "https://dl.bintray.com/scalacenter/releases",
- "https://dl.bintray.com/scalamacros/maven",
- "https://dl.bintray.com/kotlin/exposed",
- "https://dl.bintray.com/cy6ergn0m/maven",
- "https://dl.bintray.com/kotlin/kotlin-js-wrappers",
"https://dl.google.com/android/repository",
"https://dl.google.com/dl/android/maven2",
"https://dl.google.com/dl/android/studio/ide-zips",
"https://dl.google.com/go",
"https://download.jetbrains.com",
- "https://jcenter.bintray.com",
- "https://jetbrains.bintray.com/dekaf",
- "https://jetbrains.bintray.com/intellij-jbr",
- "https://jetbrains.bintray.com/intellij-jdk",
- "https://jetbrains.bintray.com/intellij-plugin-service",
- "https://jetbrains.bintray.com/intellij-terraform",
- "https://jetbrains.bintray.com/intellij-third-party-dependencies",
- "https://jetbrains.bintray.com/jediterm",
- "https://jetbrains.bintray.com/kotlin-native-dependencies",
- "https://jetbrains.bintray.com/markdown",
- "https://jetbrains.bintray.com/teamcity-rest-client",
- "https://jetbrains.bintray.com/test-discovery",
- "https://jetbrains.bintray.com/wormhole",
"https://jitpack.io",
"https://maven.pkg.jetbrains.space/kotlin/p/kotlin/dev",
"https://maven.pkg.jetbrains.space/kotlin/p/kotlin/bootstrap",
"https://maven.pkg.jetbrains.space/kotlin/p/kotlin/eap",
- "https://kotlin.bintray.com/dukat",
- "https://kotlin.bintray.com/kotlin-dependencies",
"https://oss.sonatype.org/content/repositories/releases",
"https://oss.sonatype.org/content/repositories/snapshots",
"https://oss.sonatype.org/content/repositories/staging",
@@ -85,10 +54,7 @@ private val mirroredUrls = listOf(
)
private val aliases = mapOf(
- "https://repo.maven.apache.org/maven2" to "https://repo1.maven.org/maven2", // Maven Central
- "https://kotlin.bintray.com/kotlin-dev" to "https://dl.bintray.com/kotlin/kotlin-dev",
- "https://kotlin.bintray.com/kotlin-eap" to "https://dl.bintray.com/kotlin/kotlin-eap",
- "https://kotlin.bintray.com/kotlinx" to "https://dl.bintray.com/kotlin/kotlinx"
+ "https://repo.maven.apache.org/maven2" to "https://repo1.maven.org/maven2" // Maven Central
)
private fun URI.toCacheRedirectorUri() = URI("https://cache-redirector.jetbrains.com/$host/$path")
diff --git a/buildSrc/src/main/kotlin/Dokka.kt b/buildSrc/src/main/kotlin/Dokka.kt
index dd5f1ea4..a6b06ee1 100644
--- a/buildSrc/src/main/kotlin/Dokka.kt
+++ b/buildSrc/src/main/kotlin/Dokka.kt
@@ -1,14 +1,12 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
-import org.gradle.api.Project
-import org.gradle.kotlin.dsl.delegateClosureOf
-import org.gradle.kotlin.dsl.withType
-import org.jetbrains.dokka.DokkaConfiguration.ExternalDocumentationLink.Builder
-import org.jetbrains.dokka.gradle.DokkaTask
-import java.io.File
-import java.net.URL
+import org.gradle.api.*
+import org.gradle.kotlin.dsl.*
+import org.jetbrains.dokka.gradle.*
+import java.io.*
+import java.net.*
/**
* Package-list by external URL for documentation generation.
@@ -17,10 +15,12 @@ fun Project.externalDocumentationLink(
url: String,
packageList: File = projectDir.resolve("package.list")
) {
- tasks.withType<DokkaTask>().configureEach {
- externalDocumentationLink(delegateClosureOf<Builder> {
- this.url = URL(url)
- packageListUrl = packageList.toPath().toUri().toURL()
- })
+ tasks.withType<AbstractDokkaLeafTask>().configureEach {
+ dokkaSourceSets.configureEach {
+ externalDocumentationLink {
+ this.url.set(URL(url))
+ packageListUrl.set(packageList.toPath().toUri().toURL())
+ }
+ }
}
}
diff --git a/buildSrc/src/main/kotlin/Idea.kt b/buildSrc/src/main/kotlin/Idea.kt
index 615b8aad..28b4aa55 100644
--- a/buildSrc/src/main/kotlin/Idea.kt
+++ b/buildSrc/src/main/kotlin/Idea.kt
@@ -1,3 +1,7 @@
+/*
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
object Idea {
@JvmStatic // for Gradle
val active: Boolean
diff --git a/buildSrc/src/main/kotlin/MavenCentral.kt b/buildSrc/src/main/kotlin/MavenCentral.kt
deleted file mode 100644
index 3efaf33f..00000000
--- a/buildSrc/src/main/kotlin/MavenCentral.kt
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
- */
-
-@file:Suppress("UnstableApiUsage")
-
-import org.gradle.api.Project
-import org.gradle.api.publish.maven.MavenPom
-
-// Pom configuration
-
-fun MavenPom.configureMavenCentralMetadata(project: Project) {
- name by project.name
- description by "Coroutines support libraries for Kotlin"
- url by "https://github.com/Kotlin/kotlinx.coroutines"
-
- licenses {
- license {
- name by "The Apache Software License, Version 2.0"
- url by "https://www.apache.org/licenses/LICENSE-2.0.txt"
- distribution by "repo"
- }
- }
-
- developers {
- developer {
- id by "JetBrains"
- name by "JetBrains Team"
- organization by "JetBrains"
- organizationUrl by "https://www.jetbrains.com"
- }
- }
-
- scm {
- url by "https://github.com/Kotlin/kotlinx.coroutines"
- }
-}
diff --git a/buildSrc/src/main/kotlin/Platform.kt b/buildSrc/src/main/kotlin/Platform.kt
index b667a138..f22c161b 100644
--- a/buildSrc/src/main/kotlin/Platform.kt
+++ b/buildSrc/src/main/kotlin/Platform.kt
@@ -1,3 +1,7 @@
+/*
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
import org.gradle.api.Project
// Use from Groovy for now
diff --git a/buildSrc/src/main/kotlin/Projects.kt b/buildSrc/src/main/kotlin/Projects.kt
index 109311e8..dd284b61 100644
--- a/buildSrc/src/main/kotlin/Projects.kt
+++ b/buildSrc/src/main/kotlin/Projects.kt
@@ -1,3 +1,7 @@
+/*
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
import org.gradle.api.Project
fun Project.version(target: String): String =
diff --git a/buildSrc/src/main/kotlin/Properties.kt b/buildSrc/src/main/kotlin/Properties.kt
index a0968ee6..34314502 100644
--- a/buildSrc/src/main/kotlin/Properties.kt
+++ b/buildSrc/src/main/kotlin/Properties.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
@file:Suppress("UnstableApiUsage")
diff --git a/buildSrc/src/main/kotlin/Publishing.kt b/buildSrc/src/main/kotlin/Publishing.kt
new file mode 100644
index 00000000..8c6dd5de
--- /dev/null
+++ b/buildSrc/src/main/kotlin/Publishing.kt
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+@file:Suppress("UnstableApiUsage")
+
+import org.gradle.api.Project
+import org.gradle.api.artifacts.dsl.*
+import org.gradle.api.publish.maven.*
+import org.gradle.plugins.signing.*
+import java.net.*
+
+// Pom configuration
+
+fun MavenPom.configureMavenCentralMetadata(project: Project) {
+ name by project.name
+ description by "Coroutines support libraries for Kotlin"
+ url by "https://github.com/Kotlin/kotlinx.coroutines"
+
+ licenses {
+ license {
+ name by "The Apache Software License, Version 2.0"
+ url by "https://www.apache.org/licenses/LICENSE-2.0.txt"
+ distribution by "repo"
+ }
+ }
+
+ developers {
+ developer {
+ id by "JetBrains"
+ name by "JetBrains Team"
+ organization by "JetBrains"
+ organizationUrl by "https://www.jetbrains.com"
+ }
+ }
+
+ scm {
+ url by "https://github.com/Kotlin/kotlinx.coroutines"
+ }
+}
+
+fun mavenRepositoryUri(): URI {
+ val repositoryId: String? = System.getenv("libs.repository.id")
+ return if (repositoryId == null) {
+ URI("https://oss.sonatype.org/service/local/staging/deploy/maven2/")
+ } else {
+ URI("https://oss.sonatype.org/service/local/staging/deployByRepositoryId/$repositoryId")
+ }
+}
+
+fun configureMavenPublication(rh: RepositoryHandler, project: Project) {
+ rh.maven {
+ url = mavenRepositoryUri()
+ credentials {
+ username = project.getSensitiveProperty("libs.sonatype.user")
+ password = project.getSensitiveProperty("libs.sonatype.password")
+ }
+ }
+}
+
+fun signPublicationIfKeyPresent(project: Project, publication: MavenPublication) {
+ val keyId = project.getSensitiveProperty("libs.sign.key.id")
+ val signingKey = project.getSensitiveProperty("libs.sign.key.private")
+ val signingKeyPassphrase = project.getSensitiveProperty("libs.sign.passphrase")
+ if (!signingKey.isNullOrBlank()) {
+ project.extensions.configure<SigningExtension>("signing") {
+ useInMemoryPgpKeys(keyId, signingKey, signingKeyPassphrase)
+ sign(publication)
+ }
+ }
+}
+
+private fun Project.getSensitiveProperty(name: String): String? {
+ return project.findProperty(name) as? String ?: System.getenv(name)
+}
diff --git a/buildSrc/src/main/kotlin/RunR8.kt b/buildSrc/src/main/kotlin/RunR8.kt
deleted file mode 100644
index d9eba79b..00000000
--- a/buildSrc/src/main/kotlin/RunR8.kt
+++ /dev/null
@@ -1,52 +0,0 @@
-import org.gradle.api.tasks.InputFile
-import org.gradle.api.tasks.InputFiles
-import org.gradle.api.tasks.JavaExec
-import org.gradle.api.tasks.OutputDirectory
-import org.gradle.api.tasks.bundling.Zip
-import org.gradle.kotlin.dsl.get
-import org.gradle.kotlin.dsl.named
-import java.io.File
-
-/*
- * Task used by our ui/android tests to test minification results
- * and keep track of size of the binary.
- * TODO move back to kotlinx-coroutines-android when it's migrated to the kts
- */
-open class RunR8 : JavaExec() {
-
- @OutputDirectory
- lateinit var outputDex: File
-
- @InputFile
- lateinit var inputConfig: File
-
- @InputFile
- val inputConfigCommon: File = File("testdata/r8-test-common.pro")
-
- @InputFiles
- val jarFile: File = project.tasks.named<Zip>("jar").get().archivePath
-
- init {
- classpath = project.configurations["r8"]
- main = "com.android.tools.r8.R8"
- }
-
- override fun exec() {
- // Resolve classpath only during execution
- val arguments = mutableListOf(
- "--release",
- "--no-desugaring",
- "--output", outputDex.absolutePath,
- "--pg-conf", inputConfig.absolutePath
- )
- arguments.addAll(project.configurations["runtimeClasspath"].files.map { it.absolutePath })
- arguments.add(jarFile.absolutePath)
-
- args = arguments
-
- project.delete(outputDex)
- outputDex.mkdirs()
-
- super.exec()
- }
-}
diff --git a/buildSrc/src/main/kotlin/UnpackAar.kt b/buildSrc/src/main/kotlin/UnpackAar.kt
index c7d0b53d..b3152d7a 100644
--- a/buildSrc/src/main/kotlin/UnpackAar.kt
+++ b/buildSrc/src/main/kotlin/UnpackAar.kt
@@ -1,3 +1,7 @@
+/*
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
import org.gradle.api.artifacts.transform.InputArtifact
import org.gradle.api.artifacts.transform.TransformAction
import org.gradle.api.artifacts.transform.TransformOutputs
diff --git a/buildSrc/src/main/kotlin/jdk-convention.gradle.kts b/buildSrc/src/main/kotlin/jdk-convention.gradle.kts
index 8bc1abf1..64bd90dc 100644
--- a/buildSrc/src/main/kotlin/jdk-convention.gradle.kts
+++ b/buildSrc/src/main/kotlin/jdk-convention.gradle.kts
@@ -1,3 +1,7 @@
+/*
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
import org.gradle.api.JavaVersion
if (!JavaVersion.current().isJava11Compatible) {
diff --git a/buildSrc/src/main/kotlin/kotlin-js-conventions.gradle.kts b/buildSrc/src/main/kotlin/kotlin-js-conventions.gradle.kts
new file mode 100644
index 00000000..c1897ca7
--- /dev/null
+++ b/buildSrc/src/main/kotlin/kotlin-js-conventions.gradle.kts
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// Platform-specific configuration to compile JS modules
+
+import org.jetbrains.kotlin.gradle.dsl.KotlinJsCompile
+
+plugins {
+ kotlin("js")
+}
+
+dependencies {
+ testImplementation(kotlin("test-js"))
+}
+
+kotlin {
+ js(LEGACY) {
+ moduleName = project.name.removeSuffix("-js")
+ }
+
+ sourceSets {
+ main {
+ kotlin.srcDirs("src")
+ resources.srcDirs("resources")
+ }
+ test {
+ kotlin.srcDirs("test")
+ resources.srcDirs("test-resources")
+ }
+ }
+}
+
+tasks.withType<KotlinJsCompile> {
+ kotlinOptions {
+ moduleKind = "umd"
+ sourceMap = true
+ metaInfo = true
+ }
+}
diff --git a/buildSrc/src/main/kotlin/kotlin-jvm-conventions.gradle.kts b/buildSrc/src/main/kotlin/kotlin-jvm-conventions.gradle.kts
new file mode 100644
index 00000000..c7744f87
--- /dev/null
+++ b/buildSrc/src/main/kotlin/kotlin-jvm-conventions.gradle.kts
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// Platform-specific configuration to compile JVM modules
+
+import org.gradle.api.*
+
+plugins {
+ kotlin("jvm")
+}
+
+java {
+ sourceCompatibility = JavaVersion.VERSION_1_6
+ targetCompatibility = JavaVersion.VERSION_1_6
+}
+
+dependencies {
+ testImplementation(kotlin("test"))
+ // Workaround to make addSuppressed work in tests
+ testImplementation(kotlin("reflect"))
+ testImplementation(kotlin("stdlib-jdk7"))
+ testImplementation(kotlin("test-junit"))
+ testImplementation("junit:junit:${version("junit")}")
+}
+
+tasks.compileKotlin {
+ kotlinOptions {
+ freeCompilerArgs += listOf("-Xexplicit-api=strict")
+ }
+}
+
+tasks.withType<Test> {
+ testLogging {
+ showStandardStreams = true
+ events("passed", "failed")
+ }
+ val stressTest = project.properties["stressTest"]
+ if (stressTest != null) systemProperties["stressTest"] = stressTest
+}
diff --git a/bump-version.sh b/bump-version.sh
index 00930cbd..ae0fc0b0 100755
--- a/bump-version.sh
+++ b/bump-version.sh
@@ -20,8 +20,6 @@ update_version "kotlinx-coroutines-core/README.md"
update_version "kotlinx-coroutines-debug/README.md"
update_version "kotlinx-coroutines-test/README.md"
update_version "ui/coroutines-guide-ui.md"
-update_version "ui/kotlinx-coroutines-android/example-app/gradle.properties"
-update_version "ui/kotlinx-coroutines-android/animation-app/gradle.properties"
update_version "gradle.properties"
# Escape dots, e.g. 1.0.0 -> 1\.0\.0
diff --git a/coroutines-guide.md b/coroutines-guide.md
index 09cfb93c..3b4707cf 100644
--- a/coroutines-guide.md
+++ b/coroutines-guide.md
@@ -1,126 +1,14 @@
-The main coroutines guide has moved to the [docs folder](docs/coroutines-guide.md) and split up into smaller documents.
+The main coroutines guide has moved to the [docs folder](docs/topics/coroutines-guide.md) and split up into smaller documents.
## Table of contents
-<!--- TOC_REF docs/basics.md -->
-* <a name='coroutine-basics'></a>[Coroutine Basics](docs/basics.md#coroutine-basics)
- * <a name='your-first-coroutine'></a>[Your first coroutine](docs/basics.md#your-first-coroutine)
- * <a name='bridging-blocking-and-non-blocking-worlds'></a>[Bridging blocking and non-blocking worlds](docs/basics.md#bridging-blocking-and-non-blocking-worlds)
- * <a name='waiting-for-a-job'></a>[Waiting for a job](docs/basics.md#waiting-for-a-job)
- * <a name='structured-concurrency'></a>[Structured concurrency](docs/basics.md#structured-concurrency)
- * <a name='scope-builder'></a>[Scope builder](docs/basics.md#scope-builder)
- * <a name='extract-function-refactoring'></a>[Extract function refactoring](docs/basics.md#extract-function-refactoring)
- * <a name='coroutines-are-light-weight'></a>[Coroutines ARE light-weight](docs/basics.md#coroutines-are-light-weight)
- * <a name='global-coroutines-are-like-daemon-threads'></a>[Global coroutines are like daemon threads](docs/basics.md#global-coroutines-are-like-daemon-threads)
-<!--- TOC_REF docs/cancellation-and-timeouts.md -->
-* <a name='cancellation-and-timeouts'></a>[Cancellation and Timeouts](docs/cancellation-and-timeouts.md#cancellation-and-timeouts)
- * <a name='cancelling-coroutine-execution'></a>[Cancelling coroutine execution](docs/cancellation-and-timeouts.md#cancelling-coroutine-execution)
- * <a name='cancellation-is-cooperative'></a>[Cancellation is cooperative](docs/cancellation-and-timeouts.md#cancellation-is-cooperative)
- * <a name='making-computation-code-cancellable'></a>[Making computation code cancellable](docs/cancellation-and-timeouts.md#making-computation-code-cancellable)
- * <a name='closing-resources-with-finally'></a>[Closing resources with `finally`](docs/cancellation-and-timeouts.md#closing-resources-with-finally)
- * <a name='run-non-cancellable-block'></a>[Run non-cancellable block](docs/cancellation-and-timeouts.md#run-non-cancellable-block)
- * <a name='timeout'></a>[Timeout](docs/cancellation-and-timeouts.md#timeout)
- * <a name='asynchronous-timeout-and-resources'></a>[Asynchronous timeout and resources](docs/cancellation-and-timeouts.md#asynchronous-timeout-and-resources)
-<!--- TOC_REF docs/composing-suspending-functions.md -->
-* <a name='composing-suspending-functions'></a>[Composing Suspending Functions](docs/composing-suspending-functions.md#composing-suspending-functions)
- * <a name='sequential-by-default'></a>[Sequential by default](docs/composing-suspending-functions.md#sequential-by-default)
- * <a name='concurrent-using-async'></a>[Concurrent using async](docs/composing-suspending-functions.md#concurrent-using-async)
- * <a name='lazily-started-async'></a>[Lazily started async](docs/composing-suspending-functions.md#lazily-started-async)
- * <a name='async-style-functions'></a>[Async-style functions](docs/composing-suspending-functions.md#async-style-functions)
- * <a name='structured-concurrency-with-async'></a>[Structured concurrency with async](docs/composing-suspending-functions.md#structured-concurrency-with-async)
-<!--- TOC_REF docs/coroutine-context-and-dispatchers.md -->
-* <a name='coroutine-context-and-dispatchers'></a>[Coroutine Context and Dispatchers](docs/coroutine-context-and-dispatchers.md#coroutine-context-and-dispatchers)
- * <a name='dispatchers-and-threads'></a>[Dispatchers and threads](docs/coroutine-context-and-dispatchers.md#dispatchers-and-threads)
- * <a name='unconfined-vs-confined-dispatcher'></a>[Unconfined vs confined dispatcher](docs/coroutine-context-and-dispatchers.md#unconfined-vs-confined-dispatcher)
- * <a name='debugging-coroutines-and-threads'></a>[Debugging coroutines and threads](docs/coroutine-context-and-dispatchers.md#debugging-coroutines-and-threads)
- * <a name='debugging-with-idea'></a>[Debugging with IDEA](docs/coroutine-context-and-dispatchers.md#debugging-with-idea)
- * <a name='debugging-using-logging'></a>[Debugging using logging](docs/coroutine-context-and-dispatchers.md#debugging-using-logging)
- * <a name='jumping-between-threads'></a>[Jumping between threads](docs/coroutine-context-and-dispatchers.md#jumping-between-threads)
- * <a name='job-in-the-context'></a>[Job in the context](docs/coroutine-context-and-dispatchers.md#job-in-the-context)
- * <a name='children-of-a-coroutine'></a>[Children of a coroutine](docs/coroutine-context-and-dispatchers.md#children-of-a-coroutine)
- * <a name='parental-responsibilities'></a>[Parental responsibilities](docs/coroutine-context-and-dispatchers.md#parental-responsibilities)
- * <a name='naming-coroutines-for-debugging'></a>[Naming coroutines for debugging](docs/coroutine-context-and-dispatchers.md#naming-coroutines-for-debugging)
- * <a name='combining-context-elements'></a>[Combining context elements](docs/coroutine-context-and-dispatchers.md#combining-context-elements)
- * <a name='coroutine-scope'></a>[Coroutine scope](docs/coroutine-context-and-dispatchers.md#coroutine-scope)
- * <a name='thread-local-data'></a>[Thread-local data](docs/coroutine-context-and-dispatchers.md#thread-local-data)
-<!--- TOC_REF docs/flow.md -->
-* <a name='asynchronous-flow'></a>[Asynchronous Flow](docs/flow.md#asynchronous-flow)
- * <a name='representing-multiple-values'></a>[Representing multiple values](docs/flow.md#representing-multiple-values)
- * <a name='sequences'></a>[Sequences](docs/flow.md#sequences)
- * <a name='suspending-functions'></a>[Suspending functions](docs/flow.md#suspending-functions)
- * <a name='flows'></a>[Flows](docs/flow.md#flows)
- * <a name='flows-are-cold'></a>[Flows are cold](docs/flow.md#flows-are-cold)
- * <a name='flow-cancellation-basics'></a>[Flow cancellation basics](docs/flow.md#flow-cancellation-basics)
- * <a name='flow-builders'></a>[Flow builders](docs/flow.md#flow-builders)
- * <a name='intermediate-flow-operators'></a>[Intermediate flow operators](docs/flow.md#intermediate-flow-operators)
- * <a name='transform-operator'></a>[Transform operator](docs/flow.md#transform-operator)
- * <a name='size-limiting-operators'></a>[Size-limiting operators](docs/flow.md#size-limiting-operators)
- * <a name='terminal-flow-operators'></a>[Terminal flow operators](docs/flow.md#terminal-flow-operators)
- * <a name='flows-are-sequential'></a>[Flows are sequential](docs/flow.md#flows-are-sequential)
- * <a name='flow-context'></a>[Flow context](docs/flow.md#flow-context)
- * <a name='wrong-emission-withcontext'></a>[Wrong emission withContext](docs/flow.md#wrong-emission-withcontext)
- * <a name='flowon-operator'></a>[flowOn operator](docs/flow.md#flowon-operator)
- * <a name='buffering'></a>[Buffering](docs/flow.md#buffering)
- * <a name='conflation'></a>[Conflation](docs/flow.md#conflation)
- * <a name='processing-the-latest-value'></a>[Processing the latest value](docs/flow.md#processing-the-latest-value)
- * <a name='composing-multiple-flows'></a>[Composing multiple flows](docs/flow.md#composing-multiple-flows)
- * <a name='zip'></a>[Zip](docs/flow.md#zip)
- * <a name='combine'></a>[Combine](docs/flow.md#combine)
- * <a name='flattening-flows'></a>[Flattening flows](docs/flow.md#flattening-flows)
- * <a name='flatmapconcat'></a>[flatMapConcat](docs/flow.md#flatmapconcat)
- * <a name='flatmapmerge'></a>[flatMapMerge](docs/flow.md#flatmapmerge)
- * <a name='flatmaplatest'></a>[flatMapLatest](docs/flow.md#flatmaplatest)
- * <a name='flow-exceptions'></a>[Flow exceptions](docs/flow.md#flow-exceptions)
- * <a name='collector-try-and-catch'></a>[Collector try and catch](docs/flow.md#collector-try-and-catch)
- * <a name='everything-is-caught'></a>[Everything is caught](docs/flow.md#everything-is-caught)
- * <a name='exception-transparency'></a>[Exception transparency](docs/flow.md#exception-transparency)
- * <a name='transparent-catch'></a>[Transparent catch](docs/flow.md#transparent-catch)
- * <a name='catching-declaratively'></a>[Catching declaratively](docs/flow.md#catching-declaratively)
- * <a name='flow-completion'></a>[Flow completion](docs/flow.md#flow-completion)
- * <a name='imperative-finally-block'></a>[Imperative finally block](docs/flow.md#imperative-finally-block)
- * <a name='declarative-handling'></a>[Declarative handling](docs/flow.md#declarative-handling)
- * <a name='successful-completion'></a>[Successful completion](docs/flow.md#successful-completion)
- * <a name='imperative-versus-declarative'></a>[Imperative versus declarative](docs/flow.md#imperative-versus-declarative)
- * <a name='launching-flow'></a>[Launching flow](docs/flow.md#launching-flow)
- * <a name='flow-cancellation-checks'></a>[Flow cancellation checks](docs/flow.md#flow-cancellation-checks)
- * <a name='making-busy-flow-cancellable'></a>[Making busy flow cancellable](docs/flow.md#making-busy-flow-cancellable)
- * <a name='flow-and-reactive-streams'></a>[Flow and Reactive Streams](docs/flow.md#flow-and-reactive-streams)
-<!--- TOC_REF docs/channels.md -->
-* <a name='channels'></a>[Channels](docs/channels.md#channels)
- * <a name='channel-basics'></a>[Channel basics](docs/channels.md#channel-basics)
- * <a name='closing-and-iteration-over-channels'></a>[Closing and iteration over channels](docs/channels.md#closing-and-iteration-over-channels)
- * <a name='building-channel-producers'></a>[Building channel producers](docs/channels.md#building-channel-producers)
- * <a name='pipelines'></a>[Pipelines](docs/channels.md#pipelines)
- * <a name='prime-numbers-with-pipeline'></a>[Prime numbers with pipeline](docs/channels.md#prime-numbers-with-pipeline)
- * <a name='fan-out'></a>[Fan-out](docs/channels.md#fan-out)
- * <a name='fan-in'></a>[Fan-in](docs/channels.md#fan-in)
- * <a name='buffered-channels'></a>[Buffered channels](docs/channels.md#buffered-channels)
- * <a name='channels-are-fair'></a>[Channels are fair](docs/channels.md#channels-are-fair)
- * <a name='ticker-channels'></a>[Ticker channels](docs/channels.md#ticker-channels)
-<!--- TOC_REF docs/exception-handling.md -->
-* <a name='exception-handling'></a>[Exception Handling](docs/exception-handling.md#exception-handling)
- * <a name='exception-propagation'></a>[Exception propagation](docs/exception-handling.md#exception-propagation)
- * <a name='coroutineexceptionhandler'></a>[CoroutineExceptionHandler](docs/exception-handling.md#coroutineexceptionhandler)
- * <a name='cancellation-and-exceptions'></a>[Cancellation and exceptions](docs/exception-handling.md#cancellation-and-exceptions)
- * <a name='exceptions-aggregation'></a>[Exceptions aggregation](docs/exception-handling.md#exceptions-aggregation)
- * <a name='supervision'></a>[Supervision](docs/exception-handling.md#supervision)
- * <a name='supervision-job'></a>[Supervision job](docs/exception-handling.md#supervision-job)
- * <a name='supervision-scope'></a>[Supervision scope](docs/exception-handling.md#supervision-scope)
- * <a name='exceptions-in-supervised-coroutines'></a>[Exceptions in supervised coroutines](docs/exception-handling.md#exceptions-in-supervised-coroutines)
-<!--- TOC_REF docs/shared-mutable-state-and-concurrency.md -->
-* <a name='shared-mutable-state-and-concurrency'></a>[Shared mutable state and concurrency](docs/shared-mutable-state-and-concurrency.md#shared-mutable-state-and-concurrency)
- * <a name='the-problem'></a>[The problem](docs/shared-mutable-state-and-concurrency.md#the-problem)
- * <a name='volatiles-are-of-no-help'></a>[Volatiles are of no help](docs/shared-mutable-state-and-concurrency.md#volatiles-are-of-no-help)
- * <a name='thread-safe-data-structures'></a>[Thread-safe data structures](docs/shared-mutable-state-and-concurrency.md#thread-safe-data-structures)
- * <a name='thread-confinement-fine-grained'></a>[Thread confinement fine-grained](docs/shared-mutable-state-and-concurrency.md#thread-confinement-fine-grained)
- * <a name='thread-confinement-coarse-grained'></a>[Thread confinement coarse-grained](docs/shared-mutable-state-and-concurrency.md#thread-confinement-coarse-grained)
- * <a name='mutual-exclusion'></a>[Mutual exclusion](docs/shared-mutable-state-and-concurrency.md#mutual-exclusion)
- * <a name='actors'></a>[Actors](docs/shared-mutable-state-and-concurrency.md#actors)
-<!--- TOC_REF docs/select-expression.md -->
-* <a name='select-expression-experimental'></a>[Select Expression (experimental)](docs/select-expression.md#select-expression-experimental)
- * <a name='selecting-from-channels'></a>[Selecting from channels](docs/select-expression.md#selecting-from-channels)
- * <a name='selecting-on-close'></a>[Selecting on close](docs/select-expression.md#selecting-on-close)
- * <a name='selecting-to-send'></a>[Selecting to send](docs/select-expression.md#selecting-to-send)
- * <a name='selecting-deferred-values'></a>[Selecting deferred values](docs/select-expression.md#selecting-deferred-values)
- * <a name='switch-over-a-channel-of-deferred-values'></a>[Switch over a channel of deferred values](docs/select-expression.md#switch-over-a-channel-of-deferred-values)
+<!--- TOC_REF docs/topics/coroutines-basics.md -->
+<!--- TOC_REF docs/topics/cancellation-and-timeouts.md -->
+<!--- TOC_REF docs/topics/composing-suspending-functions.md -->
+<!--- TOC_REF docs/topics/coroutine-context-and-dispatchers.md -->
+<!--- TOC_REF docs/topics/flow.md -->
+<!--- TOC_REF docs/topics/channels.md -->
+<!--- TOC_REF docs/topics/exception-handling.md -->
+<!--- TOC_REF docs/topics/shared-mutable-state-and-concurrency.md -->
+<!--- TOC_REF docs/topics/select-expression.md -->
<!--- END -->
diff --git a/docs/_nav.yml b/docs/_nav.yml
deleted file mode 100644
index 79a69690..00000000
--- a/docs/_nav.yml
+++ /dev/null
@@ -1,30 +0,0 @@
-- md: coroutines-guide.md
- url: coroutines-guide.html
- title: Coroutines Guide
-- md: basics.md
- url: basics.html
- title: Basics
-- md: cancellation-and-timeouts.md
- url: cancellation-and-timeouts.html
- title: Cancellation and Timeouts
-- md: composing-suspending-functions.md
- url: composing-suspending-functions.html
- title: Composing Suspending Functions
-- md: coroutine-context-and-dispatchers.md
- url: coroutine-context-and-dispatchers.html
- title: Coroutine Context and Dispatchers
-- md: flow.md
- url: flow.html
- title: Asynchronous Flow
-- md: channels.md
- url: channels.html
- title: Channels
-- md: exception-handling.md
- url: exception-handling.html
- title: Exception Handling and Supervision
-- md: shared-mutable-state-and-concurrency.md
- url: shared-mutable-state-and-concurrency.html
- title: Shared Mutable State and Concurrency
-- md: select-expression.md
- url: select-expression.html
- title: Select Expression (experimental)
diff --git a/docs/basics.md b/docs/basics.md
index 8aca23a1..a18bf3dd 100644
--- a/docs/basics.md
+++ b/docs/basics.md
@@ -1,410 +1,3 @@
-<!--- TEST_NAME BasicsGuideTest -->
-
-**Table of contents**
-
-<!--- TOC -->
-
-* [Coroutine Basics](#coroutine-basics)
- * [Your first coroutine](#your-first-coroutine)
- * [Bridging blocking and non-blocking worlds](#bridging-blocking-and-non-blocking-worlds)
- * [Waiting for a job](#waiting-for-a-job)
- * [Structured concurrency](#structured-concurrency)
- * [Scope builder](#scope-builder)
- * [Extract function refactoring](#extract-function-refactoring)
- * [Coroutines ARE light-weight](#coroutines-are-light-weight)
- * [Global coroutines are like daemon threads](#global-coroutines-are-like-daemon-threads)
-
-<!--- END -->
-
-## Coroutine Basics
-
-This section covers basic coroutine concepts.
-
-### Your first coroutine
-
-Run the following code:
-
-<div class="sample" markdown="1" theme="idea" data-min-compiler-version="1.3">
-
-```kotlin
-import kotlinx.coroutines.*
-
-fun main() {
- GlobalScope.launch { // launch a new coroutine in background and continue
- delay(1000L) // non-blocking delay for 1 second (default time unit is ms)
- println("World!") // print after delay
- }
- println("Hello,") // main thread continues while coroutine is delayed
- Thread.sleep(2000L) // block main thread for 2 seconds to keep JVM alive
-}
-```
-
-</div>
-
-> You can get the full code [here](../kotlinx-coroutines-core/jvm/test/guide/example-basic-01.kt).
-
-You will see the following result:
-
-```text
-Hello,
-World!
-```
-
-<!--- TEST -->
-
-Essentially, coroutines are light-weight threads.
-They are launched with [launch] _coroutine builder_ in a context of some [CoroutineScope].
-Here we are launching a new coroutine in the [GlobalScope], meaning that the lifetime of the new
-coroutine is limited only by the lifetime of the whole application.
-
-You can achieve the same result by replacing
-`GlobalScope.launch { ... }` with `thread { ... }`, and `delay(...)` with `Thread.sleep(...)`.
-Try it (don't forget to import `kotlin.concurrent.thread`).
-
-If you start by replacing `GlobalScope.launch` with `thread`, the compiler produces the following error:
-
-```
-Error: Kotlin: Suspend functions are only allowed to be called from a coroutine or another suspend function
-```
-
-That is because [delay] is a special _suspending function_ that does not block a thread, but _suspends_ the
-coroutine, and it can be only used from a coroutine.
-
-### Bridging blocking and non-blocking worlds
-
-The first example mixes _non-blocking_ `delay(...)` and _blocking_ `Thread.sleep(...)` in the same code.
-It is easy to lose track of which one is blocking and which one is not.
-Let's be explicit about blocking using the [runBlocking] coroutine builder:
-
-<div class="sample" markdown="1" theme="idea" data-min-compiler-version="1.3">
-
-```kotlin
-import kotlinx.coroutines.*
-
-fun main() {
- GlobalScope.launch { // launch a new coroutine in background and continue
- delay(1000L)
- println("World!")
- }
- println("Hello,") // main thread continues here immediately
- runBlocking { // but this expression blocks the main thread
- delay(2000L) // ... while we delay for 2 seconds to keep JVM alive
- }
-}
-```
-
-</div>
-
-> You can get the full code [here](../kotlinx-coroutines-core/jvm/test/guide/example-basic-02.kt).
-
-<!--- TEST
-Hello,
-World!
--->
-
-The result is the same, but this code uses only non-blocking [delay].
-The main thread invoking `runBlocking` _blocks_ until the coroutine inside `runBlocking` completes.
-
-This example can be also rewritten in a more idiomatic way, using `runBlocking` to wrap
-the execution of the main function:
-
-<div class="sample" markdown="1" theme="idea" data-min-compiler-version="1.3">
-
-```kotlin
-import kotlinx.coroutines.*
-
-fun main() = runBlocking<Unit> { // start main coroutine
- GlobalScope.launch { // launch a new coroutine in background and continue
- delay(1000L)
- println("World!")
- }
- println("Hello,") // main coroutine continues here immediately
- delay(2000L) // delaying for 2 seconds to keep JVM alive
-}
-```
-
-</div>
-
-> You can get the full code [here](../kotlinx-coroutines-core/jvm/test/guide/example-basic-03.kt).
-
-<!--- TEST
-Hello,
-World!
--->
-
-Here `runBlocking<Unit> { ... }` works as an adaptor that is used to start the top-level main coroutine.
-We explicitly specify its `Unit` return type, because a well-formed `main` function in Kotlin has to return `Unit`.
-
-This is also a way to write unit tests for suspending functions:
-
-<!--- INCLUDE
-import kotlinx.coroutines.*
--->
-
-<div class="sample" markdown="1" theme="idea" data-highlight-only>
-
-```kotlin
-class MyTest {
- @Test
- fun testMySuspendingFunction() = runBlocking<Unit> {
- // here we can use suspending functions using any assertion style that we like
- }
-}
-```
-
-</div>
-
-<!--- CLEAR -->
-
-### Waiting for a job
-
-Delaying for a time while another coroutine is working is not a good approach. Let's explicitly
-wait (in a non-blocking way) until the background [Job] that we have launched is complete:
-
-<div class="sample" markdown="1" theme="idea" data-min-compiler-version="1.3">
-
-```kotlin
-import kotlinx.coroutines.*
-
-fun main() = runBlocking {
-//sampleStart
- val job = GlobalScope.launch { // launch a new coroutine and keep a reference to its Job
- delay(1000L)
- println("World!")
- }
- println("Hello,")
- job.join() // wait until child coroutine completes
-//sampleEnd
-}
-```
-
-</div>
-
-> You can get the full code [here](../kotlinx-coroutines-core/jvm/test/guide/example-basic-04.kt).
-
-<!--- TEST
-Hello,
-World!
--->
-
-Now the result is still the same, but the code of the main coroutine is not tied to the duration of
-the background job in any way. Much better.
-
-### Structured concurrency
-
-There is still something to be desired for practical usage of coroutines.
-When we use `GlobalScope.launch`, we create a top-level coroutine. Even though it is light-weight, it still
-consumes some memory resources while it runs. If we forget to keep a reference to the newly launched
-coroutine, it still runs. What if the code in the coroutine hangs (for example, we erroneously
-delay for too long), what if we launched too many coroutines and ran out of memory?
-Having to manually keep references to all the launched coroutines and [join][Job.join] them is error-prone.
-
-There is a better solution. We can use structured concurrency in our code.
-Instead of launching coroutines in the [GlobalScope], just like we usually do with threads (threads are always global),
-we can launch coroutines in the specific scope of the operation we are performing.
-
-In our example, we have a `main` function that is turned into a coroutine using the [runBlocking] coroutine builder.
-Every coroutine builder, including `runBlocking`, adds an instance of [CoroutineScope] to the scope of its code block.
-We can launch coroutines in this scope without having to `join` them explicitly, because
-an outer coroutine (`runBlocking` in our example) does not complete until all the coroutines launched
-in its scope complete. Thus, we can make our example simpler:
-
-<div class="sample" markdown="1" theme="idea" data-min-compiler-version="1.3">
-
-```kotlin
-import kotlinx.coroutines.*
-
-fun main() = runBlocking { // this: CoroutineScope
- launch { // launch a new coroutine in the scope of runBlocking
- delay(1000L)
- println("World!")
- }
- println("Hello,")
-}
-```
-
-</div>
-
-> You can get the full code [here](../kotlinx-coroutines-core/jvm/test/guide/example-basic-05.kt).
-
-<!--- TEST
-Hello,
-World!
--->
-
-### Scope builder
-
-In addition to the coroutine scope provided by different builders, it is possible to declare your own scope using the
-[coroutineScope][_coroutineScope] builder. It creates a coroutine scope and does not complete until all launched children complete.
-
-[runBlocking] and [coroutineScope][_coroutineScope] may look similar because they both wait for their body and all its children to complete.
-The main difference is that the [runBlocking] method _blocks_ the current thread for waiting,
-while [coroutineScope][_coroutineScope] just suspends, releasing the underlying thread for other usages.
-Because of that difference, [runBlocking] is a regular function and [coroutineScope][_coroutineScope] is a suspending function.
-
-It can be demonstrated by the following example:
-
-<div class="sample" markdown="1" theme="idea" data-min-compiler-version="1.3">
-
-```kotlin
-import kotlinx.coroutines.*
-
-fun main() = runBlocking { // this: CoroutineScope
- launch {
- delay(200L)
- println("Task from runBlocking")
- }
-
- coroutineScope { // Creates a coroutine scope
- launch {
- delay(500L)
- println("Task from nested launch")
- }
-
- delay(100L)
- println("Task from coroutine scope") // This line will be printed before the nested launch
- }
-
- println("Coroutine scope is over") // This line is not printed until the nested launch completes
-}
-```
-
-</div>
-
-> You can get the full code [here](../kotlinx-coroutines-core/jvm/test/guide/example-basic-06.kt).
-
-<!--- TEST
-Task from coroutine scope
-Task from runBlocking
-Task from nested launch
-Coroutine scope is over
--->
-
-Note that right after the "Task from coroutine scope" message (while waiting for nested launch)
- "Task from runBlocking" is executed and printed — even though the [coroutineScope][_coroutineScope] is not completed yet.
-
-### Extract function refactoring
-
-Let's extract the block of code inside `launch { ... }` into a separate function. When you
-perform "Extract function" refactoring on this code, you get a new function with the `suspend` modifier.
-This is your first _suspending function_. Suspending functions can be used inside coroutines
-just like regular functions, but their additional feature is that they can, in turn,
-use other suspending functions (like `delay` in this example) to _suspend_ execution of a coroutine.
-
-<div class="sample" markdown="1" theme="idea" data-min-compiler-version="1.3">
-
-```kotlin
-import kotlinx.coroutines.*
-
-fun main() = runBlocking {
- launch { doWorld() }
- println("Hello,")
-}
-
-// this is your first suspending function
-suspend fun doWorld() {
- delay(1000L)
- println("World!")
-}
-```
-
-</div>
-
-> You can get the full code [here](../kotlinx-coroutines-core/jvm/test/guide/example-basic-07.kt).
-
-<!--- TEST
-Hello,
-World!
--->
-
-
-But what if the extracted function contains a coroutine builder which is invoked on the current scope?
-In this case, the `suspend` modifier on the extracted function is not enough. Making `doWorld` an extension
-method on `CoroutineScope` is one of the solutions, but it may not always be applicable as it does not make the API clearer.
-The idiomatic solution is to have either an explicit `CoroutineScope` as a field in a class containing the target function
-or an implicit one when the outer class implements `CoroutineScope`.
-As a last resort, [CoroutineScope(coroutineContext)][CoroutineScope()] can be used, but such an approach is structurally unsafe
-because you no longer have control on the scope of execution of this method. Only private APIs can use this builder.
-
-### Coroutines ARE light-weight
-
-Run the following code:
-
-<div class="sample" markdown="1" theme="idea" data-highlight-only>
-
-```kotlin
-import kotlinx.coroutines.*
-
-fun main() = runBlocking {
- repeat(100_000) { // launch a lot of coroutines
- launch {
- delay(5000L)
- print(".")
- }
- }
-}
-```
-
-</div>
-
-> You can get the full code [here](../kotlinx-coroutines-core/jvm/test/guide/example-basic-08.kt).
-
-<!--- TEST lines.size == 1 && lines[0] == ".".repeat(100_000) -->
-
-It launches 100K coroutines and, after 5 seconds, each coroutine prints a dot.
-
-Now, try that with threads. What would happen? (Most likely your code will produce some sort of out-of-memory error)
-
-### Global coroutines are like daemon threads
-
-The following code launches a long-running coroutine in [GlobalScope] that prints "I'm sleeping" twice a second and then
-returns from the main function after some delay:
-
-<div class="sample" markdown="1" theme="idea" data-min-compiler-version="1.3">
-
-```kotlin
-import kotlinx.coroutines.*
-
-fun main() = runBlocking {
-//sampleStart
- GlobalScope.launch {
- repeat(1000) { i ->
- println("I'm sleeping $i ...")
- delay(500L)
- }
- }
- delay(1300L) // just quit after delay
-//sampleEnd
-}
-```
-
-</div>
-
-> You can get the full code [here](../kotlinx-coroutines-core/jvm/test/guide/example-basic-09.kt).
-
-You can run and see that it prints three lines and terminates:
-
-```text
-I'm sleeping 0 ...
-I'm sleeping 1 ...
-I'm sleeping 2 ...
-```
-
-<!--- TEST -->
-
-Active coroutines that were launched in [GlobalScope] do not keep the process alive. They are like daemon threads.
-
-<!--- MODULE kotlinx-coroutines-core -->
-<!--- INDEX kotlinx.coroutines -->
-[launch]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/launch.html
-[CoroutineScope]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-scope/index.html
-[GlobalScope]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-global-scope/index.html
-[delay]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/delay.html
-[runBlocking]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/run-blocking.html
-[Job]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job/index.html
-[Job.join]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job/join.html
-[_coroutineScope]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/coroutine-scope.html
-[CoroutineScope()]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-scope.html
-<!--- END -->
-
+The documentation has been moved to the [https://kotlinlang.org/docs/coroutines-basics.html](https://kotlinlang.org/docs/coroutines-basics.html) page.
+To edit the documentation, open the [topics/coroutines-basics.md](topics/coroutines-basics.md) page.
diff --git a/docs/cancellation-and-timeouts.md b/docs/cancellation-and-timeouts.md
index b296bde4..ad6afca9 100644
--- a/docs/cancellation-and-timeouts.md
+++ b/docs/cancellation-and-timeouts.md
@@ -1,482 +1,3 @@
-<!--- TEST_NAME CancellationGuideTest -->
+The documentation has been moved to the [https://kotlinlang.org/docs/cancellation-and-timeouts.html](https://kotlinlang.org/docs/cancellation-and-timeouts.html) page.
-**Table of contents**
-
-<!--- TOC -->
-
-* [Cancellation and Timeouts](#cancellation-and-timeouts)
- * [Cancelling coroutine execution](#cancelling-coroutine-execution)
- * [Cancellation is cooperative](#cancellation-is-cooperative)
- * [Making computation code cancellable](#making-computation-code-cancellable)
- * [Closing resources with `finally`](#closing-resources-with-finally)
- * [Run non-cancellable block](#run-non-cancellable-block)
- * [Timeout](#timeout)
- * [Asynchronous timeout and resources](#asynchronous-timeout-and-resources)
-
-<!--- END -->
-
-## Cancellation and Timeouts
-
-This section covers coroutine cancellation and timeouts.
-
-### Cancelling coroutine execution
-
-In a long-running application you might need fine-grained control on your background coroutines.
-For example, a user might have closed the page that launched a coroutine and now its result
-is no longer needed and its operation can be cancelled.
-The [launch] function returns a [Job] that can be used to cancel the running coroutine:
-
-<div class="sample" markdown="1" theme="idea" data-min-compiler-version="1.3">
-
-```kotlin
-import kotlinx.coroutines.*
-
-fun main() = runBlocking {
-//sampleStart
- val job = launch {
- repeat(1000) { i ->
- println("job: I'm sleeping $i ...")
- delay(500L)
- }
- }
- delay(1300L) // delay a bit
- println("main: I'm tired of waiting!")
- job.cancel() // cancels the job
- job.join() // waits for job's completion
- println("main: Now I can quit.")
-//sampleEnd
-}
-```
-
-</div>
-
-> You can get the full code [here](../kotlinx-coroutines-core/jvm/test/guide/example-cancel-01.kt).
-
-It produces the following output:
-
-```text
-job: I'm sleeping 0 ...
-job: I'm sleeping 1 ...
-job: I'm sleeping 2 ...
-main: I'm tired of waiting!
-main: Now I can quit.
-```
-
-<!--- TEST -->
-
-As soon as main invokes `job.cancel`, we don't see any output from the other coroutine because it was cancelled.
-There is also a [Job] extension function [cancelAndJoin]
-that combines [cancel][Job.cancel] and [join][Job.join] invocations.
-
-### Cancellation is cooperative
-
-Coroutine cancellation is _cooperative_. A coroutine code has to cooperate to be cancellable.
-All the suspending functions in `kotlinx.coroutines` are _cancellable_. They check for cancellation of
-coroutine and throw [CancellationException] when cancelled. However, if a coroutine is working in
-a computation and does not check for cancellation, then it cannot be cancelled, like the following
-example shows:
-
-<div class="sample" markdown="1" theme="idea" data-min-compiler-version="1.3">
-
-```kotlin
-import kotlinx.coroutines.*
-
-fun main() = runBlocking {
-//sampleStart
- val startTime = System.currentTimeMillis()
- val job = launch(Dispatchers.Default) {
- var nextPrintTime = startTime
- var i = 0
- while (i < 5) { // computation loop, just wastes CPU
- // print a message twice a second
- if (System.currentTimeMillis() >= nextPrintTime) {
- println("job: I'm sleeping ${i++} ...")
- nextPrintTime += 500L
- }
- }
- }
- delay(1300L) // delay a bit
- println("main: I'm tired of waiting!")
- job.cancelAndJoin() // cancels the job and waits for its completion
- println("main: Now I can quit.")
-//sampleEnd
-}
-```
-
-</div>
-
-> You can get the full code [here](../kotlinx-coroutines-core/jvm/test/guide/example-cancel-02.kt).
-
-Run it to see that it continues to print "I'm sleeping" even after cancellation
-until the job completes by itself after five iterations.
-
-<!--- TEST
-job: I'm sleeping 0 ...
-job: I'm sleeping 1 ...
-job: I'm sleeping 2 ...
-main: I'm tired of waiting!
-job: I'm sleeping 3 ...
-job: I'm sleeping 4 ...
-main: Now I can quit.
--->
-
-### Making computation code cancellable
-
-There are two approaches to making computation code cancellable. The first one is to periodically
-invoke a suspending function that checks for cancellation. There is a [yield] function that is a good choice for that purpose.
-The other one is to explicitly check the cancellation status. Let us try the latter approach.
-
-Replace `while (i < 5)` in the previous example with `while (isActive)` and rerun it.
-
-<div class="sample" markdown="1" theme="idea" data-min-compiler-version="1.3">
-
-```kotlin
-import kotlinx.coroutines.*
-
-fun main() = runBlocking {
-//sampleStart
- val startTime = System.currentTimeMillis()
- val job = launch(Dispatchers.Default) {
- var nextPrintTime = startTime
- var i = 0
- while (isActive) { // cancellable computation loop
- // print a message twice a second
- if (System.currentTimeMillis() >= nextPrintTime) {
- println("job: I'm sleeping ${i++} ...")
- nextPrintTime += 500L
- }
- }
- }
- delay(1300L) // delay a bit
- println("main: I'm tired of waiting!")
- job.cancelAndJoin() // cancels the job and waits for its completion
- println("main: Now I can quit.")
-//sampleEnd
-}
-```
-
-</div>
-
-> You can get the full code [here](../kotlinx-coroutines-core/jvm/test/guide/example-cancel-03.kt).
-
-As you can see, now this loop is cancelled. [isActive] is an extension property
-available inside the coroutine via the [CoroutineScope] object.
-
-<!--- TEST
-job: I'm sleeping 0 ...
-job: I'm sleeping 1 ...
-job: I'm sleeping 2 ...
-main: I'm tired of waiting!
-main: Now I can quit.
--->
-
-### Closing resources with `finally`
-
-Cancellable suspending functions throw [CancellationException] on cancellation which can be handled in
-the usual way. For example, `try {...} finally {...}` expression and Kotlin `use` function execute their
-finalization actions normally when a coroutine is cancelled:
-
-
-<div class="sample" markdown="1" theme="idea" data-min-compiler-version="1.3">
-
-```kotlin
-import kotlinx.coroutines.*
-
-fun main() = runBlocking {
-//sampleStart
- val job = launch {
- try {
- repeat(1000) { i ->
- println("job: I'm sleeping $i ...")
- delay(500L)
- }
- } finally {
- println("job: I'm running finally")
- }
- }
- delay(1300L) // delay a bit
- println("main: I'm tired of waiting!")
- job.cancelAndJoin() // cancels the job and waits for its completion
- println("main: Now I can quit.")
-//sampleEnd
-}
-```
-
-</div>
-
-> You can get the full code [here](../kotlinx-coroutines-core/jvm/test/guide/example-cancel-04.kt).
-
-Both [join][Job.join] and [cancelAndJoin] wait for all finalization actions to complete,
-so the example above produces the following output:
-
-```text
-job: I'm sleeping 0 ...
-job: I'm sleeping 1 ...
-job: I'm sleeping 2 ...
-main: I'm tired of waiting!
-job: I'm running finally
-main: Now I can quit.
-```
-
-<!--- TEST -->
-
-### Run non-cancellable block
-
-Any attempt to use a suspending function in the `finally` block of the previous example causes
-[CancellationException], because the coroutine running this code is cancelled. Usually, this is not a
-problem, since all well-behaving closing operations (closing a file, cancelling a job, or closing any kind of a
-communication channel) are usually non-blocking and do not involve any suspending functions. However, in the
-rare case when you need to suspend in a cancelled coroutine you can wrap the corresponding code in
-`withContext(NonCancellable) {...}` using [withContext] function and [NonCancellable] context as the following example shows:
-
-<div class="sample" markdown="1" theme="idea" data-min-compiler-version="1.3">
-
-```kotlin
-import kotlinx.coroutines.*
-
-fun main() = runBlocking {
-//sampleStart
- val job = launch {
- try {
- repeat(1000) { i ->
- println("job: I'm sleeping $i ...")
- delay(500L)
- }
- } finally {
- withContext(NonCancellable) {
- println("job: I'm running finally")
- delay(1000L)
- println("job: And I've just delayed for 1 sec because I'm non-cancellable")
- }
- }
- }
- delay(1300L) // delay a bit
- println("main: I'm tired of waiting!")
- job.cancelAndJoin() // cancels the job and waits for its completion
- println("main: Now I can quit.")
-//sampleEnd
-}
-```
-
-</div>
-
-> You can get the full code [here](../kotlinx-coroutines-core/jvm/test/guide/example-cancel-05.kt).
-
-<!--- TEST
-job: I'm sleeping 0 ...
-job: I'm sleeping 1 ...
-job: I'm sleeping 2 ...
-main: I'm tired of waiting!
-job: I'm running finally
-job: And I've just delayed for 1 sec because I'm non-cancellable
-main: Now I can quit.
--->
-
-### Timeout
-
-The most obvious practical reason to cancel execution of a coroutine
-is because its execution time has exceeded some timeout.
-While you can manually track the reference to the corresponding [Job] and launch a separate coroutine to cancel
-the tracked one after delay, there is a ready to use [withTimeout] function that does it.
-Look at the following example:
-
-<div class="sample" markdown="1" theme="idea" data-min-compiler-version="1.3">
-
-```kotlin
-import kotlinx.coroutines.*
-
-fun main() = runBlocking {
-//sampleStart
- withTimeout(1300L) {
- repeat(1000) { i ->
- println("I'm sleeping $i ...")
- delay(500L)
- }
- }
-//sampleEnd
-}
-```
-
-</div>
-
-> You can get the full code [here](../kotlinx-coroutines-core/jvm/test/guide/example-cancel-06.kt).
-
-It produces the following output:
-
-```text
-I'm sleeping 0 ...
-I'm sleeping 1 ...
-I'm sleeping 2 ...
-Exception in thread "main" kotlinx.coroutines.TimeoutCancellationException: Timed out waiting for 1300 ms
-```
-
-<!--- TEST STARTS_WITH -->
-
-The `TimeoutCancellationException` that is thrown by [withTimeout] is a subclass of [CancellationException].
-We have not seen its stack trace printed on the console before. That is because
-inside a cancelled coroutine `CancellationException` is considered to be a normal reason for coroutine completion.
-However, in this example we have used `withTimeout` right inside the `main` function.
-
-Since cancellation is just an exception, all resources are closed in the usual way.
-You can wrap the code with timeout in a `try {...} catch (e: TimeoutCancellationException) {...}` block if
-you need to do some additional action specifically on any kind of timeout or use the [withTimeoutOrNull] function
-that is similar to [withTimeout] but returns `null` on timeout instead of throwing an exception:
-
-<div class="sample" markdown="1" theme="idea" data-min-compiler-version="1.3">
-
-```kotlin
-import kotlinx.coroutines.*
-
-fun main() = runBlocking {
-//sampleStart
- val result = withTimeoutOrNull(1300L) {
- repeat(1000) { i ->
- println("I'm sleeping $i ...")
- delay(500L)
- }
- "Done" // will get cancelled before it produces this result
- }
- println("Result is $result")
-//sampleEnd
-}
-```
-
-</div>
-
-> You can get the full code [here](../kotlinx-coroutines-core/jvm/test/guide/example-cancel-07.kt).
-
-There is no longer an exception when running this code:
-
-```text
-I'm sleeping 0 ...
-I'm sleeping 1 ...
-I'm sleeping 2 ...
-Result is null
-```
-
-<!--- TEST -->
-
-### Asynchronous timeout and resources
-
-<!--
- NOTE: Don't change this section name. It is being referenced to from within KDoc of withTimeout functions.
--->
-
-The timeout event in [withTimeout] is asynchronous with respect to the code running in its block and may happen at any time,
-even right before the return from inside of the timeout block. Keep this in mind if you open or acquire some
-resource inside the block that needs closing or release outside of the block.
-
-For example, here we imitate a closeable resource with the `Resource` class, that simply keeps track of how many times
-it was created by incrementing the `acquired` counter and decrementing this counter from its `close` function.
-Let us run a lot of coroutines with the small timeout try acquire this resource from inside
-of the `withTimeout` block after a bit of delay and release it from outside.
-
-<div class="sample" markdown="1" theme="idea" data-min-compiler-version="1.3">
-
-```kotlin
-import kotlinx.coroutines.*
-
-//sampleStart
-var acquired = 0
-
-class Resource {
- init { acquired++ } // Acquire the resource
- fun close() { acquired-- } // Release the resource
-}
-
-fun main() {
- runBlocking {
- repeat(100_000) { // Launch 100K coroutines
- launch {
- val resource = withTimeout(60) { // Timeout of 60 ms
- delay(50) // Delay for 50 ms
- Resource() // Acquire a resource and return it from withTimeout block
- }
- resource.close() // Release the resource
- }
- }
- }
- // Outside of runBlocking all coroutines have completed
- println(acquired) // Print the number of resources still acquired
-}
-//sampleEnd
-```
-
-</div>
-
-> You can get the full code [here](../kotlinx-coroutines-core/jvm/test/guide/example-cancel-08.kt).
-
-<!--- CLEAR -->
-
-If you run the above code you'll see that it does not always print zero, though it may depend on the timings
-of your machine you may need to tweak timeouts in this example to actually see non-zero values.
-
-> Note, that incrementing and decrementing `acquired` counter here from 100K coroutines is completely safe,
-> since it always happens from the same main thread. More on that will be explained in the next chapter
-> on coroutine context.
-
-To workaround this problem you can store a reference to the resource in the variable as opposed to returning it
-from the `withTimeout` block.
-
-<div class="sample" markdown="1" theme="idea" data-min-compiler-version="1.3">
-
-```kotlin
-import kotlinx.coroutines.*
-
-var acquired = 0
-
-class Resource {
- init { acquired++ } // Acquire the resource
- fun close() { acquired-- } // Release the resource
-}
-
-fun main() {
-//sampleStart
- runBlocking {
- repeat(100_000) { // Launch 100K coroutines
- launch {
- var resource: Resource? = null // Not acquired yet
- try {
- withTimeout(60) { // Timeout of 60 ms
- delay(50) // Delay for 50 ms
- resource = Resource() // Store a resource to the variable if acquired
- }
- // We can do something else with the resource here
- } finally {
- resource?.close() // Release the resource if it was acquired
- }
- }
- }
- }
- // Outside of runBlocking all coroutines have completed
- println(acquired) // Print the number of resources still acquired
-//sampleEnd
-}
-```
-
-</div>
-
-> You can get the full code [here](../kotlinx-coroutines-core/jvm/test/guide/example-cancel-09.kt).
-
-This example always prints zero. Resources do not leak.
-
-<!--- TEST
-0
--->
-
-<!--- MODULE kotlinx-coroutines-core -->
-<!--- INDEX kotlinx.coroutines -->
-[launch]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/launch.html
-[Job]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job/index.html
-[cancelAndJoin]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/cancel-and-join.html
-[Job.cancel]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job/cancel.html
-[Job.join]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job/join.html
-[CancellationException]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-cancellation-exception/index.html
-[yield]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/yield.html
-[isActive]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/is-active.html
-[CoroutineScope]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-scope/index.html
-[withContext]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/with-context.html
-[NonCancellable]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-non-cancellable.html
-[withTimeout]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/with-timeout.html
-[withTimeoutOrNull]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/with-timeout-or-null.html
-<!--- END -->
+To edit the documentation, open the [topics/cancellation-and-timeouts.md](topics/cancellation-and-timeouts.md) page. \ No newline at end of file
diff --git a/docs/cfg/buildprofiles.xml b/docs/cfg/buildprofiles.xml
new file mode 100644
index 00000000..ac4d04ad
--- /dev/null
+++ b/docs/cfg/buildprofiles.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<buildprofiles>
+ <variables>
+ <enable-browser-edits>true</enable-browser-edits>
+ <browser-edits-url>https://github.com/Kotlin/kotlinx.coroutines/edit/master/</browser-edits-url>
+ <allow-indexable-eaps>true</allow-indexable-eaps>
+ </variables>
+ <build-profile product="kc"/>
+</buildprofiles>
diff --git a/docs/channels.md b/docs/channels.md
index d1a4bac0..73415ce9 100644
--- a/docs/channels.md
+++ b/docs/channels.md
@@ -1,702 +1,3 @@
-<!--- TEST_NAME ChannelsGuideTest -->
+The documentation has been moved to the [https://kotlinlang.org/docs/channels.html](https://kotlinlang.org/docs/channels.html) page.
-**Table of contents**
-
-<!--- TOC -->
-
-* [Channels](#channels)
- * [Channel basics](#channel-basics)
- * [Closing and iteration over channels](#closing-and-iteration-over-channels)
- * [Building channel producers](#building-channel-producers)
- * [Pipelines](#pipelines)
- * [Prime numbers with pipeline](#prime-numbers-with-pipeline)
- * [Fan-out](#fan-out)
- * [Fan-in](#fan-in)
- * [Buffered channels](#buffered-channels)
- * [Channels are fair](#channels-are-fair)
- * [Ticker channels](#ticker-channels)
-
-<!--- END -->
-
-## Channels
-
-Deferred values provide a convenient way to transfer a single value between coroutines.
-Channels provide a way to transfer a stream of values.
-
-### Channel basics
-
-A [Channel] is conceptually very similar to `BlockingQueue`. One key difference is that
-instead of a blocking `put` operation it has a suspending [send][SendChannel.send], and instead of
-a blocking `take` operation it has a suspending [receive][ReceiveChannel.receive].
-
-
-<div class="sample" markdown="1" theme="idea" data-min-compiler-version="1.3">
-
-```kotlin
-import kotlinx.coroutines.*
-import kotlinx.coroutines.channels.*
-
-fun main() = runBlocking {
-//sampleStart
- val channel = Channel<Int>()
- launch {
- // this might be heavy CPU-consuming computation or async logic, we'll just send five squares
- for (x in 1..5) channel.send(x * x)
- }
- // here we print five received integers:
- repeat(5) { println(channel.receive()) }
- println("Done!")
-//sampleEnd
-}
-```
-
-</div>
-
-> You can get the full code [here](../kotlinx-coroutines-core/jvm/test/guide/example-channel-01.kt).
-
-The output of this code is:
-
-```text
-1
-4
-9
-16
-25
-Done!
-```
-
-<!--- TEST -->
-
-### Closing and iteration over channels
-
-Unlike a queue, a channel can be closed to indicate that no more elements are coming.
-On the receiver side it is convenient to use a regular `for` loop to receive elements
-from the channel.
-
-Conceptually, a [close][SendChannel.close] is like sending a special close token to the channel.
-The iteration stops as soon as this close token is received, so there is a guarantee
-that all previously sent elements before the close are received:
-
-<div class="sample" markdown="1" theme="idea" data-min-compiler-version="1.3">
-
-```kotlin
-import kotlinx.coroutines.*
-import kotlinx.coroutines.channels.*
-
-fun main() = runBlocking {
-//sampleStart
- val channel = Channel<Int>()
- launch {
- for (x in 1..5) channel.send(x * x)
- channel.close() // we're done sending
- }
- // here we print received values using `for` loop (until the channel is closed)
- for (y in channel) println(y)
- println("Done!")
-//sampleEnd
-}
-```
-
-</div>
-
-> You can get the full code [here](../kotlinx-coroutines-core/jvm/test/guide/example-channel-02.kt).
-
-<!--- TEST
-1
-4
-9
-16
-25
-Done!
--->
-
-### Building channel producers
-
-The pattern where a coroutine is producing a sequence of elements is quite common.
-This is a part of _producer-consumer_ pattern that is often found in concurrent code.
-You could abstract such a producer into a function that takes channel as its parameter, but this goes contrary
-to common sense that results must be returned from functions.
-
-There is a convenient coroutine builder named [produce] that makes it easy to do it right on producer side,
-and an extension function [consumeEach], that replaces a `for` loop on the consumer side:
-
-<div class="sample" markdown="1" theme="idea" data-min-compiler-version="1.3">
-
-```kotlin
-import kotlinx.coroutines.*
-import kotlinx.coroutines.channels.*
-
-fun CoroutineScope.produceSquares(): ReceiveChannel<Int> = produce {
- for (x in 1..5) send(x * x)
-}
-
-fun main() = runBlocking {
-//sampleStart
- val squares = produceSquares()
- squares.consumeEach { println(it) }
- println("Done!")
-//sampleEnd
-}
-```
-
-</div>
-
-> You can get the full code [here](../kotlinx-coroutines-core/jvm/test/guide/example-channel-03.kt).
-
-<!--- TEST
-1
-4
-9
-16
-25
-Done!
--->
-
-### Pipelines
-
-A pipeline is a pattern where one coroutine is producing, possibly infinite, stream of values:
-
-<div class="sample" markdown="1" theme="idea" data-highlight-only>
-
-```kotlin
-fun CoroutineScope.produceNumbers() = produce<Int> {
- var x = 1
- while (true) send(x++) // infinite stream of integers starting from 1
-}
-```
-
-</div>
-
-And another coroutine or coroutines are consuming that stream, doing some processing, and producing some other results.
-In the example below, the numbers are just squared:
-
-<div class="sample" markdown="1" theme="idea" data-highlight-only>
-
-```kotlin
-fun CoroutineScope.square(numbers: ReceiveChannel<Int>): ReceiveChannel<Int> = produce {
- for (x in numbers) send(x * x)
-}
-```
-
-</div>
-
-The main code starts and connects the whole pipeline:
-
-<!--- CLEAR -->
-
-<div class="sample" markdown="1" theme="idea" data-min-compiler-version="1.3">
-
-```kotlin
-import kotlinx.coroutines.*
-import kotlinx.coroutines.channels.*
-
-fun main() = runBlocking {
-//sampleStart
- val numbers = produceNumbers() // produces integers from 1 and on
- val squares = square(numbers) // squares integers
- repeat(5) {
- println(squares.receive()) // print first five
- }
- println("Done!") // we are done
- coroutineContext.cancelChildren() // cancel children coroutines
-//sampleEnd
-}
-
-fun CoroutineScope.produceNumbers() = produce<Int> {
- var x = 1
- while (true) send(x++) // infinite stream of integers starting from 1
-}
-
-fun CoroutineScope.square(numbers: ReceiveChannel<Int>): ReceiveChannel<Int> = produce {
- for (x in numbers) send(x * x)
-}
-```
-
-</div>
-
-> You can get the full code [here](../kotlinx-coroutines-core/jvm/test/guide/example-channel-04.kt).
-
-<!--- TEST
-1
-4
-9
-16
-25
-Done!
--->
-
-> All functions that create coroutines are defined as extensions on [CoroutineScope],
-so that we can rely on [structured concurrency](https://kotlinlang.org/docs/reference/coroutines/composing-suspending-functions.html#structured-concurrency-with-async) to make
-sure that we don't have lingering global coroutines in our application.
-
-### Prime numbers with pipeline
-
-Let's take pipelines to the extreme with an example that generates prime numbers using a pipeline
-of coroutines. We start with an infinite sequence of numbers.
-
-<div class="sample" markdown="1" theme="idea" data-highlight-only>
-
-```kotlin
-fun CoroutineScope.numbersFrom(start: Int) = produce<Int> {
- var x = start
- while (true) send(x++) // infinite stream of integers from start
-}
-```
-
-</div>
-
-The following pipeline stage filters an incoming stream of numbers, removing all the numbers
-that are divisible by the given prime number:
-
-<div class="sample" markdown="1" theme="idea" data-highlight-only>
-
-```kotlin
-fun CoroutineScope.filter(numbers: ReceiveChannel<Int>, prime: Int) = produce<Int> {
- for (x in numbers) if (x % prime != 0) send(x)
-}
-```
-
-</div>
-
-Now we build our pipeline by starting a stream of numbers from 2, taking a prime number from the current channel,
-and launching new pipeline stage for each prime number found:
-
-```
-numbersFrom(2) -> filter(2) -> filter(3) -> filter(5) -> filter(7) ...
-```
-
-The following example prints the first ten prime numbers,
-running the whole pipeline in the context of the main thread. Since all the coroutines are launched in
-the scope of the main [runBlocking] coroutine
-we don't have to keep an explicit list of all the coroutines we have started.
-We use [cancelChildren][kotlin.coroutines.CoroutineContext.cancelChildren]
-extension function to cancel all the children coroutines after we have printed
-the first ten prime numbers.
-
-<!--- CLEAR -->
-
-<div class="sample" markdown="1" theme="idea" data-min-compiler-version="1.3">
-
-```kotlin
-import kotlinx.coroutines.*
-import kotlinx.coroutines.channels.*
-
-fun main() = runBlocking {
-//sampleStart
- var cur = numbersFrom(2)
- repeat(10) {
- val prime = cur.receive()
- println(prime)
- cur = filter(cur, prime)
- }
- coroutineContext.cancelChildren() // cancel all children to let main finish
-//sampleEnd
-}
-
-fun CoroutineScope.numbersFrom(start: Int) = produce<Int> {
- var x = start
- while (true) send(x++) // infinite stream of integers from start
-}
-
-fun CoroutineScope.filter(numbers: ReceiveChannel<Int>, prime: Int) = produce<Int> {
- for (x in numbers) if (x % prime != 0) send(x)
-}
-```
-
-</div>
-
-> You can get the full code [here](../kotlinx-coroutines-core/jvm/test/guide/example-channel-05.kt).
-
-The output of this code is:
-
-```text
-2
-3
-5
-7
-11
-13
-17
-19
-23
-29
-```
-
-<!--- TEST -->
-
-Note that you can build the same pipeline using
-[`iterator`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.sequences/iterator.html)
-coroutine builder from the standard library.
-Replace `produce` with `iterator`, `send` with `yield`, `receive` with `next`,
-`ReceiveChannel` with `Iterator`, and get rid of the coroutine scope. You will not need `runBlocking` either.
-However, the benefit of a pipeline that uses channels as shown above is that it can actually use
-multiple CPU cores if you run it in [Dispatchers.Default] context.
-
-Anyway, this is an extremely impractical way to find prime numbers. In practice, pipelines do involve some
-other suspending invocations (like asynchronous calls to remote services) and these pipelines cannot be
-built using `sequence`/`iterator`, because they do not allow arbitrary suspension, unlike
-`produce`, which is fully asynchronous.
-
-### Fan-out
-
-Multiple coroutines may receive from the same channel, distributing work between themselves.
-Let us start with a producer coroutine that is periodically producing integers
-(ten numbers per second):
-
-<div class="sample" markdown="1" theme="idea" data-highlight-only>
-
-```kotlin
-fun CoroutineScope.produceNumbers() = produce<Int> {
- var x = 1 // start from 1
- while (true) {
- send(x++) // produce next
- delay(100) // wait 0.1s
- }
-}
-```
-
-</div>
-
-Then we can have several processor coroutines. In this example, they just print their id and
-received number:
-
-<div class="sample" markdown="1" theme="idea" data-highlight-only>
-
-```kotlin
-fun CoroutineScope.launchProcessor(id: Int, channel: ReceiveChannel<Int>) = launch {
- for (msg in channel) {
- println("Processor #$id received $msg")
- }
-}
-```
-
-</div>
-
-Now let us launch five processors and let them work for almost a second. See what happens:
-
-<!--- CLEAR -->
-
-<div class="sample" markdown="1" theme="idea" data-min-compiler-version="1.3">
-
-```kotlin
-import kotlinx.coroutines.*
-import kotlinx.coroutines.channels.*
-
-fun main() = runBlocking<Unit> {
-//sampleStart
- val producer = produceNumbers()
- repeat(5) { launchProcessor(it, producer) }
- delay(950)
- producer.cancel() // cancel producer coroutine and thus kill them all
-//sampleEnd
-}
-
-fun CoroutineScope.produceNumbers() = produce<Int> {
- var x = 1 // start from 1
- while (true) {
- send(x++) // produce next
- delay(100) // wait 0.1s
- }
-}
-
-fun CoroutineScope.launchProcessor(id: Int, channel: ReceiveChannel<Int>) = launch {
- for (msg in channel) {
- println("Processor #$id received $msg")
- }
-}
-```
-
-</div>
-
-> You can get the full code [here](../kotlinx-coroutines-core/jvm/test/guide/example-channel-06.kt).
-
-The output will be similar to the the following one, albeit the processor ids that receive
-each specific integer may be different:
-
-```
-Processor #2 received 1
-Processor #4 received 2
-Processor #0 received 3
-Processor #1 received 4
-Processor #3 received 5
-Processor #2 received 6
-Processor #4 received 7
-Processor #0 received 8
-Processor #1 received 9
-Processor #3 received 10
-```
-
-<!--- TEST lines.size == 10 && lines.withIndex().all { (i, line) -> line.startsWith("Processor #") && line.endsWith(" received ${i + 1}") } -->
-
-Note that cancelling a producer coroutine closes its channel, thus eventually terminating iteration
-over the channel that processor coroutines are doing.
-
-Also, pay attention to how we explicitly iterate over channel with `for` loop to perform fan-out in `launchProcessor` code.
-Unlike `consumeEach`, this `for` loop pattern is perfectly safe to use from multiple coroutines. If one of the processor
-coroutines fails, then others would still be processing the channel, while a processor that is written via `consumeEach`
-always consumes (cancels) the underlying channel on its normal or abnormal completion.
-
-### Fan-in
-
-Multiple coroutines may send to the same channel.
-For example, let us have a channel of strings, and a suspending function that
-repeatedly sends a specified string to this channel with a specified delay:
-
-<div class="sample" markdown="1" theme="idea" data-highlight-only>
-
-```kotlin
-suspend fun sendString(channel: SendChannel<String>, s: String, time: Long) {
- while (true) {
- delay(time)
- channel.send(s)
- }
-}
-```
-
-</div>
-
-Now, let us see what happens if we launch a couple of coroutines sending strings
-(in this example we launch them in the context of the main thread as main coroutine's children):
-
-<!--- CLEAR -->
-
-<div class="sample" markdown="1" theme="idea" data-min-compiler-version="1.3">
-
-```kotlin
-import kotlinx.coroutines.*
-import kotlinx.coroutines.channels.*
-
-fun main() = runBlocking {
-//sampleStart
- val channel = Channel<String>()
- launch { sendString(channel, "foo", 200L) }
- launch { sendString(channel, "BAR!", 500L) }
- repeat(6) { // receive first six
- println(channel.receive())
- }
- coroutineContext.cancelChildren() // cancel all children to let main finish
-//sampleEnd
-}
-
-suspend fun sendString(channel: SendChannel<String>, s: String, time: Long) {
- while (true) {
- delay(time)
- channel.send(s)
- }
-}
-```
-
-</div>
-
-> You can get the full code [here](../kotlinx-coroutines-core/jvm/test/guide/example-channel-07.kt).
-
-The output is:
-
-```text
-foo
-foo
-BAR!
-foo
-foo
-BAR!
-```
-
-<!--- TEST -->
-
-### Buffered channels
-
-The channels shown so far had no buffer. Unbuffered channels transfer elements when sender and receiver
-meet each other (aka rendezvous). If send is invoked first, then it is suspended until receive is invoked,
-if receive is invoked first, it is suspended until send is invoked.
-
-Both [Channel()] factory function and [produce] builder take an optional `capacity` parameter to
-specify _buffer size_. Buffer allows senders to send multiple elements before suspending,
-similar to the `BlockingQueue` with a specified capacity, which blocks when buffer is full.
-
-Take a look at the behavior of the following code:
-
-
-<div class="sample" markdown="1" theme="idea" data-min-compiler-version="1.3">
-
-```kotlin
-import kotlinx.coroutines.*
-import kotlinx.coroutines.channels.*
-
-fun main() = runBlocking<Unit> {
-//sampleStart
- val channel = Channel<Int>(4) // create buffered channel
- val sender = launch { // launch sender coroutine
- repeat(10) {
- println("Sending $it") // print before sending each element
- channel.send(it) // will suspend when buffer is full
- }
- }
- // don't receive anything... just wait....
- delay(1000)
- sender.cancel() // cancel sender coroutine
-//sampleEnd
-}
-```
-
-</div>
-
-> You can get the full code [here](../kotlinx-coroutines-core/jvm/test/guide/example-channel-08.kt).
-
-It prints "sending" _five_ times using a buffered channel with capacity of _four_:
-
-```text
-Sending 0
-Sending 1
-Sending 2
-Sending 3
-Sending 4
-```
-
-<!--- TEST -->
-
-The first four elements are added to the buffer and the sender suspends when trying to send the fifth one.
-
-### Channels are fair
-
-Send and receive operations to channels are _fair_ with respect to the order of their invocation from
-multiple coroutines. They are served in first-in first-out order, e.g. the first coroutine to invoke `receive`
-gets the element. In the following example two coroutines "ping" and "pong" are
-receiving the "ball" object from the shared "table" channel.
-
-
-<div class="sample" markdown="1" theme="idea" data-min-compiler-version="1.3">
-
-```kotlin
-import kotlinx.coroutines.*
-import kotlinx.coroutines.channels.*
-
-//sampleStart
-data class Ball(var hits: Int)
-
-fun main() = runBlocking {
- val table = Channel<Ball>() // a shared table
- launch { player("ping", table) }
- launch { player("pong", table) }
- table.send(Ball(0)) // serve the ball
- delay(1000) // delay 1 second
- coroutineContext.cancelChildren() // game over, cancel them
-}
-
-suspend fun player(name: String, table: Channel<Ball>) {
- for (ball in table) { // receive the ball in a loop
- ball.hits++
- println("$name $ball")
- delay(300) // wait a bit
- table.send(ball) // send the ball back
- }
-}
-//sampleEnd
-```
-
-</div>
-
-> You can get the full code [here](../kotlinx-coroutines-core/jvm/test/guide/example-channel-09.kt).
-
-The "ping" coroutine is started first, so it is the first one to receive the ball. Even though "ping"
-coroutine immediately starts receiving the ball again after sending it back to the table, the ball gets
-received by the "pong" coroutine, because it was already waiting for it:
-
-```text
-ping Ball(hits=1)
-pong Ball(hits=2)
-ping Ball(hits=3)
-pong Ball(hits=4)
-```
-
-<!--- TEST -->
-
-Note that sometimes channels may produce executions that look unfair due to the nature of the executor
-that is being used. See [this issue](https://github.com/Kotlin/kotlinx.coroutines/issues/111) for details.
-
-### Ticker channels
-
-Ticker channel is a special rendezvous channel that produces `Unit` every time given delay passes since last consumption from this channel.
-Though it may seem to be useless standalone, it is a useful building block to create complex time-based [produce]
-pipelines and operators that do windowing and other time-dependent processing.
-Ticker channel can be used in [select] to perform "on tick" action.
-
-To create such channel use a factory method [ticker].
-To indicate that no further elements are needed use [ReceiveChannel.cancel] method on it.
-
-Now let's see how it works in practice:
-
-<div class="sample" markdown="1" theme="idea" data-highlight-only>
-
-```kotlin
-import kotlinx.coroutines.*
-import kotlinx.coroutines.channels.*
-
-fun main() = runBlocking<Unit> {
- val tickerChannel = ticker(delayMillis = 100, initialDelayMillis = 0) // create ticker channel
- var nextElement = withTimeoutOrNull(1) { tickerChannel.receive() }
- println("Initial element is available immediately: $nextElement") // no initial delay
-
- nextElement = withTimeoutOrNull(50) { tickerChannel.receive() } // all subsequent elements have 100ms delay
- println("Next element is not ready in 50 ms: $nextElement")
-
- nextElement = withTimeoutOrNull(60) { tickerChannel.receive() }
- println("Next element is ready in 100 ms: $nextElement")
-
- // Emulate large consumption delays
- println("Consumer pauses for 150ms")
- delay(150)
- // Next element is available immediately
- nextElement = withTimeoutOrNull(1) { tickerChannel.receive() }
- println("Next element is available immediately after large consumer delay: $nextElement")
- // Note that the pause between `receive` calls is taken into account and next element arrives faster
- nextElement = withTimeoutOrNull(60) { tickerChannel.receive() }
- println("Next element is ready in 50ms after consumer pause in 150ms: $nextElement")
-
- tickerChannel.cancel() // indicate that no more elements are needed
-}
-```
-
-</div>
-
-> You can get the full code [here](../kotlinx-coroutines-core/jvm/test/guide/example-channel-10.kt).
-
-It prints following lines:
-
-```text
-Initial element is available immediately: kotlin.Unit
-Next element is not ready in 50 ms: null
-Next element is ready in 100 ms: kotlin.Unit
-Consumer pauses for 150ms
-Next element is available immediately after large consumer delay: kotlin.Unit
-Next element is ready in 50ms after consumer pause in 150ms: kotlin.Unit
-```
-
-<!--- TEST -->
-
-Note that [ticker] is aware of possible consumer pauses and, by default, adjusts next produced element
-delay if a pause occurs, trying to maintain a fixed rate of produced elements.
-
-Optionally, a `mode` parameter equal to [TickerMode.FIXED_DELAY] can be specified to maintain a fixed
-delay between elements.
-
-
-<!--- MODULE kotlinx-coroutines-core -->
-<!--- INDEX kotlinx.coroutines -->
-[CoroutineScope]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-scope/index.html
-[runBlocking]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/run-blocking.html
-[kotlin.coroutines.CoroutineContext.cancelChildren]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/kotlin.coroutines.-coroutine-context/cancel-children.html
-[Dispatchers.Default]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-dispatchers/-default.html
-<!--- INDEX kotlinx.coroutines.channels -->
-[Channel]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-channel/index.html
-[SendChannel.send]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-send-channel/send.html
-[ReceiveChannel.receive]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-receive-channel/receive.html
-[SendChannel.close]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-send-channel/close.html
-[produce]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/produce.html
-[consumeEach]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/consume-each.html
-[Channel()]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-channel.html
-[ticker]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/ticker.html
-[ReceiveChannel.cancel]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-receive-channel/cancel.html
-[TickerMode.FIXED_DELAY]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-ticker-mode/-f-i-x-e-d_-d-e-l-a-y.html
-<!--- INDEX kotlinx.coroutines.selects -->
-[select]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.selects/select.html
-<!--- END -->
+To edit the documentation, open the [topics/channels.md](topics/channels.md) page. \ No newline at end of file
diff --git a/docs/compatibility.md b/docs/compatibility.md
index 8dafae72..b61f6299 100644
--- a/docs/compatibility.md
+++ b/docs/compatibility.md
@@ -1,125 +1 @@
-<!--- TOC -->
-
-* [Compatibility](#compatibility)
-* [Public API types](#public-api-types)
- * [Experimental API](#experimental-api)
- * [Flow preview API](#flow-preview-api)
- * [Obsolete API](#obsolete-api)
- * [Internal API](#internal-api)
- * [Stable API](#stable-api)
- * [Deprecation cycle](#deprecation-cycle)
-* [Using annotated API](#using-annotated-api)
- * [Programmatically](#programmatically)
- * [Gradle](#gradle)
- * [Maven](#maven)
-
-<!--- END -->
-
-## Compatibility
-This document describes the compatibility policy of `kotlinx.coroutines` library since version 1.0.0 and semantics of compatibility-specific annotations.
-
-
-## Public API types
-`kotlinx.coroutines` public API comes in five flavours: stable, experimental, obsolete, internal and deprecated.
-All public API except stable is marked with the corresponding annotation.
-
-### Experimental API
-Experimental API is marked with [@ExperimentalCoroutinesApi][ExperimentalCoroutinesApi] annotation.
-API is marked experimental when its design has potential open questions which may eventually lead to
-either semantics changes of the API or its deprecation.
-
-By default, most of the new API is marked as experimental and becomes stable in one of the next major releases if no new issues arise.
-Otherwise, either semantics is fixed without changes in ABI or API goes through deprecation cycle.
-
-When using experimental API may be dangerous:
-* You are writing a library which depends on `kotlinx.coroutines` and want to use experimental coroutines API in a stable library API.
-It may lead to undesired consequences when end users of your library update their `kotlinx.coroutines` version where experimental API
-has slightly different semantics.
-* You want to build core infrastructure of the application around experimental API.
-
-### Flow preview API
-All [Flow]-related API is marked with [@FlowPreview][FlowPreview] annotation.
-This annotation indicates that Flow API is in preview status.
-We provide no compatibility guarantees between releases for preview features, including binary, source and semantics compatibility.
-
-When using preview API may be dangerous:
-* You are writing a library/framework and want to use [Flow] API in a stable release or in a stable API.
-* You want to use [Flow] in the core infrastructure of your application.
-* You want to use [Flow] as "write-and-forget" solution and cannot afford additional maintenance cost when
- it comes to `kotlinx.coroutines` updates.
-
-
-### Obsolete API
-Obsolete API is marked with [@ObsoleteCoroutinesApi][ObsoleteCoroutinesApi] annotation.
-Obsolete API is similar to experimental, but already known to have serious design flaws and its potential replacement,
-but replacement is not yet implemented.
-
-The semantics of this API won't be changed, but it will go through a deprecation cycle as soon as the replacement is ready.
-
-### Internal API
-Internal API is marked with [@InternalCoroutinesApi][InternalCoroutinesApi] or is part of `kotlinx.coroutines.internal` package.
-This API has no guarantees on its stability, can and will be changed and/or removed in the future releases.
-If you can't avoid using internal API, please report it to [issue tracker](https://github.com/Kotlin/kotlinx.coroutines/issues/new).
-
-### Stable API
-Stable API is guaranteed to preserve its ABI and documented semantics. If at some point unfixable design flaws will be discovered,
-this API will go through a deprecation cycle and remain binary compatible as long as possible.
-
-### Deprecation cycle
-When some API is deprecated, it goes through multiple stages and there is at least one major release between stages.
-* Feature is deprecated with compilation warning. Most of the time, proper replacement
-(and corresponding `replaceWith` declaration) is provided to automatically migrate deprecated usages with a help of IntelliJ IDEA.
-* Deprecation level is increased to `error` or `hidden`. It is no longer possible to compile new code against deprecated API,
- though it is still present in the ABI.
-* API is completely removed. While we give our best efforts not to do so and have no plans of removing any API, we still are leaving
-this option in case of unforeseen problems such as security holes.
-
-## Using annotated API
-All API annotations are [kotlin.Experimental](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-experimental/index.html).
-It is done in order to produce compilation warning about using experimental or obsolete API.
-Warnings can be disabled either programmatically for a specific call site or globally for the whole module.
-
-### Programmatically
-For a specific call-site, warning can be disabled by using [UseExperimental](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-use-experimental/index.html) annotation:
-```kotlin
-@UseExperimental(ExperimentalCoroutinesApi::class) // Disables warning about experimental coroutines API
-fun experimentalApiUsage() {
- someKotlinxCoroutinesExperimentalMethod()
-}
-```
-
-### Gradle
-For the Gradle project, a warning can be disabled by passing a compiler flag in your `build.gradle` file:
-
-```groovy
-tasks.withType(org.jetbrains.kotlin.gradle.tasks.AbstractKotlinCompile).all {
- kotlinOptions.freeCompilerArgs += ["-Xuse-experimental=kotlinx.coroutines.ExperimentalCoroutinesApi"]
-}
-
-```
-
-### Maven
-For the Maven project, a warning can be disabled by passing a compiler flag in your `pom.xml` file:
-```xml
-<plugin>
- <artifactId>kotlin-maven-plugin</artifactId>
- <groupId>org.jetbrains.kotlin</groupId>
- ... your configuration ...
- <configuration>
- <args>
- <arg>-Xuse-experimental=kotlinx.coroutines.ExperimentalCoroutinesApi</arg>
- </args>
- </configuration>
-</plugin>
-```
-
-
-<!--- MODULE kotlinx-coroutines-core -->
-<!--- INDEX kotlinx.coroutines.flow -->
-[Flow]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/-flow/index.html
-<!--- INDEX kotlinx.coroutines -->
-[ExperimentalCoroutinesApi]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-experimental-coroutines-api/index.html
-[FlowPreview]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-flow-preview/index.html
-[ObsoleteCoroutinesApi]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-obsolete-coroutines-api/index.html
-[InternalCoroutinesApi]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-internal-coroutines-api/index.html
-<!--- END -->
+The documentation has been moved to the [topics/compatibility.md](topics/compatibility.md). \ No newline at end of file
diff --git a/docs/composing-suspending-functions.md b/docs/composing-suspending-functions.md
index 81b6f53c..4318c4ee 100644
--- a/docs/composing-suspending-functions.md
+++ b/docs/composing-suspending-functions.md
@@ -1,435 +1,3 @@
-<!--- TEST_NAME ComposingGuideTest -->
+The documentation has been moved to the [https://kotlinlang.org/docs/composing-suspending-functions.html](https://kotlinlang.org/docs/composing-suspending-functions.html) page.
-**Table of contents**
-
-<!--- TOC -->
-
-* [Composing Suspending Functions](#composing-suspending-functions)
- * [Sequential by default](#sequential-by-default)
- * [Concurrent using async](#concurrent-using-async)
- * [Lazily started async](#lazily-started-async)
- * [Async-style functions](#async-style-functions)
- * [Structured concurrency with async](#structured-concurrency-with-async)
-
-<!--- END -->
-
-## Composing Suspending Functions
-
-This section covers various approaches to composition of suspending functions.
-
-### Sequential by default
-
-Assume that we have two suspending functions defined elsewhere that do something useful like some kind of
-remote service call or computation. We just pretend they are useful, but actually each one just
-delays for a second for the purpose of this example:
-
-<div class="sample" markdown="1" theme="idea" data-highlight-only>
-
-```kotlin
-suspend fun doSomethingUsefulOne(): Int {
- delay(1000L) // pretend we are doing something useful here
- return 13
-}
-
-suspend fun doSomethingUsefulTwo(): Int {
- delay(1000L) // pretend we are doing something useful here, too
- return 29
-}
-```
-
-</div>
-
-
-What do we do if we need them to be invoked _sequentially_ &mdash; first `doSomethingUsefulOne` _and then_
-`doSomethingUsefulTwo`, and compute the sum of their results?
-In practice we do this if we use the result of the first function to make a decision on whether we need
-to invoke the second one or to decide on how to invoke it.
-
-We use a normal sequential invocation, because the code in the coroutine, just like in the regular
-code, is _sequential_ by default. The following example demonstrates it by measuring the total
-time it takes to execute both suspending functions:
-
-<!--- CLEAR -->
-
-<div class="sample" markdown="1" theme="idea" data-min-compiler-version="1.3">
-
-```kotlin
-import kotlinx.coroutines.*
-import kotlin.system.*
-
-fun main() = runBlocking<Unit> {
-//sampleStart
- val time = measureTimeMillis {
- val one = doSomethingUsefulOne()
- val two = doSomethingUsefulTwo()
- println("The answer is ${one + two}")
- }
- println("Completed in $time ms")
-//sampleEnd
-}
-
-suspend fun doSomethingUsefulOne(): Int {
- delay(1000L) // pretend we are doing something useful here
- return 13
-}
-
-suspend fun doSomethingUsefulTwo(): Int {
- delay(1000L) // pretend we are doing something useful here, too
- return 29
-}
-```
-
-</div>
-
-> You can get the full code [here](../kotlinx-coroutines-core/jvm/test/guide/example-compose-01.kt).
-
-It produces something like this:
-
-```text
-The answer is 42
-Completed in 2017 ms
-```
-
-<!--- TEST ARBITRARY_TIME -->
-
-### Concurrent using async
-
-What if there are no dependencies between invocations of `doSomethingUsefulOne` and `doSomethingUsefulTwo` and
-we want to get the answer faster, by doing both _concurrently_? This is where [async] comes to help.
-
-Conceptually, [async] is just like [launch]. It starts a separate coroutine which is a light-weight thread
-that works concurrently with all the other coroutines. The difference is that `launch` returns a [Job] and
-does not carry any resulting value, while `async` returns a [Deferred] &mdash; a light-weight non-blocking future
-that represents a promise to provide a result later. You can use `.await()` on a deferred value to get its eventual result,
-but `Deferred` is also a `Job`, so you can cancel it if needed.
-
-
-<div class="sample" markdown="1" theme="idea" data-min-compiler-version="1.3">
-
-```kotlin
-import kotlinx.coroutines.*
-import kotlin.system.*
-
-fun main() = runBlocking<Unit> {
-//sampleStart
- val time = measureTimeMillis {
- val one = async { doSomethingUsefulOne() }
- val two = async { doSomethingUsefulTwo() }
- println("The answer is ${one.await() + two.await()}")
- }
- println("Completed in $time ms")
-//sampleEnd
-}
-
-suspend fun doSomethingUsefulOne(): Int {
- delay(1000L) // pretend we are doing something useful here
- return 13
-}
-
-suspend fun doSomethingUsefulTwo(): Int {
- delay(1000L) // pretend we are doing something useful here, too
- return 29
-}
-```
-
-</div>
-
-> You can get the full code [here](../kotlinx-coroutines-core/jvm/test/guide/example-compose-02.kt).
-
-It produces something like this:
-
-```text
-The answer is 42
-Completed in 1017 ms
-```
-
-<!--- TEST ARBITRARY_TIME -->
-
-This is twice as fast, because the two coroutines execute concurrently.
-Note that concurrency with coroutines is always explicit.
-
-### Lazily started async
-
-Optionally, [async] can be made lazy by setting its `start` parameter to [CoroutineStart.LAZY].
-In this mode it only starts the coroutine when its result is required by
-[await][Deferred.await], or if its `Job`'s [start][Job.start] function
-is invoked. Run the following example:
-
-<div class="sample" markdown="1" theme="idea" data-min-compiler-version="1.3">
-
-```kotlin
-import kotlinx.coroutines.*
-import kotlin.system.*
-
-fun main() = runBlocking<Unit> {
-//sampleStart
- val time = measureTimeMillis {
- val one = async(start = CoroutineStart.LAZY) { doSomethingUsefulOne() }
- val two = async(start = CoroutineStart.LAZY) { doSomethingUsefulTwo() }
- // some computation
- one.start() // start the first one
- two.start() // start the second one
- println("The answer is ${one.await() + two.await()}")
- }
- println("Completed in $time ms")
-//sampleEnd
-}
-
-suspend fun doSomethingUsefulOne(): Int {
- delay(1000L) // pretend we are doing something useful here
- return 13
-}
-
-suspend fun doSomethingUsefulTwo(): Int {
- delay(1000L) // pretend we are doing something useful here, too
- return 29
-}
-```
-
-</div>
-
-> You can get the full code [here](../kotlinx-coroutines-core/jvm/test/guide/example-compose-03.kt).
-
-It produces something like this:
-
-```text
-The answer is 42
-Completed in 1017 ms
-```
-
-<!--- TEST ARBITRARY_TIME -->
-
-So, here the two coroutines are defined but not executed as in the previous example, but the control is given to
-the programmer on when exactly to start the execution by calling [start][Job.start]. We first
-start `one`, then start `two`, and then await for the individual coroutines to finish.
-
-Note that if we just call [await][Deferred.await] in `println` without first calling [start][Job.start] on individual
-coroutines, this will lead to sequential behavior, since [await][Deferred.await] starts the coroutine
-execution and waits for its finish, which is not the intended use-case for laziness.
-The use-case for `async(start = CoroutineStart.LAZY)` is a replacement for the
-standard `lazy` function in cases when computation of the value involves suspending functions.
-
-### Async-style functions
-
-We can define async-style functions that invoke `doSomethingUsefulOne` and `doSomethingUsefulTwo`
-_asynchronously_ using the [async] coroutine builder with an explicit [GlobalScope] reference.
-We name such functions with the
-"...Async" suffix to highlight the fact that they only start asynchronous computation and one needs
-to use the resulting deferred value to get the result.
-
-<div class="sample" markdown="1" theme="idea" data-highlight-only>
-
-```kotlin
-// The result type of somethingUsefulOneAsync is Deferred<Int>
-fun somethingUsefulOneAsync() = GlobalScope.async {
- doSomethingUsefulOne()
-}
-
-// The result type of somethingUsefulTwoAsync is Deferred<Int>
-fun somethingUsefulTwoAsync() = GlobalScope.async {
- doSomethingUsefulTwo()
-}
-```
-
-</div>
-
-Note that these `xxxAsync` functions are **not** _suspending_ functions. They can be used from anywhere.
-However, their use always implies asynchronous (here meaning _concurrent_) execution of their action
-with the invoking code.
-
-The following example shows their use outside of coroutine:
-
-<!--- CLEAR -->
-
-<div class="sample" markdown="1" theme="idea" data-min-compiler-version="1.3">
-
-```kotlin
-import kotlinx.coroutines.*
-import kotlin.system.*
-
-//sampleStart
-// note that we don't have `runBlocking` to the right of `main` in this example
-fun main() {
- val time = measureTimeMillis {
- // we can initiate async actions outside of a coroutine
- val one = somethingUsefulOneAsync()
- val two = somethingUsefulTwoAsync()
- // but waiting for a result must involve either suspending or blocking.
- // here we use `runBlocking { ... }` to block the main thread while waiting for the result
- runBlocking {
- println("The answer is ${one.await() + two.await()}")
- }
- }
- println("Completed in $time ms")
-}
-//sampleEnd
-
-fun somethingUsefulOneAsync() = GlobalScope.async {
- doSomethingUsefulOne()
-}
-
-fun somethingUsefulTwoAsync() = GlobalScope.async {
- doSomethingUsefulTwo()
-}
-
-suspend fun doSomethingUsefulOne(): Int {
- delay(1000L) // pretend we are doing something useful here
- return 13
-}
-
-suspend fun doSomethingUsefulTwo(): Int {
- delay(1000L) // pretend we are doing something useful here, too
- return 29
-}
-```
-
-</div>
-
-> You can get the full code [here](../kotlinx-coroutines-core/jvm/test/guide/example-compose-04.kt).
-
-<!--- TEST ARBITRARY_TIME
-The answer is 42
-Completed in 1085 ms
--->
-
-> This programming style with async functions is provided here only for illustration, because it is a popular style
-in other programming languages. Using this style with Kotlin coroutines is **strongly discouraged** for the
-reasons explained below.
-
-Consider what happens if between the `val one = somethingUsefulOneAsync()` line and `one.await()` expression there is some logic
-error in the code and the program throws an exception and the operation that was being performed by the program aborts.
-Normally, a global error-handler could catch this exception, log and report the error for developers, but the program
-could otherwise continue doing other operations. But here we have `somethingUsefulOneAsync` still running in the background,
-even though the operation that initiated it was aborted. This problem does not happen with structured
-concurrency, as shown in the section below.
-
-### Structured concurrency with async
-
-Let us take the [Concurrent using async](#concurrent-using-async) example and extract a function that
-concurrently performs `doSomethingUsefulOne` and `doSomethingUsefulTwo` and returns the sum of their results.
-Because the [async] coroutine builder is defined as an extension on [CoroutineScope], we need to have it in the
-scope and that is what the [coroutineScope][_coroutineScope] function provides:
-
-<div class="sample" markdown="1" theme="idea" data-highlight-only>
-
-```kotlin
-suspend fun concurrentSum(): Int = coroutineScope {
- val one = async { doSomethingUsefulOne() }
- val two = async { doSomethingUsefulTwo() }
- one.await() + two.await()
-}
-```
-
-</div>
-
-This way, if something goes wrong inside the code of the `concurrentSum` function and it throws an exception,
-all the coroutines that were launched in its scope will be cancelled.
-
-<!--- CLEAR -->
-
-<div class="sample" markdown="1" theme="idea" data-min-compiler-version="1.3">
-
-```kotlin
-import kotlinx.coroutines.*
-import kotlin.system.*
-
-fun main() = runBlocking<Unit> {
-//sampleStart
- val time = measureTimeMillis {
- println("The answer is ${concurrentSum()}")
- }
- println("Completed in $time ms")
-//sampleEnd
-}
-
-suspend fun concurrentSum(): Int = coroutineScope {
- val one = async { doSomethingUsefulOne() }
- val two = async { doSomethingUsefulTwo() }
- one.await() + two.await()
-}
-
-suspend fun doSomethingUsefulOne(): Int {
- delay(1000L) // pretend we are doing something useful here
- return 13
-}
-
-suspend fun doSomethingUsefulTwo(): Int {
- delay(1000L) // pretend we are doing something useful here, too
- return 29
-}
-```
-
-</div>
-
-> You can get the full code [here](../kotlinx-coroutines-core/jvm/test/guide/example-compose-05.kt).
-
-We still have concurrent execution of both operations, as evident from the output of the above `main` function:
-
-```text
-The answer is 42
-Completed in 1017 ms
-```
-
-<!--- TEST ARBITRARY_TIME -->
-
-Cancellation is always propagated through coroutines hierarchy:
-
-<!--- CLEAR -->
-
-<div class="sample" markdown="1" theme="idea" data-min-compiler-version="1.3">
-
-```kotlin
-import kotlinx.coroutines.*
-
-fun main() = runBlocking<Unit> {
- try {
- failedConcurrentSum()
- } catch(e: ArithmeticException) {
- println("Computation failed with ArithmeticException")
- }
-}
-
-suspend fun failedConcurrentSum(): Int = coroutineScope {
- val one = async<Int> {
- try {
- delay(Long.MAX_VALUE) // Emulates very long computation
- 42
- } finally {
- println("First child was cancelled")
- }
- }
- val two = async<Int> {
- println("Second child throws an exception")
- throw ArithmeticException()
- }
- one.await() + two.await()
-}
-```
-
-</div>
-
-> You can get the full code [here](../kotlinx-coroutines-core/jvm/test/guide/example-compose-06.kt).
-
-Note how both the first `async` and the awaiting parent are cancelled on failure of one of the children
-(namely, `two`):
-```text
-Second child throws an exception
-First child was cancelled
-Computation failed with ArithmeticException
-```
-
-<!--- TEST -->
-
-<!--- MODULE kotlinx-coroutines-core -->
-<!--- INDEX kotlinx.coroutines -->
-[async]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/async.html
-[launch]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/launch.html
-[Job]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job/index.html
-[Deferred]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-deferred/index.html
-[CoroutineStart.LAZY]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-start/-l-a-z-y.html
-[Deferred.await]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-deferred/await.html
-[Job.start]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job/start.html
-[GlobalScope]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-global-scope/index.html
-[CoroutineScope]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-scope/index.html
-[_coroutineScope]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/coroutine-scope.html
-<!--- END -->
+To edit the documentation, open the [topics/composing-suspending-functions.md](topics/composing-suspending-functions.md) page. \ No newline at end of file
diff --git a/docs/coroutine-context-and-dispatchers.md b/docs/coroutine-context-and-dispatchers.md
index 36e049db..f8ba1283 100644
--- a/docs/coroutine-context-and-dispatchers.md
+++ b/docs/coroutine-context-and-dispatchers.md
@@ -1,712 +1,3 @@
-<!--- TEST_NAME DispatcherGuideTest -->
+The documentation has been moved to the [https://kotlinlang.org/docs/coroutine-context-and-dispatchers.html](https://kotlinlang.org/docs/coroutine-context-and-dispatchers.html) page.
-**Table of contents**
-
-<!--- TOC -->
-
-* [Coroutine Context and Dispatchers](#coroutine-context-and-dispatchers)
- * [Dispatchers and threads](#dispatchers-and-threads)
- * [Unconfined vs confined dispatcher](#unconfined-vs-confined-dispatcher)
- * [Debugging coroutines and threads](#debugging-coroutines-and-threads)
- * [Debugging with IDEA](#debugging-with-idea)
- * [Debugging using logging](#debugging-using-logging)
- * [Jumping between threads](#jumping-between-threads)
- * [Job in the context](#job-in-the-context)
- * [Children of a coroutine](#children-of-a-coroutine)
- * [Parental responsibilities](#parental-responsibilities)
- * [Naming coroutines for debugging](#naming-coroutines-for-debugging)
- * [Combining context elements](#combining-context-elements)
- * [Coroutine scope](#coroutine-scope)
- * [Thread-local data](#thread-local-data)
-
-<!--- END -->
-
-## Coroutine Context and Dispatchers
-
-Coroutines always execute in some context represented by a value of the
-[CoroutineContext](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.coroutines/-coroutine-context/)
-type, defined in the Kotlin standard library.
-
-The coroutine context is a set of various elements. The main elements are the [Job] of the coroutine,
-which we've seen before, and its dispatcher, which is covered in this section.
-
-### Dispatchers and threads
-
-The coroutine context includes a _coroutine dispatcher_ (see [CoroutineDispatcher]) that determines what thread or threads
-the corresponding coroutine uses for its execution. The coroutine dispatcher can confine coroutine execution
-to a specific thread, dispatch it to a thread pool, or let it run unconfined.
-
-All coroutine builders like [launch] and [async] accept an optional
-[CoroutineContext](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.coroutines/-coroutine-context/)
-parameter that can be used to explicitly specify the dispatcher for the new coroutine and other context elements.
-
-Try the following example:
-
-<div class="sample" markdown="1" theme="idea" data-min-compiler-version="1.3">
-
-```kotlin
-import kotlinx.coroutines.*
-
-fun main() = runBlocking<Unit> {
-//sampleStart
- launch { // context of the parent, main runBlocking coroutine
- println("main runBlocking : I'm working in thread ${Thread.currentThread().name}")
- }
- launch(Dispatchers.Unconfined) { // not confined -- will work with main thread
- println("Unconfined : I'm working in thread ${Thread.currentThread().name}")
- }
- launch(Dispatchers.Default) { // will get dispatched to DefaultDispatcher
- println("Default : I'm working in thread ${Thread.currentThread().name}")
- }
- launch(newSingleThreadContext("MyOwnThread")) { // will get its own new thread
- println("newSingleThreadContext: I'm working in thread ${Thread.currentThread().name}")
- }
-//sampleEnd
-}
-```
-
-</div>
-
-> You can get the full code [here](../kotlinx-coroutines-core/jvm/test/guide/example-context-01.kt).
-
-It produces the following output (maybe in different order):
-
-```text
-Unconfined : I'm working in thread main
-Default : I'm working in thread DefaultDispatcher-worker-1
-newSingleThreadContext: I'm working in thread MyOwnThread
-main runBlocking : I'm working in thread main
-```
-
-<!--- TEST LINES_START_UNORDERED -->
-
-When `launch { ... }` is used without parameters, it inherits the context (and thus dispatcher)
-from the [CoroutineScope] it is being launched from. In this case, it inherits the
-context of the main `runBlocking` coroutine which runs in the `main` thread.
-
-[Dispatchers.Unconfined] is a special dispatcher that also appears to run in the `main` thread, but it is,
-in fact, a different mechanism that is explained later.
-
-The default dispatcher that is used when coroutines are launched in [GlobalScope]
-is represented by [Dispatchers.Default] and uses a shared background pool of threads,
-so `launch(Dispatchers.Default) { ... }` uses the same dispatcher as `GlobalScope.launch { ... }`.
-
-[newSingleThreadContext] creates a thread for the coroutine to run.
-A dedicated thread is a very expensive resource.
-In a real application it must be either released, when no longer needed, using the [close][ExecutorCoroutineDispatcher.close]
-function, or stored in a top-level variable and reused throughout the application.
-
-### Unconfined vs confined dispatcher
-
-The [Dispatchers.Unconfined] coroutine dispatcher starts a coroutine in the caller thread, but only until the
-first suspension point. After suspension it resumes the coroutine in the thread that is fully determined by the
-suspending function that was invoked. The unconfined dispatcher is appropriate for coroutines which neither
-consume CPU time nor update any shared data (like UI) confined to a specific thread.
-
-On the other side, the dispatcher is inherited from the outer [CoroutineScope] by default.
-The default dispatcher for the [runBlocking] coroutine, in particular,
-is confined to the invoker thread, so inheriting it has the effect of confining execution to
-this thread with predictable FIFO scheduling.
-
-<div class="sample" markdown="1" theme="idea" data-min-compiler-version="1.3">
-
-```kotlin
-import kotlinx.coroutines.*
-
-fun main() = runBlocking<Unit> {
-//sampleStart
- launch(Dispatchers.Unconfined) { // not confined -- will work with main thread
- println("Unconfined : I'm working in thread ${Thread.currentThread().name}")
- delay(500)
- println("Unconfined : After delay in thread ${Thread.currentThread().name}")
- }
- launch { // context of the parent, main runBlocking coroutine
- println("main runBlocking: I'm working in thread ${Thread.currentThread().name}")
- delay(1000)
- println("main runBlocking: After delay in thread ${Thread.currentThread().name}")
- }
-//sampleEnd
-}
-```
-
-</div>
-
-> You can get the full code [here](../kotlinx-coroutines-core/jvm/test/guide/example-context-02.kt).
-
-Produces the output:
-
-```text
-Unconfined : I'm working in thread main
-main runBlocking: I'm working in thread main
-Unconfined : After delay in thread kotlinx.coroutines.DefaultExecutor
-main runBlocking: After delay in thread main
-```
-
-<!--- TEST LINES_START -->
-
-So, the coroutine with the context inherited from `runBlocking {...}` continues to execute
-in the `main` thread, while the unconfined one resumes in the default executor thread that the [delay]
-function is using.
-
-> The unconfined dispatcher is an advanced mechanism that can be helpful in certain corner cases where
-dispatching of a coroutine for its execution later is not needed or produces undesirable side-effects,
-because some operation in a coroutine must be performed right away.
-The unconfined dispatcher should not be used in general code.
-
-### Debugging coroutines and threads
-
-Coroutines can suspend on one thread and resume on another thread.
-Even with a single-threaded dispatcher it might be hard to
-figure out what the coroutine was doing, where, and when if you don't have special tooling.
-
-#### Debugging with IDEA
-
-The Coroutine Debugger of the Kotlin plugin simplifies debugging coroutines in IntelliJ IDEA.
-
-> Debugging works for versions 1.3.8 or later of `kotlinx-coroutines-core`.
-
-The **Debug** tool window contains the **Coroutines** tab. In this tab, you can find information about both currently running and suspended coroutines.
-The coroutines are grouped by the dispatcher they are running on.
-
-![Debugging coroutines](images/coroutine-idea-debugging-1.png)
-
-With the coroutine debugger, you can:
-* Check the state of each coroutine.
-* See the values of local and captured variables for both running and suspended coroutines.
-* See a full coroutine creation stack, as well as a call stack inside the coroutine. The stack includes all frames with
-variable values, even those that would be lost during standard debugging.
-* Get a full report that contains the state of each coroutine and its stack. To obtain it, right-click inside the **Coroutines** tab, and then click **Get Coroutines Dump**.
-
-To start coroutine debugging, you just need to set breakpoints and run the application in debug mode.
-
-Learn more about coroutines debugging in the [tutorial](https://kotlinlang.org/docs/tutorials/coroutines/debug-coroutines-with-idea.html).
-
-#### Debugging using logging
-
-Another approach to debugging applications with
-threads without Coroutine Debugger is to print the thread name in the log file on each log statement. This feature is universally supported
-by logging frameworks. When using coroutines, the thread name alone does not give much of a context, so
-`kotlinx.coroutines` includes debugging facilities to make it easier.
-
-Run the following code with `-Dkotlinx.coroutines.debug` JVM option:
-
-<div class="sample" markdown="1" theme="idea" data-min-compiler-version="1.3">
-
-```kotlin
-import kotlinx.coroutines.*
-
-fun log(msg: String) = println("[${Thread.currentThread().name}] $msg")
-
-fun main() = runBlocking<Unit> {
-//sampleStart
- val a = async {
- log("I'm computing a piece of the answer")
- 6
- }
- val b = async {
- log("I'm computing another piece of the answer")
- 7
- }
- log("The answer is ${a.await() * b.await()}")
-//sampleEnd
-}
-```
-
-</div>
-
-> You can get the full code [here](../kotlinx-coroutines-core/jvm/test/guide/example-context-03.kt).
-
-There are three coroutines. The main coroutine (#1) inside `runBlocking`
-and two coroutines computing the deferred values `a` (#2) and `b` (#3).
-They are all executing in the context of `runBlocking` and are confined to the main thread.
-The output of this code is:
-
-```text
-[main @coroutine#2] I'm computing a piece of the answer
-[main @coroutine#3] I'm computing another piece of the answer
-[main @coroutine#1] The answer is 42
-```
-
-<!--- TEST FLEXIBLE_THREAD -->
-
-The `log` function prints the name of the thread in square brackets, and you can see that it is the `main`
-thread with the identifier of the currently executing coroutine appended to it. This identifier
-is consecutively assigned to all created coroutines when the debugging mode is on.
-
-> Debugging mode is also turned on when JVM is run with `-ea` option.
-You can read more about debugging facilities in the documentation of the [DEBUG_PROPERTY_NAME] property.
-
-### Jumping between threads
-
-Run the following code with the `-Dkotlinx.coroutines.debug` JVM option (see [debug](#debugging-coroutines-and-threads)):
-
-<div class="sample" markdown="1" theme="idea" data-min-compiler-version="1.3">
-
-```kotlin
-import kotlinx.coroutines.*
-
-fun log(msg: String) = println("[${Thread.currentThread().name}] $msg")
-
-fun main() {
-//sampleStart
- newSingleThreadContext("Ctx1").use { ctx1 ->
- newSingleThreadContext("Ctx2").use { ctx2 ->
- runBlocking(ctx1) {
- log("Started in ctx1")
- withContext(ctx2) {
- log("Working in ctx2")
- }
- log("Back to ctx1")
- }
- }
- }
-//sampleEnd
-}
-```
-
-</div>
-
-> You can get the full code [here](../kotlinx-coroutines-core/jvm/test/guide/example-context-04.kt).
-
-It demonstrates several new techniques. One is using [runBlocking] with an explicitly specified context, and
-the other one is using the [withContext] function to change the context of a coroutine while still staying in the
-same coroutine, as you can see in the output below:
-
-```text
-[Ctx1 @coroutine#1] Started in ctx1
-[Ctx2 @coroutine#1] Working in ctx2
-[Ctx1 @coroutine#1] Back to ctx1
-```
-
-<!--- TEST -->
-
-Note that this example also uses the `use` function from the Kotlin standard library to release threads
-created with [newSingleThreadContext] when they are no longer needed.
-
-### Job in the context
-
-The coroutine's [Job] is part of its context, and can be retrieved from it
-using the `coroutineContext[Job]` expression:
-
-<div class="sample" markdown="1" theme="idea" data-min-compiler-version="1.3">
-
-```kotlin
-import kotlinx.coroutines.*
-
-fun main() = runBlocking<Unit> {
-//sampleStart
- println("My job is ${coroutineContext[Job]}")
-//sampleEnd
-}
-```
-
-</div>
-
-> You can get the full code [here](../kotlinx-coroutines-core/jvm/test/guide/example-context-05.kt).
-
-In the [debug mode](#debugging-coroutines-and-threads), it outputs something like this:
-
-```
-My job is "coroutine#1":BlockingCoroutine{Active}@6d311334
-```
-
-<!--- TEST lines.size == 1 && lines[0].startsWith("My job is \"coroutine#1\":BlockingCoroutine{Active}@") -->
-
-Note that [isActive] in [CoroutineScope] is just a convenient shortcut for
-`coroutineContext[Job]?.isActive == true`.
-
-### Children of a coroutine
-
-When a coroutine is launched in the [CoroutineScope] of another coroutine,
-it inherits its context via [CoroutineScope.coroutineContext] and
-the [Job] of the new coroutine becomes
-a _child_ of the parent coroutine's job. When the parent coroutine is cancelled, all its children
-are recursively cancelled, too.
-
-However, when [GlobalScope] is used to launch a coroutine, there is no parent for the job of the new coroutine.
-It is therefore not tied to the scope it was launched from and operates independently.
-
-
-<div class="sample" markdown="1" theme="idea" data-min-compiler-version="1.3">
-
-```kotlin
-import kotlinx.coroutines.*
-
-fun main() = runBlocking<Unit> {
-//sampleStart
- // launch a coroutine to process some kind of incoming request
- val request = launch {
- // it spawns two other jobs, one with GlobalScope
- GlobalScope.launch {
- println("job1: I run in GlobalScope and execute independently!")
- delay(1000)
- println("job1: I am not affected by cancellation of the request")
- }
- // and the other inherits the parent context
- launch {
- delay(100)
- println("job2: I am a child of the request coroutine")
- delay(1000)
- println("job2: I will not execute this line if my parent request is cancelled")
- }
- }
- delay(500)
- request.cancel() // cancel processing of the request
- delay(1000) // delay a second to see what happens
- println("main: Who has survived request cancellation?")
-//sampleEnd
-}
-```
-
-</div>
-
-> You can get the full code [here](../kotlinx-coroutines-core/jvm/test/guide/example-context-06.kt).
-
-The output of this code is:
-
-```text
-job1: I run in GlobalScope and execute independently!
-job2: I am a child of the request coroutine
-job1: I am not affected by cancellation of the request
-main: Who has survived request cancellation?
-```
-
-<!--- TEST -->
-
-### Parental responsibilities
-
-A parent coroutine always waits for completion of all its children. A parent does not have to explicitly track
-all the children it launches, and it does not have to use [Job.join] to wait for them at the end:
-
-<div class="sample" markdown="1" theme="idea" data-min-compiler-version="1.3">
-
-```kotlin
-import kotlinx.coroutines.*
-
-fun main() = runBlocking<Unit> {
-//sampleStart
- // launch a coroutine to process some kind of incoming request
- val request = launch {
- repeat(3) { i -> // launch a few children jobs
- launch {
- delay((i + 1) * 200L) // variable delay 200ms, 400ms, 600ms
- println("Coroutine $i is done")
- }
- }
- println("request: I'm done and I don't explicitly join my children that are still active")
- }
- request.join() // wait for completion of the request, including all its children
- println("Now processing of the request is complete")
-//sampleEnd
-}
-```
-
-</div>
-
-> You can get the full code [here](../kotlinx-coroutines-core/jvm/test/guide/example-context-07.kt).
-
-The result is going to be:
-
-```text
-request: I'm done and I don't explicitly join my children that are still active
-Coroutine 0 is done
-Coroutine 1 is done
-Coroutine 2 is done
-Now processing of the request is complete
-```
-
-<!--- TEST -->
-
-### Naming coroutines for debugging
-
-Automatically assigned ids are good when coroutines log often and you just need to correlate log records
-coming from the same coroutine. However, when a coroutine is tied to the processing of a specific request
-or doing some specific background task, it is better to name it explicitly for debugging purposes.
-The [CoroutineName] context element serves the same purpose as the thread name. It is included in the thread name that
-is executing this coroutine when the [debugging mode](#debugging-coroutines-and-threads) is turned on.
-
-The following example demonstrates this concept:
-
-<div class="sample" markdown="1" theme="idea" data-min-compiler-version="1.3">
-
-```kotlin
-import kotlinx.coroutines.*
-
-fun log(msg: String) = println("[${Thread.currentThread().name}] $msg")
-
-fun main() = runBlocking(CoroutineName("main")) {
-//sampleStart
- log("Started main coroutine")
- // run two background value computations
- val v1 = async(CoroutineName("v1coroutine")) {
- delay(500)
- log("Computing v1")
- 252
- }
- val v2 = async(CoroutineName("v2coroutine")) {
- delay(1000)
- log("Computing v2")
- 6
- }
- log("The answer for v1 / v2 = ${v1.await() / v2.await()}")
-//sampleEnd
-}
-```
-
-</div>
-
-> You can get the full code [here](../kotlinx-coroutines-core/jvm/test/guide/example-context-08.kt).
-
-The output it produces with `-Dkotlinx.coroutines.debug` JVM option is similar to:
-
-```text
-[main @main#1] Started main coroutine
-[main @v1coroutine#2] Computing v1
-[main @v2coroutine#3] Computing v2
-[main @main#1] The answer for v1 / v2 = 42
-```
-
-<!--- TEST FLEXIBLE_THREAD -->
-
-### Combining context elements
-
-Sometimes we need to define multiple elements for a coroutine context. We can use the `+` operator for that.
-For example, we can launch a coroutine with an explicitly specified dispatcher and an explicitly specified
-name at the same time:
-
-<div class="sample" markdown="1" theme="idea" data-min-compiler-version="1.3">
-
-```kotlin
-import kotlinx.coroutines.*
-
-fun main() = runBlocking<Unit> {
-//sampleStart
- launch(Dispatchers.Default + CoroutineName("test")) {
- println("I'm working in thread ${Thread.currentThread().name}")
- }
-//sampleEnd
-}
-```
-
-</div>
-
-> You can get the full code [here](../kotlinx-coroutines-core/jvm/test/guide/example-context-09.kt).
-
-The output of this code with the `-Dkotlinx.coroutines.debug` JVM option is:
-
-```text
-I'm working in thread DefaultDispatcher-worker-1 @test#2
-```
-
-<!--- TEST FLEXIBLE_THREAD -->
-
-### Coroutine scope
-
-Let us put our knowledge about contexts, children and jobs together. Assume that our application has
-an object with a lifecycle, but that object is not a coroutine. For example, we are writing an Android application
-and launch various coroutines in the context of an Android activity to perform asynchronous operations to fetch
-and update data, do animations, etc. All of these coroutines must be cancelled when the activity is destroyed
-to avoid memory leaks. We, of course, can manipulate contexts and jobs manually to tie the lifecycles of the activity
-and its coroutines, but `kotlinx.coroutines` provides an abstraction encapsulating that: [CoroutineScope].
-You should be already familiar with the coroutine scope as all coroutine builders are declared as extensions on it.
-
-We manage the lifecycles of our coroutines by creating an instance of [CoroutineScope] tied to
-the lifecycle of our activity. A `CoroutineScope` instance can be created by the [CoroutineScope()] or [MainScope()]
-factory functions. The former creates a general-purpose scope, while the latter creates a scope for UI applications and uses
-[Dispatchers.Main] as the default dispatcher:
-
-<div class="sample" markdown="1" theme="idea" data-highlight-only>
-
-```kotlin
-class Activity {
- private val mainScope = MainScope()
-
- fun destroy() {
- mainScope.cancel()
- }
- // to be continued ...
-```
-
-</div>
-
-Now, we can launch coroutines in the scope of this `Activity` using the defined `scope`.
-For the demo, we launch ten coroutines that delay for a different time:
-
-<div class="sample" markdown="1" theme="idea" data-highlight-only>
-
-```kotlin
- // class Activity continues
- fun doSomething() {
- // launch ten coroutines for a demo, each working for a different time
- repeat(10) { i ->
- mainScope.launch {
- delay((i + 1) * 200L) // variable delay 200ms, 400ms, ... etc
- println("Coroutine $i is done")
- }
- }
- }
-} // class Activity ends
-```
-
-</div>
-
-In our main function we create the activity, call our test `doSomething` function, and destroy the activity after 500ms.
-This cancels all the coroutines that were launched from `doSomething`. We can see that because after the destruction
-of the activity no more messages are printed, even if we wait a little longer.
-
-<!--- CLEAR -->
-
-<div class="sample" markdown="1" theme="idea" data-min-compiler-version="1.3">
-
-```kotlin
-import kotlinx.coroutines.*
-
-class Activity {
- private val mainScope = CoroutineScope(Dispatchers.Default) // use Default for test purposes
-
- fun destroy() {
- mainScope.cancel()
- }
-
- fun doSomething() {
- // launch ten coroutines for a demo, each working for a different time
- repeat(10) { i ->
- mainScope.launch {
- delay((i + 1) * 200L) // variable delay 200ms, 400ms, ... etc
- println("Coroutine $i is done")
- }
- }
- }
-} // class Activity ends
-
-fun main() = runBlocking<Unit> {
-//sampleStart
- val activity = Activity()
- activity.doSomething() // run test function
- println("Launched coroutines")
- delay(500L) // delay for half a second
- println("Destroying activity!")
- activity.destroy() // cancels all coroutines
- delay(1000) // visually confirm that they don't work
-//sampleEnd
-}
-```
-
-</div>
-
-> You can get the full code [here](../kotlinx-coroutines-core/jvm/test/guide/example-context-10.kt).
-
-The output of this example is:
-
-```text
-Launched coroutines
-Coroutine 0 is done
-Coroutine 1 is done
-Destroying activity!
-```
-
-<!--- TEST -->
-
-As you can see, only the first two coroutines print a message and the others are cancelled
-by a single invocation of `job.cancel()` in `Activity.destroy()`.
-
-> Note, that Android has first-party support for coroutine scope in all entities with the lifecycle.
-See [the corresponding documentation](https://developer.android.com/topic/libraries/architecture/coroutines#lifecyclescope).
-
-### Thread-local data
-
-Sometimes it is convenient to have an ability to pass some thread-local data to or between coroutines.
-However, since they are not bound to any particular thread, this will likely lead to boilerplate if done manually.
-
-For [`ThreadLocal`](https://docs.oracle.com/javase/8/docs/api/java/lang/ThreadLocal.html),
-the [asContextElement] extension function is here for the rescue. It creates an additional context element
-which keeps the value of the given `ThreadLocal` and restores it every time the coroutine switches its context.
-
-It is easy to demonstrate it in action:
-
-<div class="sample" markdown="1" theme="idea" data-min-compiler-version="1.3">
-
-```kotlin
-import kotlinx.coroutines.*
-
-val threadLocal = ThreadLocal<String?>() // declare thread-local variable
-
-fun main() = runBlocking<Unit> {
-//sampleStart
- threadLocal.set("main")
- println("Pre-main, current thread: ${Thread.currentThread()}, thread local value: '${threadLocal.get()}'")
- val job = launch(Dispatchers.Default + threadLocal.asContextElement(value = "launch")) {
- println("Launch start, current thread: ${Thread.currentThread()}, thread local value: '${threadLocal.get()}'")
- yield()
- println("After yield, current thread: ${Thread.currentThread()}, thread local value: '${threadLocal.get()}'")
- }
- job.join()
- println("Post-main, current thread: ${Thread.currentThread()}, thread local value: '${threadLocal.get()}'")
-//sampleEnd
-}
-```
-
-</div>
-
-> You can get the full code [here](../kotlinx-coroutines-core/jvm/test/guide/example-context-11.kt).
-
-In this example we launch a new coroutine in a background thread pool using [Dispatchers.Default], so
-it works on a different thread from the thread pool, but it still has the value of the thread local variable
-that we specified using `threadLocal.asContextElement(value = "launch")`,
-no matter which thread the coroutine is executed on.
-Thus, the output (with [debug](#debugging-coroutines-and-threads)) is:
-
-```text
-Pre-main, current thread: Thread[main @coroutine#1,5,main], thread local value: 'main'
-Launch start, current thread: Thread[DefaultDispatcher-worker-1 @coroutine#2,5,main], thread local value: 'launch'
-After yield, current thread: Thread[DefaultDispatcher-worker-2 @coroutine#2,5,main], thread local value: 'launch'
-Post-main, current thread: Thread[main @coroutine#1,5,main], thread local value: 'main'
-```
-
-<!--- TEST FLEXIBLE_THREAD -->
-
-It's easy to forget to set the corresponding context element. The thread-local variable accessed from the coroutine may
-then have an unexpected value, if the thread running the coroutine is different.
-To avoid such situations, it is recommended to use the [ensurePresent] method
-and fail-fast on improper usages.
-
-`ThreadLocal` has first-class support and can be used with any primitive `kotlinx.coroutines` provides.
-It has one key limitation, though: when a thread-local is mutated, a new value is not propagated to the coroutine caller
-(because a context element cannot track all `ThreadLocal` object accesses), and the updated value is lost on the next suspension.
-Use [withContext] to update the value of the thread-local in a coroutine, see [asContextElement] for more details.
-
-Alternatively, a value can be stored in a mutable box like `class Counter(var i: Int)`, which is, in turn,
-stored in a thread-local variable. However, in this case you are fully responsible to synchronize
-potentially concurrent modifications to the variable in this mutable box.
-
-For advanced usage, for example for integration with logging MDC, transactional contexts or any other libraries
-which internally use thread-locals for passing data, see the documentation of the [ThreadContextElement] interface
-that should be implemented.
-
-<!--- MODULE kotlinx-coroutines-core -->
-<!--- INDEX kotlinx.coroutines -->
-[Job]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job/index.html
-[CoroutineDispatcher]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-dispatcher/index.html
-[launch]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/launch.html
-[async]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/async.html
-[CoroutineScope]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-scope/index.html
-[Dispatchers.Unconfined]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-dispatchers/-unconfined.html
-[GlobalScope]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-global-scope/index.html
-[Dispatchers.Default]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-dispatchers/-default.html
-[newSingleThreadContext]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/new-single-thread-context.html
-[ExecutorCoroutineDispatcher.close]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-executor-coroutine-dispatcher/close.html
-[runBlocking]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/run-blocking.html
-[delay]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/delay.html
-[DEBUG_PROPERTY_NAME]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-d-e-b-u-g_-p-r-o-p-e-r-t-y_-n-a-m-e.html
-[withContext]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/with-context.html
-[isActive]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/is-active.html
-[CoroutineScope.coroutineContext]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-scope/coroutine-context.html
-[Job.join]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job/join.html
-[CoroutineName]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-name/index.html
-[CoroutineScope()]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-scope.html
-[MainScope()]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-main-scope.html
-[Dispatchers.Main]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-dispatchers/-main.html
-[asContextElement]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/java.lang.-thread-local/as-context-element.html
-[ensurePresent]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/java.lang.-thread-local/ensure-present.html
-[ThreadContextElement]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-thread-context-element/index.html
-<!--- END -->
+To edit the documentation, open the [topics/coroutine-context-and-dispatchers.md](topics/coroutine-context-and-dispatchers.md) page. \ No newline at end of file
diff --git a/docs/coroutines-guide.md b/docs/coroutines-guide.md
index 2d15a7bb..e2caa750 100644
--- a/docs/coroutines-guide.md
+++ b/docs/coroutines-guide.md
@@ -1,32 +1,3 @@
+The documentation has been moved to the [https://kotlinlang.org/docs/coroutines-guide.html](https://kotlinlang.org/docs/coroutines-guide.html) page.
-Kotlin, as a language, provides only minimal low-level APIs in its standard library to enable various other
-libraries to utilize coroutines. Unlike many other languages with similar capabilities, `async` and `await`
-are not keywords in Kotlin and are not even part of its standard library. Moreover, Kotlin's concept
-of _suspending function_ provides a safer and less error-prone abstraction for asynchronous
-operations than futures and promises.
-
-`kotlinx.coroutines` is a rich library for coroutines developed by JetBrains. It contains a number of high-level
-coroutine-enabled primitives that this guide covers, including `launch`, `async` and others.
-
-This is a guide on core features of `kotlinx.coroutines` with a series of examples, divided up into different topics.
-
-In order to use coroutines as well as follow the examples in this guide, you need to add a dependency on the `kotlinx-coroutines-core` module as explained
-[in the project README](../README.md#using-in-your-projects).
-
-## Table of contents
-
-* [Basics](basics.md)
-* [Cancellation and Timeouts](cancellation-and-timeouts.md)
-* [Composing Suspending Functions](composing-suspending-functions.md)
-* [Coroutine Context and Dispatchers](coroutine-context-and-dispatchers.md)
-* [Asynchronous Flow](flow.md)
-* [Channels](channels.md)
-* [Exception Handling and Supervision](exception-handling.md)
-* [Shared Mutable State and Concurrency](shared-mutable-state-and-concurrency.md)
-* [Select Expression (experimental)](select-expression.md)
-
-## Additional references
-
-* [Guide to UI programming with coroutines](../ui/coroutines-guide-ui.md)
-* [Coroutines design document (KEEP)](https://github.com/Kotlin/kotlin-coroutines/blob/master/kotlin-coroutines-informal.md)
-* [Full kotlinx.coroutines API reference](https://kotlin.github.io/kotlinx.coroutines)
+To edit the documentation, open the [topics/coroutines-guide.md](topics/coroutines-guide.md) page. \ No newline at end of file
diff --git a/docs/debugging.md b/docs/debugging.md
index 6c846f23..a5dab630 100644
--- a/docs/debugging.md
+++ b/docs/debugging.md
@@ -1,106 +1 @@
-**Table of contents**
-
-<!--- TOC -->
-
-* [Debugging coroutines](#debugging-coroutines)
-* [Debug mode](#debug-mode)
-* [Stacktrace recovery](#stacktrace-recovery)
- * [Stacktrace recovery machinery](#stacktrace-recovery-machinery)
-* [Debug agent](#debug-agent)
- * [Debug agent and Android](#debug-agent-and-android)
-* [Android optimization](#android-optimization)
-
-<!--- END -->
-
-## Debugging coroutines
-
-Debugging asynchronous programs is challenging, because multiple concurrent coroutines are typically working at the same time.
-To help with that, `kotlinx.coroutines` comes with additional features for debugging: debug mode, stacktrace recovery
-and debug agent.
-
-## Debug mode
-
-The first debugging feature of `kotlinx.coroutines` is debug mode.
-It can be enabled either by setting system property [DEBUG_PROPERTY_NAME] or by running Java with enabled assertions (`-ea` flag).
-The latter is helpful to have debug mode enabled by default in unit tests.
-
-Debug mode attaches a unique [name][CoroutineName] to every launched coroutine.
-Coroutine name can be seen in a regular Java debugger,
-in a string representation of the coroutine or in the thread name executing named coroutine.
-Overhead of this feature is negligible and it can be safely turned on by default to simplify logging and diagnostic.
-
-## Stacktrace recovery
-
-Stacktrace recovery is another useful feature of debug mode. It is enabled by default in the debug mode,
-but can be separately disabled by setting `kotlinx.coroutines.stacktrace.recovery` system property to `false`.
-
-Stacktrace recovery tries to stitch asynchronous exception stacktrace with a stacktrace of the receiver by copying it, providing
-not only information where an exception was thrown, but also where it was asynchronously rethrown or caught.
-
-It is easy to demonstrate with actual stacktraces of the same program that awaits asynchronous operation in `main` function
-(runnable code is [here](../kotlinx-coroutines-debug/test/RecoveryExample.kt)):
-
-| Without recovery | With recovery |
-| - | - |
-| ![before](images/before.png "before") | ![after](images/after.png "after") |
-
-The only downside of this approach is losing referential transparency of the exception.
-
-### Stacktrace recovery machinery
-
-This section explains the inner mechanism of stacktrace recovery and can be skipped.
-
-When an exception is rethrown between coroutines (e.g. through `withContext` or `Deferred.await` boundary), stacktrace recovery
-machinery tries to create a copy of the original exception (with the original exception as the cause), then rewrite stacktrace
-of the copy with coroutine-related stack frames (using [Throwable.setStackTrace](https://docs.oracle.com/javase/9/docs/api/java/lang/Throwable.html#setStackTrace-java.lang.StackTraceElement:A-))
-and then throws the resulting exception instead of the original one.
-
-Exception copy logic is straightforward:
- 1) If the exception class implements [CopyableThrowable], [CopyableThrowable.createCopy] is used.
- `null` can be returned from `createCopy` to opt-out specific exception from being recovered.
- 2) If the exception class has class-specific fields not inherited from Throwable, the exception is not copied.
- 3) Otherwise, one of the public exception's constructor is invoked reflectively with an optional `initCause` call.
-
-## Debug agent
-
-[kotlinx-coroutines-debug](../kotlinx-coroutines-debug) module provides one of the most powerful debug capabilities in `kotlinx.coroutines`.
-
-This is a separate module with a JVM agent that keeps track of all alive coroutines, introspects and dumps them similar to thread dump command,
-additionally enhancing stacktraces with information where coroutine was created.
-
-The full tutorial of how to use debug agent can be found in the corresponding [readme](../kotlinx-coroutines-debug/README.md).
-
-### Debug agent and Android
-
-Unfortunately, Android runtime does not support Instrument API necessary for `kotlinx-coroutines-debug` to function, triggering `java.lang.NoClassDefFoundError: Failed resolution of: Ljava/lang/management/ManagementFactory;`.
-
-Nevertheless, it will be possible to support debug agent on Android as soon as [GradleAspectJ-Android](https://github.com/Archinamon/android-gradle-aspectj) will support android-gradle 3.3
-
-<!---
-Make an exception googlable
-java.lang.NoClassDefFoundError: Failed resolution of: Ljava/lang/management/ManagementFactory;
- at kotlinx.coroutines.repackaged.net.bytebuddy.agent.ByteBuddyAgent$ProcessProvider$ForCurrentVm$ForLegacyVm.resolve(ByteBuddyAgent.java:1055)
- at kotlinx.coroutines.repackaged.net.bytebuddy.agent.ByteBuddyAgent$ProcessProvider$ForCurrentVm.resolve(ByteBuddyAgent.java:1038)
- at kotlinx.coroutines.repackaged.net.bytebuddy.agent.ByteBuddyAgent.install(ByteBuddyAgent.java:374)
- at kotlinx.coroutines.repackaged.net.bytebuddy.agent.ByteBuddyAgent.install(ByteBuddyAgent.java:342)
- at kotlinx.coroutines.repackaged.net.bytebuddy.agent.ByteBuddyAgent.install(ByteBuddyAgent.java:328)
- at kotlinx.coroutines.debug.internal.DebugProbesImpl.install(DebugProbesImpl.kt:39)
- at kotlinx.coroutines.debug.DebugProbes.install(DebugProbes.kt:49)
--->
-
-## Android optimization
-
-In optimized (release) builds with R8 version 1.6.0 or later both
-[Debugging mode](../../docs/debugging.md#debug-mode) and
-[Stacktrace recovery](../../docs/debugging.md#stacktrace-recovery)
-are permanently turned off.
-For more details see ["Optimization" section for Android](../ui/kotlinx-coroutines-android/README.md#optimization).
-
-<!--- MODULE kotlinx-coroutines-core -->
-<!--- INDEX kotlinx.coroutines -->
-[DEBUG_PROPERTY_NAME]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-d-e-b-u-g_-p-r-o-p-e-r-t-y_-n-a-m-e.html
-[CoroutineName]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-name/index.html
-[CopyableThrowable]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-copyable-throwable/index.html
-[CopyableThrowable.createCopy]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-copyable-throwable/create-copy.html
-<!--- MODULE kotlinx-coroutines-debug -->
-<!--- END -->
+The documentation has been moved to the [topics/debugging.md](topics/debugging.md). \ No newline at end of file
diff --git a/docs/exception-handling.md b/docs/exception-handling.md
index a3070213..77dbac98 100644
--- a/docs/exception-handling.md
+++ b/docs/exception-handling.md
@@ -1,526 +1,3 @@
-<!--- TEST_NAME ExceptionsGuideTest -->
+The documentation has been moved to the [https://kotlinlang.org/docs/exception-handling.html](https://kotlinlang.org/docs/exception-handling.html) page.
-**Table of contents**
-
-<!--- TOC -->
-
-* [Exception Handling](#exception-handling)
- * [Exception propagation](#exception-propagation)
- * [CoroutineExceptionHandler](#coroutineexceptionhandler)
- * [Cancellation and exceptions](#cancellation-and-exceptions)
- * [Exceptions aggregation](#exceptions-aggregation)
- * [Supervision](#supervision)
- * [Supervision job](#supervision-job)
- * [Supervision scope](#supervision-scope)
- * [Exceptions in supervised coroutines](#exceptions-in-supervised-coroutines)
-
-<!--- END -->
-
-## Exception Handling
-
-This section covers exception handling and cancellation on exceptions.
-We already know that a cancelled coroutine throws [CancellationException] in suspension points and that it
-is ignored by the coroutines' machinery. Here we look at what happens if an exception is thrown during cancellation or multiple children of the same
-coroutine throw an exception.
-
-### Exception propagation
-
-Coroutine builders come in two flavors: propagating exceptions automatically ([launch] and [actor]) or
-exposing them to users ([async] and [produce]).
-When these builders are used to create a _root_ coroutine, that is not a _child_ of another coroutine,
-the former builders treat exceptions as **uncaught** exceptions, similar to Java's `Thread.uncaughtExceptionHandler`,
-while the latter are relying on the user to consume the final
-exception, for example via [await][Deferred.await] or [receive][ReceiveChannel.receive]
-([produce] and [receive][ReceiveChannel.receive] are covered later in [Channels](https://github.com/Kotlin/kotlinx.coroutines/blob/master/docs/channels.md) section).
-
-It can be demonstrated by a simple example that creates root coroutines using the [GlobalScope]:
-
-<div class="sample" markdown="1" theme="idea" data-highlight-only>
-
-```kotlin
-import kotlinx.coroutines.*
-
-fun main() = runBlocking {
- val job = GlobalScope.launch { // root coroutine with launch
- println("Throwing exception from launch")
- throw IndexOutOfBoundsException() // Will be printed to the console by Thread.defaultUncaughtExceptionHandler
- }
- job.join()
- println("Joined failed job")
- val deferred = GlobalScope.async { // root coroutine with async
- println("Throwing exception from async")
- throw ArithmeticException() // Nothing is printed, relying on user to call await
- }
- try {
- deferred.await()
- println("Unreached")
- } catch (e: ArithmeticException) {
- println("Caught ArithmeticException")
- }
-}
-```
-
-</div>
-
-> You can get the full code [here](../kotlinx-coroutines-core/jvm/test/guide/example-exceptions-01.kt).
-
-The output of this code is (with [debug](https://github.com/Kotlin/kotlinx.coroutines/blob/master/docs/coroutine-context-and-dispatchers.md#debugging-coroutines-and-threads)):
-
-```text
-Throwing exception from launch
-Exception in thread "DefaultDispatcher-worker-2 @coroutine#2" java.lang.IndexOutOfBoundsException
-Joined failed job
-Throwing exception from async
-Caught ArithmeticException
-```
-
-<!--- TEST EXCEPTION-->
-
-### CoroutineExceptionHandler
-
-It is possible to customize the default behavior of printing **uncaught** exceptions to the console.
-[CoroutineExceptionHandler] context element on a _root_ coroutine can be used as generic `catch` block for
-this root coroutine and all its children where custom exception handling may take place.
-It is similar to [`Thread.uncaughtExceptionHandler`](https://docs.oracle.com/javase/8/docs/api/java/lang/Thread.html#setUncaughtExceptionHandler(java.lang.Thread.UncaughtExceptionHandler)).
-You cannot recover from the exception in the `CoroutineExceptionHandler`. The coroutine had already completed
-with the corresponding exception when the handler is called. Normally, the handler is used to
-log the exception, show some kind of error message, terminate, and/or restart the application.
-
-On JVM it is possible to redefine global exception handler for all coroutines by registering [CoroutineExceptionHandler] via
-[`ServiceLoader`](https://docs.oracle.com/javase/8/docs/api/java/util/ServiceLoader.html).
-Global exception handler is similar to
-[`Thread.defaultUncaughtExceptionHandler`](https://docs.oracle.com/javase/8/docs/api/java/lang/Thread.html#setDefaultUncaughtExceptionHandler(java.lang.Thread.UncaughtExceptionHandler))
-which is used when no more specific handlers are registered.
-On Android, `uncaughtExceptionPreHandler` is installed as a global coroutine exception handler.
-
-`CoroutineExceptionHandler` is invoked only on **uncaught** exceptions &mdash; exceptions that were not handled in any other way.
-In particular, all _children_ coroutines (coroutines created in the context of another [Job]) delegate handling of
-their exceptions to their parent coroutine, which also delegates to the parent, and so on until the root,
-so the `CoroutineExceptionHandler` installed in their context is never used.
-In addition to that, [async] builder always catches all exceptions and represents them in the resulting [Deferred] object,
-so its `CoroutineExceptionHandler` has no effect either.
-
-> Coroutines running in supervision scope do not propagate exceptions to their parent and are
-excluded from this rule. A further [Supervision](#supervision) section of this document gives more details.
-
-<div class="sample" markdown="1" theme="idea" data-min-compiler-version="1.3">
-
-```kotlin
-import kotlinx.coroutines.*
-
-fun main() = runBlocking {
-//sampleStart
- val handler = CoroutineExceptionHandler { _, exception ->
- println("CoroutineExceptionHandler got $exception")
- }
- val job = GlobalScope.launch(handler) { // root coroutine, running in GlobalScope
- throw AssertionError()
- }
- val deferred = GlobalScope.async(handler) { // also root, but async instead of launch
- throw ArithmeticException() // Nothing will be printed, relying on user to call deferred.await()
- }
- joinAll(job, deferred)
-//sampleEnd
-}
-```
-
-</div>
-
-> You can get the full code [here](../kotlinx-coroutines-core/jvm/test/guide/example-exceptions-02.kt).
-
-The output of this code is:
-
-```text
-CoroutineExceptionHandler got java.lang.AssertionError
-```
-
-<!--- TEST-->
-
-### Cancellation and exceptions
-
-Cancellation is closely related to exceptions. Coroutines internally use `CancellationException` for cancellation, these
-exceptions are ignored by all handlers, so they should be used only as the source of additional debug information, which can
-be obtained by `catch` block.
-When a coroutine is cancelled using [Job.cancel], it terminates, but it does not cancel its parent.
-
-<div class="sample" markdown="1" theme="idea" data-min-compiler-version="1.3">
-
-```kotlin
-import kotlinx.coroutines.*
-
-fun main() = runBlocking {
-//sampleStart
- val job = launch {
- val child = launch {
- try {
- delay(Long.MAX_VALUE)
- } finally {
- println("Child is cancelled")
- }
- }
- yield()
- println("Cancelling child")
- child.cancel()
- child.join()
- yield()
- println("Parent is not cancelled")
- }
- job.join()
-//sampleEnd
-}
-```
-
-</div>
-
-> You can get the full code [here](../kotlinx-coroutines-core/jvm/test/guide/example-exceptions-03.kt).
-
-The output of this code is:
-
-```text
-Cancelling child
-Child is cancelled
-Parent is not cancelled
-```
-
-<!--- TEST-->
-
-If a coroutine encounters an exception other than `CancellationException`, it cancels its parent with that exception.
-This behaviour cannot be overridden and is used to provide stable coroutines hierarchies for
-[structured concurrency](https://github.com/Kotlin/kotlinx.coroutines/blob/master/docs/composing-suspending-functions.md#structured-concurrency-with-async).
-[CoroutineExceptionHandler] implementation is not used for child coroutines.
-
-> In these examples [CoroutineExceptionHandler] is always installed to a coroutine
-that is created in [GlobalScope]. It does not make sense to install an exception handler to a coroutine that
-is launched in the scope of the main [runBlocking], since the main coroutine is going to be always cancelled
-when its child completes with exception despite the installed handler.
-
-The original exception is handled by the parent only when all its children terminate,
-which is demonstrated by the following example.
-
-<div class="sample" markdown="1" theme="idea" data-min-compiler-version="1.3">
-
-```kotlin
-import kotlinx.coroutines.*
-
-fun main() = runBlocking {
-//sampleStart
- val handler = CoroutineExceptionHandler { _, exception ->
- println("CoroutineExceptionHandler got $exception")
- }
- val job = GlobalScope.launch(handler) {
- launch { // the first child
- try {
- delay(Long.MAX_VALUE)
- } finally {
- withContext(NonCancellable) {
- println("Children are cancelled, but exception is not handled until all children terminate")
- delay(100)
- println("The first child finished its non cancellable block")
- }
- }
- }
- launch { // the second child
- delay(10)
- println("Second child throws an exception")
- throw ArithmeticException()
- }
- }
- job.join()
-//sampleEnd
-}
-```
-
-</div>
-
-> You can get the full code [here](../kotlinx-coroutines-core/jvm/test/guide/example-exceptions-04.kt).
-
-The output of this code is:
-
-```text
-Second child throws an exception
-Children are cancelled, but exception is not handled until all children terminate
-The first child finished its non cancellable block
-CoroutineExceptionHandler got java.lang.ArithmeticException
-```
-<!--- TEST-->
-
-### Exceptions aggregation
-
-When multiple children of a coroutine fail with an exception, the
-general rule is "the first exception wins", so the first exception gets handled.
-All additional exceptions that happen after the first one are attached to the first exception as suppressed ones.
-
-<!--- INCLUDE
-import kotlinx.coroutines.exceptions.*
--->
-
-<div class="sample" markdown="1" theme="idea" data-min-compiler-version="1.3">
-
-```kotlin
-import kotlinx.coroutines.*
-import java.io.*
-
-fun main() = runBlocking {
- val handler = CoroutineExceptionHandler { _, exception ->
- println("CoroutineExceptionHandler got $exception with suppressed ${exception.suppressed.contentToString()}")
- }
- val job = GlobalScope.launch(handler) {
- launch {
- try {
- delay(Long.MAX_VALUE) // it gets cancelled when another sibling fails with IOException
- } finally {
- throw ArithmeticException() // the second exception
- }
- }
- launch {
- delay(100)
- throw IOException() // the first exception
- }
- delay(Long.MAX_VALUE)
- }
- job.join()
-}
-```
-
-</div>
-
-> You can get the full code [here](../kotlinx-coroutines-core/jvm/test/guide/example-exceptions-05.kt).
-
-> Note: This above code will work properly only on JDK7+ that supports `suppressed` exceptions
-
-The output of this code is:
-
-```text
-CoroutineExceptionHandler got java.io.IOException with suppressed [java.lang.ArithmeticException]
-```
-
-<!--- TEST-->
-
-> Note that this mechanism currently only works on Java version 1.7+.
-The JS and Native restrictions are temporary and will be lifted in the future.
-
-Cancellation exceptions are transparent and are unwrapped by default:
-
-<div class="sample" markdown="1" theme="idea" data-min-compiler-version="1.3">
-
-```kotlin
-import kotlinx.coroutines.*
-import java.io.*
-
-fun main() = runBlocking {
-//sampleStart
- val handler = CoroutineExceptionHandler { _, exception ->
- println("CoroutineExceptionHandler got $exception")
- }
- val job = GlobalScope.launch(handler) {
- val inner = launch { // all this stack of coroutines will get cancelled
- launch {
- launch {
- throw IOException() // the original exception
- }
- }
- }
- try {
- inner.join()
- } catch (e: CancellationException) {
- println("Rethrowing CancellationException with original cause")
- throw e // cancellation exception is rethrown, yet the original IOException gets to the handler
- }
- }
- job.join()
-//sampleEnd
-}
-```
-
-</div>
-
-> You can get the full code [here](../kotlinx-coroutines-core/jvm/test/guide/example-exceptions-06.kt).
-
-The output of this code is:
-
-```text
-Rethrowing CancellationException with original cause
-CoroutineExceptionHandler got java.io.IOException
-```
-<!--- TEST-->
-
-### Supervision
-
-As we have studied before, cancellation is a bidirectional relationship propagating through the whole
-hierarchy of coroutines. Let us take a look at the case when unidirectional cancellation is required.
-
-A good example of such a requirement is a UI component with the job defined in its scope. If any of the UI's child tasks
-have failed, it is not always necessary to cancel (effectively kill) the whole UI component,
-but if UI component is destroyed (and its job is cancelled), then it is necessary to fail all child jobs as their results are no longer needed.
-
-Another example is a server process that spawns multiple child jobs and needs to _supervise_
-their execution, tracking their failures and only restarting the failed ones.
-
-#### Supervision job
-
-The [SupervisorJob][SupervisorJob()] can be used for these purposes.
-It is similar to a regular [Job][Job()] with the only exception that cancellation is propagated
-only downwards. This can easily be demonstrated using the following example:
-
-<div class="sample" markdown="1" theme="idea" data-highlight-only>
-
-```kotlin
-import kotlinx.coroutines.*
-
-fun main() = runBlocking {
- val supervisor = SupervisorJob()
- with(CoroutineScope(coroutineContext + supervisor)) {
- // launch the first child -- its exception is ignored for this example (don't do this in practice!)
- val firstChild = launch(CoroutineExceptionHandler { _, _ -> }) {
- println("The first child is failing")
- throw AssertionError("The first child is cancelled")
- }
- // launch the second child
- val secondChild = launch {
- firstChild.join()
- // Cancellation of the first child is not propagated to the second child
- println("The first child is cancelled: ${firstChild.isCancelled}, but the second one is still active")
- try {
- delay(Long.MAX_VALUE)
- } finally {
- // But cancellation of the supervisor is propagated
- println("The second child is cancelled because the supervisor was cancelled")
- }
- }
- // wait until the first child fails & completes
- firstChild.join()
- println("Cancelling the supervisor")
- supervisor.cancel()
- secondChild.join()
- }
-}
-```
-
-</div>
-
-> You can get the full code [here](../kotlinx-coroutines-core/jvm/test/guide/example-supervision-01.kt).
-
-The output of this code is:
-
-```text
-The first child is failing
-The first child is cancelled: true, but the second one is still active
-Cancelling the supervisor
-The second child is cancelled because the supervisor was cancelled
-```
-<!--- TEST-->
-
-
-#### Supervision scope
-
-Instead of [coroutineScope][_coroutineScope], we can use [supervisorScope][_supervisorScope] for _scoped_ concurrency. It propagates the cancellation
-in one direction only and cancels all its children only if it failed itself. It also waits for all children before completion
-just like [coroutineScope][_coroutineScope] does.
-
-<div class="sample" markdown="1" theme="idea" data-highlight-only>
-
-```kotlin
-import kotlin.coroutines.*
-import kotlinx.coroutines.*
-
-fun main() = runBlocking {
- try {
- supervisorScope {
- val child = launch {
- try {
- println("The child is sleeping")
- delay(Long.MAX_VALUE)
- } finally {
- println("The child is cancelled")
- }
- }
- // Give our child a chance to execute and print using yield
- yield()
- println("Throwing an exception from the scope")
- throw AssertionError()
- }
- } catch(e: AssertionError) {
- println("Caught an assertion error")
- }
-}
-```
-
-</div>
-
-> You can get the full code [here](../kotlinx-coroutines-core/jvm/test/guide/example-supervision-02.kt).
-
-The output of this code is:
-
-```text
-The child is sleeping
-Throwing an exception from the scope
-The child is cancelled
-Caught an assertion error
-```
-<!--- TEST-->
-
-#### Exceptions in supervised coroutines
-
-Another crucial difference between regular and supervisor jobs is exception handling.
-Every child should handle its exceptions by itself via the exception handling mechanism.
-This difference comes from the fact that child's failure does not propagate to the parent.
-It means that coroutines launched directly inside the [supervisorScope][_supervisorScope] _do_ use the [CoroutineExceptionHandler]
-that is installed in their scope in the same way as root coroutines do
-(see the [CoroutineExceptionHandler](#coroutineexceptionhandler) section for details).
-
-<div class="sample" markdown="1" theme="idea" data-highlight-only>
-
-```kotlin
-import kotlin.coroutines.*
-import kotlinx.coroutines.*
-
-fun main() = runBlocking {
- val handler = CoroutineExceptionHandler { _, exception ->
- println("CoroutineExceptionHandler got $exception")
- }
- supervisorScope {
- val child = launch(handler) {
- println("The child throws an exception")
- throw AssertionError()
- }
- println("The scope is completing")
- }
- println("The scope is completed")
-}
-```
-
-</div>
-
-> You can get the full code [here](../kotlinx-coroutines-core/jvm/test/guide/example-supervision-03.kt).
-
-The output of this code is:
-
-```text
-The scope is completing
-The child throws an exception
-CoroutineExceptionHandler got java.lang.AssertionError
-The scope is completed
-```
-<!--- TEST-->
-
-<!--- MODULE kotlinx-coroutines-core -->
-<!--- INDEX kotlinx.coroutines -->
-[CancellationException]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-cancellation-exception/index.html
-[launch]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/launch.html
-[async]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/async.html
-[Deferred.await]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-deferred/await.html
-[GlobalScope]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-global-scope/index.html
-[CoroutineExceptionHandler]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-exception-handler/index.html
-[Job]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job/index.html
-[Deferred]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-deferred/index.html
-[Job.cancel]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job/cancel.html
-[runBlocking]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/run-blocking.html
-[SupervisorJob()]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-supervisor-job.html
-[Job()]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job.html
-[_coroutineScope]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/coroutine-scope.html
-[_supervisorScope]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/supervisor-scope.html
-<!--- INDEX kotlinx.coroutines.channels -->
-[actor]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/actor.html
-[produce]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/produce.html
-[ReceiveChannel.receive]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-receive-channel/receive.html
-<!--- END -->
+To edit the documentation, open the [topics/exception-handling.md](topics/exception-handling.md) page. \ No newline at end of file
diff --git a/docs/flow.md b/docs/flow.md
index 4374e7aa..400a15f1 100644
--- a/docs/flow.md
+++ b/docs/flow.md
@@ -1,1979 +1,3 @@
-<!--- TEST_NAME FlowGuideTest -->
+The documentation has been moved to the [https://kotlinlang.org/docs/flow.html](https://kotlinlang.org/docs/flow.html) page.
-**Table of contents**
-
-<!--- TOC -->
-
-* [Asynchronous Flow](#asynchronous-flow)
- * [Representing multiple values](#representing-multiple-values)
- * [Sequences](#sequences)
- * [Suspending functions](#suspending-functions)
- * [Flows](#flows)
- * [Flows are cold](#flows-are-cold)
- * [Flow cancellation basics](#flow-cancellation-basics)
- * [Flow builders](#flow-builders)
- * [Intermediate flow operators](#intermediate-flow-operators)
- * [Transform operator](#transform-operator)
- * [Size-limiting operators](#size-limiting-operators)
- * [Terminal flow operators](#terminal-flow-operators)
- * [Flows are sequential](#flows-are-sequential)
- * [Flow context](#flow-context)
- * [Wrong emission withContext](#wrong-emission-withcontext)
- * [flowOn operator](#flowon-operator)
- * [Buffering](#buffering)
- * [Conflation](#conflation)
- * [Processing the latest value](#processing-the-latest-value)
- * [Composing multiple flows](#composing-multiple-flows)
- * [Zip](#zip)
- * [Combine](#combine)
- * [Flattening flows](#flattening-flows)
- * [flatMapConcat](#flatmapconcat)
- * [flatMapMerge](#flatmapmerge)
- * [flatMapLatest](#flatmaplatest)
- * [Flow exceptions](#flow-exceptions)
- * [Collector try and catch](#collector-try-and-catch)
- * [Everything is caught](#everything-is-caught)
- * [Exception transparency](#exception-transparency)
- * [Transparent catch](#transparent-catch)
- * [Catching declaratively](#catching-declaratively)
- * [Flow completion](#flow-completion)
- * [Imperative finally block](#imperative-finally-block)
- * [Declarative handling](#declarative-handling)
- * [Successful completion](#successful-completion)
- * [Imperative versus declarative](#imperative-versus-declarative)
- * [Launching flow](#launching-flow)
- * [Flow cancellation checks](#flow-cancellation-checks)
- * [Making busy flow cancellable](#making-busy-flow-cancellable)
- * [Flow and Reactive Streams](#flow-and-reactive-streams)
-
-<!--- END -->
-
-## Asynchronous Flow
-
-A suspending function asynchronously returns a single value, but how can we return
-multiple asynchronously computed values? This is where Kotlin Flows come in.
-
-### Representing multiple values
-
-Multiple values can be represented in Kotlin using [collections].
-For example, we can have a `simple` function that returns a [List]
-of three numbers and then print them all using [forEach]:
-
-<div class="sample" markdown="1" theme="idea" data-min-compiler-version="1.3">
-
-```kotlin
-fun simple(): List<Int> = listOf(1, 2, 3)
-
-fun main() {
- simple().forEach { value -> println(value) }
-}
-```
-
-</div>
-
-> You can get the full code from [here](../kotlinx-coroutines-core/jvm/test/guide/example-flow-01.kt).
-
-This code outputs:
-
-```text
-1
-2
-3
-```
-
-<!--- TEST -->
-
-#### Sequences
-
-If we are computing the numbers with some CPU-consuming blocking code
-(each computation taking 100ms), then we can represent the numbers using a [Sequence]:
-
-<div class="sample" markdown="1" theme="idea" data-min-compiler-version="1.3">
-
-```kotlin
-fun simple(): Sequence<Int> = sequence { // sequence builder
- for (i in 1..3) {
- Thread.sleep(100) // pretend we are computing it
- yield(i) // yield next value
- }
-}
-
-fun main() {
- simple().forEach { value -> println(value) }
-}
-```
-
-</div>
-
-> You can get the full code from [here](../kotlinx-coroutines-core/jvm/test/guide/example-flow-02.kt).
-
-This code outputs the same numbers, but it waits 100ms before printing each one.
-
-<!--- TEST
-1
-2
-3
--->
-
-#### Suspending functions
-
-However, this computation blocks the main thread that is running the code.
-When these values are computed by asynchronous code we can mark the `simple` function with a `suspend` modifier,
-so that it can perform its work without blocking and return the result as a list:
-
-<div class="sample" markdown="1" theme="idea" data-min-compiler-version="1.3">
-
-```kotlin
-import kotlinx.coroutines.*
-
-//sampleStart
-suspend fun simple(): List<Int> {
- delay(1000) // pretend we are doing something asynchronous here
- return listOf(1, 2, 3)
-}
-
-fun main() = runBlocking<Unit> {
- simple().forEach { value -> println(value) }
-}
-//sampleEnd
-```
-
-</div>
-
-> You can get the full code from [here](../kotlinx-coroutines-core/jvm/test/guide/example-flow-03.kt).
-
-This code prints the numbers after waiting for a second.
-
-<!--- TEST
-1
-2
-3
--->
-
-#### Flows
-
-Using the `List<Int>` result type, means we can only return all the values at once. To represent
-the stream of values that are being asynchronously computed, we can use a [`Flow<Int>`][Flow] type just like we would use the `Sequence<Int>` type for synchronously computed values:
-
-<div class="sample" markdown="1" theme="idea" data-min-compiler-version="1.3">
-
-```kotlin
-import kotlinx.coroutines.*
-import kotlinx.coroutines.flow.*
-
-//sampleStart
-fun simple(): Flow<Int> = flow { // flow builder
- for (i in 1..3) {
- delay(100) // pretend we are doing something useful here
- emit(i) // emit next value
- }
-}
-
-fun main() = runBlocking<Unit> {
- // Launch a concurrent coroutine to check if the main thread is blocked
- launch {
- for (k in 1..3) {
- println("I'm not blocked $k")
- delay(100)
- }
- }
- // Collect the flow
- simple().collect { value -> println(value) }
-}
-//sampleEnd
-```
-
-</div>
-
-> You can get the full code from [here](../kotlinx-coroutines-core/jvm/test/guide/example-flow-04.kt).
-
-This code waits 100ms before printing each number without blocking the main thread. This is verified
-by printing "I'm not blocked" every 100ms from a separate coroutine that is running in the main thread:
-
-```text
-I'm not blocked 1
-1
-I'm not blocked 2
-2
-I'm not blocked 3
-3
-```
-
-<!--- TEST -->
-
-Notice the following differences in the code with the [Flow] from the earlier examples:
-
-* A builder function for [Flow] type is called [flow][_flow].
-* Code inside the `flow { ... }` builder block can suspend.
-* The `simple` function is no longer marked with `suspend` modifier.
-* Values are _emitted_ from the flow using [emit][FlowCollector.emit] function.
-* Values are _collected_ from the flow using [collect][collect] function.
-
-> We can replace [delay] with `Thread.sleep` in the body of `simple`'s `flow { ... }` and see that the main
-thread is blocked in this case.
-
-### Flows are cold
-
-Flows are _cold_ streams similar to sequences &mdash; the code inside a [flow][_flow] builder does not
-run until the flow is collected. This becomes clear in the following example:
-
-<div class="sample" markdown="1" theme="idea" data-min-compiler-version="1.3">
-
-```kotlin
-import kotlinx.coroutines.*
-import kotlinx.coroutines.flow.*
-
-//sampleStart
-fun simple(): Flow<Int> = flow {
- println("Flow started")
- for (i in 1..3) {
- delay(100)
- emit(i)
- }
-}
-
-fun main() = runBlocking<Unit> {
- println("Calling simple function...")
- val flow = simple()
- println("Calling collect...")
- flow.collect { value -> println(value) }
- println("Calling collect again...")
- flow.collect { value -> println(value) }
-}
-//sampleEnd
-```
-
-</div>
-
-> You can get the full code from [here](../kotlinx-coroutines-core/jvm/test/guide/example-flow-05.kt).
-
-Which prints:
-
-```text
-Calling simple function...
-Calling collect...
-Flow started
-1
-2
-3
-Calling collect again...
-Flow started
-1
-2
-3
-```
-
-<!--- TEST -->
-
-This is a key reason the `simple` function (which returns a flow) is not marked with `suspend` modifier.
-By itself, `simple()` call returns quickly and does not wait for anything. The flow starts every time it is collected,
-that is why we see "Flow started" when we call `collect` again.
-
-### Flow cancellation basics
-
-Flow adheres to the general cooperative cancellation of coroutines. As usual, flow collection can be
-cancelled when the flow is suspended in a cancellable suspending function (like [delay]).
-The following example shows how the flow gets cancelled on a timeout when running in a [withTimeoutOrNull] block
-and stops executing its code:
-
-<div class="sample" markdown="1" theme="idea" data-min-compiler-version="1.3">
-
-```kotlin
-import kotlinx.coroutines.*
-import kotlinx.coroutines.flow.*
-
-//sampleStart
-fun simple(): Flow<Int> = flow {
- for (i in 1..3) {
- delay(100)
- println("Emitting $i")
- emit(i)
- }
-}
-
-fun main() = runBlocking<Unit> {
- withTimeoutOrNull(250) { // Timeout after 250ms
- simple().collect { value -> println(value) }
- }
- println("Done")
-}
-//sampleEnd
-```
-
-</div>
-
-> You can get the full code from [here](../kotlinx-coroutines-core/jvm/test/guide/example-flow-06.kt).
-
-Notice how only two numbers get emitted by the flow in the `simple` function, producing the following output:
-
-```text
-Emitting 1
-1
-Emitting 2
-2
-Done
-```
-
-<!--- TEST -->
-
-See [Flow cancellation checks](#flow-cancellation-checks) section for more details.
-
-### Flow builders
-
-The `flow { ... }` builder from the previous examples is the most basic one. There are other builders for
-easier declaration of flows:
-
-* [flowOf] builder that defines a flow emitting a fixed set of values.
-* Various collections and sequences can be converted to flows using `.asFlow()` extension functions.
-
-So, the example that prints the numbers from 1 to 3 from a flow can be written as:
-
-<div class="sample" markdown="1" theme="idea" data-min-compiler-version="1.3">
-
-```kotlin
-import kotlinx.coroutines.*
-import kotlinx.coroutines.flow.*
-
-fun main() = runBlocking<Unit> {
-//sampleStart
- // Convert an integer range to a flow
- (1..3).asFlow().collect { value -> println(value) }
-//sampleEnd
-}
-```
-
-</div>
-
-> You can get the full code from [here](../kotlinx-coroutines-core/jvm/test/guide/example-flow-07.kt).
-
-<!--- TEST
-1
-2
-3
--->
-
-### Intermediate flow operators
-
-Flows can be transformed with operators, just as you would with collections and sequences.
-Intermediate operators are applied to an upstream flow and return a downstream flow.
-These operators are cold, just like flows are. A call to such an operator is not
-a suspending function itself. It works quickly, returning the definition of a new transformed flow.
-
-The basic operators have familiar names like [map] and [filter].
-The important difference to sequences is that blocks of
-code inside these operators can call suspending functions.
-
-For example, a flow of incoming requests can be
-mapped to the results with the [map] operator, even when performing a request is a long-running
-operation that is implemented by a suspending function:
-
-<div class="sample" markdown="1" theme="idea" data-min-compiler-version="1.3">
-
-```kotlin
-import kotlinx.coroutines.*
-import kotlinx.coroutines.flow.*
-
-//sampleStart
-suspend fun performRequest(request: Int): String {
- delay(1000) // imitate long-running asynchronous work
- return "response $request"
-}
-
-fun main() = runBlocking<Unit> {
- (1..3).asFlow() // a flow of requests
- .map { request -> performRequest(request) }
- .collect { response -> println(response) }
-}
-//sampleEnd
-```
-
-</div>
-
-> You can get the full code from [here](../kotlinx-coroutines-core/jvm/test/guide/example-flow-08.kt).
-
-It produces the following three lines, each line appearing after each second:
-
-```text
-response 1
-response 2
-response 3
-```
-
-<!--- TEST -->
-
-#### Transform operator
-
-Among the flow transformation operators, the most general one is called [transform]. It can be used to imitate
-simple transformations like [map] and [filter], as well as implement more complex transformations.
-Using the `transform` operator, we can [emit][FlowCollector.emit] arbitrary values an arbitrary number of times.
-
-For example, using `transform` we can emit a string before performing a long-running asynchronous request
-and follow it with a response:
-
-<div class="sample" markdown="1" theme="idea" data-min-compiler-version="1.3">
-
-```kotlin
-import kotlinx.coroutines.*
-import kotlinx.coroutines.flow.*
-
-suspend fun performRequest(request: Int): String {
- delay(1000) // imitate long-running asynchronous work
- return "response $request"
-}
-
-fun main() = runBlocking<Unit> {
-//sampleStart
- (1..3).asFlow() // a flow of requests
- .transform { request ->
- emit("Making request $request")
- emit(performRequest(request))
- }
- .collect { response -> println(response) }
-//sampleEnd
-}
-```
-
-</div>
-
-> You can get the full code from [here](../kotlinx-coroutines-core/jvm/test/guide/example-flow-09.kt).
-
-The output of this code is:
-
-```text
-Making request 1
-response 1
-Making request 2
-response 2
-Making request 3
-response 3
-```
-
-<!--- TEST -->
-
-#### Size-limiting operators
-
-Size-limiting intermediate operators like [take] cancel the execution of the flow when the corresponding limit
-is reached. Cancellation in coroutines is always performed by throwing an exception, so that all the resource-management
-functions (like `try { ... } finally { ... }` blocks) operate normally in case of cancellation:
-
-<div class="sample" markdown="1" theme="idea" data-min-compiler-version="1.3">
-
-```kotlin
-import kotlinx.coroutines.*
-import kotlinx.coroutines.flow.*
-
-//sampleStart
-fun numbers(): Flow<Int> = flow {
- try {
- emit(1)
- emit(2)
- println("This line will not execute")
- emit(3)
- } finally {
- println("Finally in numbers")
- }
-}
-
-fun main() = runBlocking<Unit> {
- numbers()
- .take(2) // take only the first two
- .collect { value -> println(value) }
-}
-//sampleEnd
-```
-
-</div>
-
-> You can get the full code from [here](../kotlinx-coroutines-core/jvm/test/guide/example-flow-10.kt).
-
-The output of this code clearly shows that the execution of the `flow { ... }` body in the `numbers()` function
-stopped after emitting the second number:
-
-```text
-1
-2
-Finally in numbers
-```
-
-<!--- TEST -->
-
-### Terminal flow operators
-
-Terminal operators on flows are _suspending functions_ that start a collection of the flow.
-The [collect] operator is the most basic one, but there are other terminal operators, which can make it easier:
-
-* Conversion to various collections like [toList] and [toSet].
-* Operators to get the [first] value and to ensure that a flow emits a [single] value.
-* Reducing a flow to a value with [reduce] and [fold].
-
-For example:
-
-<div class="sample" markdown="1" theme="idea" data-min-compiler-version="1.3">
-
-```kotlin
-import kotlinx.coroutines.*
-import kotlinx.coroutines.flow.*
-
-fun main() = runBlocking<Unit> {
-//sampleStart
- val sum = (1..5).asFlow()
- .map { it * it } // squares of numbers from 1 to 5
- .reduce { a, b -> a + b } // sum them (terminal operator)
- println(sum)
-//sampleEnd
-}
-```
-
-</div>
-
-> You can get the full code from [here](../kotlinx-coroutines-core/jvm/test/guide/example-flow-11.kt).
-
-Prints a single number:
-
-```text
-55
-```
-
-<!--- TEST -->
-
-### Flows are sequential
-
-Each individual collection of a flow is performed sequentially unless special operators that operate
-on multiple flows are used. The collection works directly in the coroutine that calls a terminal operator.
-No new coroutines are launched by default.
-Each emitted value is processed by all the intermediate operators from
-upstream to downstream and is then delivered to the terminal operator after.
-
-See the following example that filters the even integers and maps them to strings:
-
-<div class="sample" markdown="1" theme="idea" data-min-compiler-version="1.3">
-
-```kotlin
-import kotlinx.coroutines.*
-import kotlinx.coroutines.flow.*
-
-fun main() = runBlocking<Unit> {
-//sampleStart
- (1..5).asFlow()
- .filter {
- println("Filter $it")
- it % 2 == 0
- }
- .map {
- println("Map $it")
- "string $it"
- }.collect {
- println("Collect $it")
- }
-//sampleEnd
-}
-```
-
-</div>
-
-> You can get the full code from [here](../kotlinx-coroutines-core/jvm/test/guide/example-flow-12.kt).
-
-Producing:
-
-```text
-Filter 1
-Filter 2
-Map 2
-Collect string 2
-Filter 3
-Filter 4
-Map 4
-Collect string 4
-Filter 5
-```
-
-<!--- TEST -->
-
-### Flow context
-
-Collection of a flow always happens in the context of the calling coroutine. For example, if there is
-a `simple` flow, then the following code runs in the context specified
-by the author of this code, regardless of the implementation details of the `simple` flow:
-
-<div class="sample" markdown="1" theme="idea" data-highlight-only>
-
-```kotlin
-withContext(context) {
- simple().collect { value ->
- println(value) // run in the specified context
- }
-}
-```
-
-</div>
-
-<!--- CLEAR -->
-
-This property of a flow is called _context preservation_.
-
-So, by default, code in the `flow { ... }` builder runs in the context that is provided by a collector
-of the corresponding flow. For example, consider the implementation of a `simple` function that prints the thread
-it is called on and emits three numbers:
-
-<div class="sample" markdown="1" theme="idea" data-min-compiler-version="1.3">
-
-```kotlin
-import kotlinx.coroutines.*
-import kotlinx.coroutines.flow.*
-
-fun log(msg: String) = println("[${Thread.currentThread().name}] $msg")
-
-//sampleStart
-fun simple(): Flow<Int> = flow {
- log("Started simple flow")
- for (i in 1..3) {
- emit(i)
- }
-}
-
-fun main() = runBlocking<Unit> {
- simple().collect { value -> log("Collected $value") }
-}
-//sampleEnd
-```
-
-</div>
-
-> You can get the full code from [here](../kotlinx-coroutines-core/jvm/test/guide/example-flow-13.kt).
-
-Running this code produces:
-
-```text
-[main @coroutine#1] Started simple flow
-[main @coroutine#1] Collected 1
-[main @coroutine#1] Collected 2
-[main @coroutine#1] Collected 3
-```
-
-<!--- TEST FLEXIBLE_THREAD -->
-
-Since `simple().collect` is called from the main thread, the body of `simple`'s flow is also called in the main thread.
-This is the perfect default for fast-running or asynchronous code that does not care about the execution context and
-does not block the caller.
-
-#### Wrong emission withContext
-
-However, the long-running CPU-consuming code might need to be executed in the context of [Dispatchers.Default] and UI-updating
-code might need to be executed in the context of [Dispatchers.Main]. Usually, [withContext] is used
-to change the context in the code using Kotlin coroutines, but code in the `flow { ... }` builder has to honor the context
-preservation property and is not allowed to [emit][FlowCollector.emit] from a different context.
-
-Try running the following code:
-
-<div class="sample" markdown="1" theme="idea" data-min-compiler-version="1.3">
-
-```kotlin
-import kotlinx.coroutines.*
-import kotlinx.coroutines.flow.*
-
-//sampleStart
-fun simple(): Flow<Int> = flow {
- // The WRONG way to change context for CPU-consuming code in flow builder
- kotlinx.coroutines.withContext(Dispatchers.Default) {
- for (i in 1..3) {
- Thread.sleep(100) // pretend we are computing it in CPU-consuming way
- emit(i) // emit next value
- }
- }
-}
-
-fun main() = runBlocking<Unit> {
- simple().collect { value -> println(value) }
-}
-//sampleEnd
-```
-
-</div>
-
-> You can get the full code from [here](../kotlinx-coroutines-core/jvm/test/guide/example-flow-14.kt).
-
-This code produces the following exception:
-
-```text
-Exception in thread "main" java.lang.IllegalStateException: Flow invariant is violated:
- Flow was collected in [CoroutineId(1), "coroutine#1":BlockingCoroutine{Active}@5511c7f8, BlockingEventLoop@2eac3323],
- but emission happened in [CoroutineId(1), "coroutine#1":DispatchedCoroutine{Active}@2dae0000, Dispatchers.Default].
- Please refer to 'flow' documentation or use 'flowOn' instead
- at ...
-```
-
-<!--- TEST EXCEPTION -->
-
-#### flowOn operator
-
-The exception refers to the [flowOn] function that shall be used to change the context of the flow emission.
-The correct way to change the context of a flow is shown in the example below, which also prints the
-names of the corresponding threads to show how it all works:
-
-<div class="sample" markdown="1" theme="idea" data-min-compiler-version="1.3">
-
-```kotlin
-import kotlinx.coroutines.*
-import kotlinx.coroutines.flow.*
-
-fun log(msg: String) = println("[${Thread.currentThread().name}] $msg")
-
-//sampleStart
-fun simple(): Flow<Int> = flow {
- for (i in 1..3) {
- Thread.sleep(100) // pretend we are computing it in CPU-consuming way
- log("Emitting $i")
- emit(i) // emit next value
- }
-}.flowOn(Dispatchers.Default) // RIGHT way to change context for CPU-consuming code in flow builder
-
-fun main() = runBlocking<Unit> {
- simple().collect { value ->
- log("Collected $value")
- }
-}
-//sampleEnd
-```
-
-</div>
-
-> You can get the full code from [here](../kotlinx-coroutines-core/jvm/test/guide/example-flow-15.kt).
-
-Notice how `flow { ... }` works in the background thread, while collection happens in the main thread:
-
-<!--- TEST FLEXIBLE_THREAD
-[DefaultDispatcher-worker-1 @coroutine#2] Emitting 1
-[main @coroutine#1] Collected 1
-[DefaultDispatcher-worker-1 @coroutine#2] Emitting 2
-[main @coroutine#1] Collected 2
-[DefaultDispatcher-worker-1 @coroutine#2] Emitting 3
-[main @coroutine#1] Collected 3
--->
-
-Another thing to observe here is that the [flowOn] operator has changed the default sequential nature of the flow.
-Now collection happens in one coroutine ("coroutine#1") and emission happens in another coroutine
-("coroutine#2") that is running in another thread concurrently with the collecting coroutine. The [flowOn] operator
-creates another coroutine for an upstream flow when it has to change the [CoroutineDispatcher] in its context.
-
-### Buffering
-
-Running different parts of a flow in different coroutines can be helpful from the standpoint of the overall time it takes
-to collect the flow, especially when long-running asynchronous operations are involved. For example, consider a case when
-the emission by a `simple` flow is slow, taking 100 ms to produce an element; and collector is also slow,
-taking 300 ms to process an element. Let's see how long it takes to collect such a flow with three numbers:
-
-<div class="sample" markdown="1" theme="idea" data-min-compiler-version="1.3">
-
-```kotlin
-import kotlinx.coroutines.*
-import kotlinx.coroutines.flow.*
-import kotlin.system.*
-
-//sampleStart
-fun simple(): Flow<Int> = flow {
- for (i in 1..3) {
- delay(100) // pretend we are asynchronously waiting 100 ms
- emit(i) // emit next value
- }
-}
-
-fun main() = runBlocking<Unit> {
- val time = measureTimeMillis {
- simple().collect { value ->
- delay(300) // pretend we are processing it for 300 ms
- println(value)
- }
- }
- println("Collected in $time ms")
-}
-//sampleEnd
-```
-
-</div>
-
-> You can get the full code from [here](../kotlinx-coroutines-core/jvm/test/guide/example-flow-16.kt).
-
-It produces something like this, with the whole collection taking around 1200 ms (three numbers, 400 ms for each):
-
-```text
-1
-2
-3
-Collected in 1220 ms
-```
-
-<!--- TEST ARBITRARY_TIME -->
-
-We can use a [buffer] operator on a flow to run emitting code of the `simple` flow concurrently with collecting code,
-as opposed to running them sequentially:
-
-<div class="sample" markdown="1" theme="idea" data-min-compiler-version="1.3">
-
-```kotlin
-import kotlinx.coroutines.*
-import kotlinx.coroutines.flow.*
-import kotlin.system.*
-
-fun simple(): Flow<Int> = flow {
- for (i in 1..3) {
- delay(100) // pretend we are asynchronously waiting 100 ms
- emit(i) // emit next value
- }
-}
-
-fun main() = runBlocking<Unit> {
-//sampleStart
- val time = measureTimeMillis {
- simple()
- .buffer() // buffer emissions, don't wait
- .collect { value ->
- delay(300) // pretend we are processing it for 300 ms
- println(value)
- }
- }
- println("Collected in $time ms")
-//sampleEnd
-}
-```
-
-</div>
-
-> You can get the full code from [here](../kotlinx-coroutines-core/jvm/test/guide/example-flow-17.kt).
-
-It produces the same numbers just faster, as we have effectively created a processing pipeline,
-having to only wait 100 ms for the first number and then spending only 300 ms to process
-each number. This way it takes around 1000 ms to run:
-
-```text
-1
-2
-3
-Collected in 1071 ms
-```
-
-<!--- TEST ARBITRARY_TIME -->
-
-> Note that the [flowOn] operator uses the same buffering mechanism when it has to change a [CoroutineDispatcher],
-but here we explicitly request buffering without changing the execution context.
-
-#### Conflation
-
-When a flow represents partial results of the operation or operation status updates, it may not be necessary
-to process each value, but instead, only most recent ones. In this case, the [conflate] operator can be used to skip
-intermediate values when a collector is too slow to process them. Building on the previous example:
-
-<div class="sample" markdown="1" theme="idea" data-min-compiler-version="1.3">
-
-```kotlin
-import kotlinx.coroutines.*
-import kotlinx.coroutines.flow.*
-import kotlin.system.*
-
-fun simple(): Flow<Int> = flow {
- for (i in 1..3) {
- delay(100) // pretend we are asynchronously waiting 100 ms
- emit(i) // emit next value
- }
-}
-
-fun main() = runBlocking<Unit> {
-//sampleStart
- val time = measureTimeMillis {
- simple()
- .conflate() // conflate emissions, don't process each one
- .collect { value ->
- delay(300) // pretend we are processing it for 300 ms
- println(value)
- }
- }
- println("Collected in $time ms")
-//sampleEnd
-}
-```
-
-</div>
-
-> You can get the full code from [here](../kotlinx-coroutines-core/jvm/test/guide/example-flow-18.kt).
-
-We see that while the first number was still being processed the second, and third were already produced, so
-the second one was _conflated_ and only the most recent (the third one) was delivered to the collector:
-
-```text
-1
-3
-Collected in 758 ms
-```
-
-<!--- TEST ARBITRARY_TIME -->
-
-#### Processing the latest value
-
-Conflation is one way to speed up processing when both the emitter and collector are slow. It does it by dropping emitted values.
-The other way is to cancel a slow collector and restart it every time a new value is emitted. There is
-a family of `xxxLatest` operators that perform the same essential logic of a `xxx` operator, but cancel the
-code in their block on a new value. Let's try changing [conflate] to [collectLatest] in the previous example:
-
-<div class="sample" markdown="1" theme="idea" data-min-compiler-version="1.3">
-
-```kotlin
-import kotlinx.coroutines.*
-import kotlinx.coroutines.flow.*
-import kotlin.system.*
-
-fun simple(): Flow<Int> = flow {
- for (i in 1..3) {
- delay(100) // pretend we are asynchronously waiting 100 ms
- emit(i) // emit next value
- }
-}
-
-fun main() = runBlocking<Unit> {
-//sampleStart
- val time = measureTimeMillis {
- simple()
- .collectLatest { value -> // cancel & restart on the latest value
- println("Collecting $value")
- delay(300) // pretend we are processing it for 300 ms
- println("Done $value")
- }
- }
- println("Collected in $time ms")
-//sampleEnd
-}
-```
-
-</div>
-
-> You can get the full code from [here](../kotlinx-coroutines-core/jvm/test/guide/example-flow-19.kt).
-
-Since the body of [collectLatest] takes 300 ms, but new values are emitted every 100 ms, we see that the block
-is run on every value, but completes only for the last value:
-
-```text
-Collecting 1
-Collecting 2
-Collecting 3
-Done 3
-Collected in 741 ms
-```
-
-<!--- TEST ARBITRARY_TIME -->
-
-### Composing multiple flows
-
-There are lots of ways to compose multiple flows.
-
-#### Zip
-
-Just like the [Sequence.zip] extension function in the Kotlin standard library,
-flows have a [zip] operator that combines the corresponding values of two flows:
-
-<div class="sample" markdown="1" theme="idea" data-min-compiler-version="1.3">
-
-```kotlin
-import kotlinx.coroutines.*
-import kotlinx.coroutines.flow.*
-
-fun main() = runBlocking<Unit> {
-//sampleStart
- val nums = (1..3).asFlow() // numbers 1..3
- val strs = flowOf("one", "two", "three") // strings
- nums.zip(strs) { a, b -> "$a -> $b" } // compose a single string
- .collect { println(it) } // collect and print
-//sampleEnd
-}
-```
-
-</div>
-
-> You can get the full code from [here](../kotlinx-coroutines-core/jvm/test/guide/example-flow-20.kt).
-
-This example prints:
-
-```text
-1 -> one
-2 -> two
-3 -> three
-```
-
-<!--- TEST -->
-
-#### Combine
-
-When flow represents the most recent value of a variable or operation (see also the related
-section on [conflation](#conflation)), it might be needed to perform a computation that depends on
-the most recent values of the corresponding flows and to recompute it whenever any of the upstream
-flows emit a value. The corresponding family of operators is called [combine].
-
-For example, if the numbers in the previous example update every 300ms, but strings update every 400 ms,
-then zipping them using the [zip] operator will still produce the same result,
-albeit results that are printed every 400 ms:
-
-> We use a [onEach] intermediate operator in this example to delay each element and make the code
-that emits sample flows more declarative and shorter.
-
-<div class="sample" markdown="1" theme="idea" data-min-compiler-version="1.3">
-
-```kotlin
-import kotlinx.coroutines.*
-import kotlinx.coroutines.flow.*
-
-fun main() = runBlocking<Unit> {
-//sampleStart
- val nums = (1..3).asFlow().onEach { delay(300) } // numbers 1..3 every 300 ms
- val strs = flowOf("one", "two", "three").onEach { delay(400) } // strings every 400 ms
- val startTime = System.currentTimeMillis() // remember the start time
- nums.zip(strs) { a, b -> "$a -> $b" } // compose a single string with "zip"
- .collect { value -> // collect and print
- println("$value at ${System.currentTimeMillis() - startTime} ms from start")
- }
-//sampleEnd
-}
-```
-
-</div>
-
-> You can get the full code from [here](../kotlinx-coroutines-core/jvm/test/guide/example-flow-21.kt).
-
-<!--- TEST ARBITRARY_TIME
-1 -> one at 437 ms from start
-2 -> two at 837 ms from start
-3 -> three at 1243 ms from start
--->
-
-However, when using a [combine] operator here instead of a [zip]:
-
-<div class="sample" markdown="1" theme="idea" data-min-compiler-version="1.3">
-
-```kotlin
-import kotlinx.coroutines.*
-import kotlinx.coroutines.flow.*
-
-fun main() = runBlocking<Unit> {
-//sampleStart
- val nums = (1..3).asFlow().onEach { delay(300) } // numbers 1..3 every 300 ms
- val strs = flowOf("one", "two", "three").onEach { delay(400) } // strings every 400 ms
- val startTime = System.currentTimeMillis() // remember the start time
- nums.combine(strs) { a, b -> "$a -> $b" } // compose a single string with "combine"
- .collect { value -> // collect and print
- println("$value at ${System.currentTimeMillis() - startTime} ms from start")
- }
-//sampleEnd
-}
-```
-
-</div>
-
-> You can get the full code from [here](../kotlinx-coroutines-core/jvm/test/guide/example-flow-22.kt).
-
-We get quite a different output, where a line is printed at each emission from either `nums` or `strs` flows:
-
-```text
-1 -> one at 452 ms from start
-2 -> one at 651 ms from start
-2 -> two at 854 ms from start
-3 -> two at 952 ms from start
-3 -> three at 1256 ms from start
-```
-
-<!--- TEST ARBITRARY_TIME -->
-
-### Flattening flows
-
-Flows represent asynchronously received sequences of values, so it is quite easy to get in a situation where
-each value triggers a request for another sequence of values. For example, we can have the following
-function that returns a flow of two strings 500 ms apart:
-
-<div class="sample" markdown="1" theme="idea" data-highlight-only>
-
-```kotlin
-fun requestFlow(i: Int): Flow<String> = flow {
- emit("$i: First")
- delay(500) // wait 500 ms
- emit("$i: Second")
-}
-```
-
-</div>
-
-<!--- CLEAR -->
-
-Now if we have a flow of three integers and call `requestFlow` for each of them like this:
-
-<div class="sample" markdown="1" theme="idea" data-highlight-only>
-
-```kotlin
-(1..3).asFlow().map { requestFlow(it) }
-```
-
-</div>
-
-<!--- CLEAR -->
-
-Then we end up with a flow of flows (`Flow<Flow<String>>`) that needs to be _flattened_ into a single flow for
-further processing. Collections and sequences have [flatten][Sequence.flatten] and [flatMap][Sequence.flatMap]
-operators for this. However, due to the asynchronous nature of flows they call for different _modes_ of flattening,
-as such, there is a family of flattening operators on flows.
-
-#### flatMapConcat
-
-Concatenating mode is implemented by [flatMapConcat] and [flattenConcat] operators. They are the most direct
-analogues of the corresponding sequence operators. They wait for the inner flow to complete before
-starting to collect the next one as the following example shows:
-
-<div class="sample" markdown="1" theme="idea" data-min-compiler-version="1.3">
-
-```kotlin
-import kotlinx.coroutines.*
-import kotlinx.coroutines.flow.*
-
-fun requestFlow(i: Int): Flow<String> = flow {
- emit("$i: First")
- delay(500) // wait 500 ms
- emit("$i: Second")
-}
-
-fun main() = runBlocking<Unit> {
-//sampleStart
- val startTime = System.currentTimeMillis() // remember the start time
- (1..3).asFlow().onEach { delay(100) } // a number every 100 ms
- .flatMapConcat { requestFlow(it) }
- .collect { value -> // collect and print
- println("$value at ${System.currentTimeMillis() - startTime} ms from start")
- }
-//sampleEnd
-}
-```
-
-</div>
-
-> You can get the full code from [here](../kotlinx-coroutines-core/jvm/test/guide/example-flow-23.kt).
-
-The sequential nature of [flatMapConcat] is clearly seen in the output:
-
-```text
-1: First at 121 ms from start
-1: Second at 622 ms from start
-2: First at 727 ms from start
-2: Second at 1227 ms from start
-3: First at 1328 ms from start
-3: Second at 1829 ms from start
-```
-
-<!--- TEST ARBITRARY_TIME -->
-
-#### flatMapMerge
-
-Another flattening mode is to concurrently collect all the incoming flows and merge their values into
-a single flow so that values are emitted as soon as possible.
-It is implemented by [flatMapMerge] and [flattenMerge] operators. They both accept an optional
-`concurrency` parameter that limits the number of concurrent flows that are collected at the same time
-(it is equal to [DEFAULT_CONCURRENCY] by default).
-
-<div class="sample" markdown="1" theme="idea" data-min-compiler-version="1.3">
-
-```kotlin
-import kotlinx.coroutines.*
-import kotlinx.coroutines.flow.*
-
-fun requestFlow(i: Int): Flow<String> = flow {
- emit("$i: First")
- delay(500) // wait 500 ms
- emit("$i: Second")
-}
-
-fun main() = runBlocking<Unit> {
-//sampleStart
- val startTime = System.currentTimeMillis() // remember the start time
- (1..3).asFlow().onEach { delay(100) } // a number every 100 ms
- .flatMapMerge { requestFlow(it) }
- .collect { value -> // collect and print
- println("$value at ${System.currentTimeMillis() - startTime} ms from start")
- }
-//sampleEnd
-}
-```
-
-</div>
-
-> You can get the full code from [here](../kotlinx-coroutines-core/jvm/test/guide/example-flow-24.kt).
-
-The concurrent nature of [flatMapMerge] is obvious:
-
-```text
-1: First at 136 ms from start
-2: First at 231 ms from start
-3: First at 333 ms from start
-1: Second at 639 ms from start
-2: Second at 732 ms from start
-3: Second at 833 ms from start
-```
-
-<!--- TEST ARBITRARY_TIME -->
-
-> Note that the [flatMapMerge] calls its block of code (`{ requestFlow(it) }` in this example) sequentially, but
-collects the resulting flows concurrently, it is the equivalent of performing a sequential
-`map { requestFlow(it) }` first and then calling [flattenMerge] on the result.
-
-#### flatMapLatest
-
-In a similar way to the [collectLatest] operator, that was shown in
-["Processing the latest value"](#processing-the-latest-value) section, there is the corresponding "Latest"
-flattening mode where a collection of the previous flow is cancelled as soon as new flow is emitted.
-It is implemented by the [flatMapLatest] operator.
-
-<div class="sample" markdown="1" theme="idea" data-min-compiler-version="1.3">
-
-```kotlin
-import kotlinx.coroutines.*
-import kotlinx.coroutines.flow.*
-
-fun requestFlow(i: Int): Flow<String> = flow {
- emit("$i: First")
- delay(500) // wait 500 ms
- emit("$i: Second")
-}
-
-fun main() = runBlocking<Unit> {
-//sampleStart
- val startTime = System.currentTimeMillis() // remember the start time
- (1..3).asFlow().onEach { delay(100) } // a number every 100 ms
- .flatMapLatest { requestFlow(it) }
- .collect { value -> // collect and print
- println("$value at ${System.currentTimeMillis() - startTime} ms from start")
- }
-//sampleEnd
-}
-```
-
-</div>
-
-> You can get the full code from [here](../kotlinx-coroutines-core/jvm/test/guide/example-flow-25.kt).
-
-The output here in this example is a good demonstration of how [flatMapLatest] works:
-
-```text
-1: First at 142 ms from start
-2: First at 322 ms from start
-3: First at 425 ms from start
-3: Second at 931 ms from start
-```
-
-<!--- TEST ARBITRARY_TIME -->
-
-> Note that [flatMapLatest] cancels all the code in its block (`{ requestFlow(it) }` in this example) on a new value.
-It makes no difference in this particular example, because the call to `requestFlow` itself is fast, not-suspending,
-and cannot be cancelled. However, it would show up if we were to use suspending functions like `delay` in there.
-
-### Flow exceptions
-
-Flow collection can complete with an exception when an emitter or code inside the operators throw an exception.
-There are several ways to handle these exceptions.
-
-#### Collector try and catch
-
-A collector can use Kotlin's [`try/catch`][exceptions] block to handle exceptions:
-
-<div class="sample" markdown="1" theme="idea" data-min-compiler-version="1.3">
-
-```kotlin
-import kotlinx.coroutines.*
-import kotlinx.coroutines.flow.*
-
-//sampleStart
-fun simple(): Flow<Int> = flow {
- for (i in 1..3) {
- println("Emitting $i")
- emit(i) // emit next value
- }
-}
-
-fun main() = runBlocking<Unit> {
- try {
- simple().collect { value ->
- println(value)
- check(value <= 1) { "Collected $value" }
- }
- } catch (e: Throwable) {
- println("Caught $e")
- }
-}
-//sampleEnd
-```
-
-</div>
-
-> You can get the full code from [here](../kotlinx-coroutines-core/jvm/test/guide/example-flow-26.kt).
-
-This code successfully catches an exception in [collect] terminal operator and,
-as we see, no more values are emitted after that:
-
-```text
-Emitting 1
-1
-Emitting 2
-2
-Caught java.lang.IllegalStateException: Collected 2
-```
-
-<!--- TEST -->
-
-#### Everything is caught
-
-The previous example actually catches any exception happening in the emitter or in any intermediate or terminal operators.
-For example, let's change the code so that emitted values are [mapped][map] to strings,
-but the corresponding code produces an exception:
-
-<div class="sample" markdown="1" theme="idea" data-min-compiler-version="1.3">
-
-```kotlin
-import kotlinx.coroutines.*
-import kotlinx.coroutines.flow.*
-
-//sampleStart
-fun simple(): Flow<String> =
- flow {
- for (i in 1..3) {
- println("Emitting $i")
- emit(i) // emit next value
- }
- }
- .map { value ->
- check(value <= 1) { "Crashed on $value" }
- "string $value"
- }
-
-fun main() = runBlocking<Unit> {
- try {
- simple().collect { value -> println(value) }
- } catch (e: Throwable) {
- println("Caught $e")
- }
-}
-//sampleEnd
-```
-
-</div>
-
-> You can get the full code from [here](../kotlinx-coroutines-core/jvm/test/guide/example-flow-27.kt).
-
-This exception is still caught and collection is stopped:
-
-```text
-Emitting 1
-string 1
-Emitting 2
-Caught java.lang.IllegalStateException: Crashed on 2
-```
-
-<!--- TEST -->
-
-### Exception transparency
-
-But how can code of the emitter encapsulate its exception handling behavior?
-
-Flows must be _transparent to exceptions_ and it is a violation of the exception transparency to [emit][FlowCollector.emit] values in the
-`flow { ... }` builder from inside of a `try/catch` block. This guarantees that a collector throwing an exception
-can always catch it using `try/catch` as in the previous example.
-
-The emitter can use a [catch] operator that preserves this exception transparency and allows encapsulation
-of its exception handling. The body of the `catch` operator can analyze an exception
-and react to it in different ways depending on which exception was caught:
-
-* Exceptions can be rethrown using `throw`.
-* Exceptions can be turned into emission of values using [emit][FlowCollector.emit] from the body of [catch].
-* Exceptions can be ignored, logged, or processed by some other code.
-
-For example, let us emit the text on catching an exception:
-
-<div class="sample" markdown="1" theme="idea" data-min-compiler-version="1.3">
-
-```kotlin
-import kotlinx.coroutines.*
-import kotlinx.coroutines.flow.*
-
-fun simple(): Flow<String> =
- flow {
- for (i in 1..3) {
- println("Emitting $i")
- emit(i) // emit next value
- }
- }
- .map { value ->
- check(value <= 1) { "Crashed on $value" }
- "string $value"
- }
-
-fun main() = runBlocking<Unit> {
-//sampleStart
- simple()
- .catch { e -> emit("Caught $e") } // emit on exception
- .collect { value -> println(value) }
-//sampleEnd
-}
-```
-
-</div>
-
-> You can get the full code from [here](../kotlinx-coroutines-core/jvm/test/guide/example-flow-28.kt).
-
-The output of the example is the same, even though we do not have `try/catch` around the code anymore.
-
-<!--- TEST
-Emitting 1
-string 1
-Emitting 2
-Caught java.lang.IllegalStateException: Crashed on 2
--->
-
-#### Transparent catch
-
-The [catch] intermediate operator, honoring exception transparency, catches only upstream exceptions
-(that is an exception from all the operators above `catch`, but not below it).
-If the block in `collect { ... }` (placed below `catch`) throws an exception then it escapes:
-
-<div class="sample" markdown="1" theme="idea" data-min-compiler-version="1.3">
-
-```kotlin
-import kotlinx.coroutines.*
-import kotlinx.coroutines.flow.*
-
-//sampleStart
-fun simple(): Flow<Int> = flow {
- for (i in 1..3) {
- println("Emitting $i")
- emit(i)
- }
-}
-
-fun main() = runBlocking<Unit> {
- simple()
- .catch { e -> println("Caught $e") } // does not catch downstream exceptions
- .collect { value ->
- check(value <= 1) { "Collected $value" }
- println(value)
- }
-}
-//sampleEnd
-```
-
-</div>
-
-> You can get the full code from [here](../kotlinx-coroutines-core/jvm/test/guide/example-flow-29.kt).
-
-A "Caught ..." message is not printed despite there being a `catch` operator:
-
-```text
-Emitting 1
-1
-Emitting 2
-Exception in thread "main" java.lang.IllegalStateException: Collected 2
- at ...
-```
-
-<!--- TEST EXCEPTION -->
-
-#### Catching declaratively
-
-We can combine the declarative nature of the [catch] operator with a desire to handle all the exceptions, by moving the body
-of the [collect] operator into [onEach] and putting it before the `catch` operator. Collection of this flow must
-be triggered by a call to `collect()` without parameters:
-
-<div class="sample" markdown="1" theme="idea" data-min-compiler-version="1.3">
-
-```kotlin
-import kotlinx.coroutines.*
-import kotlinx.coroutines.flow.*
-
-fun simple(): Flow<Int> = flow {
- for (i in 1..3) {
- println("Emitting $i")
- emit(i)
- }
-}
-
-fun main() = runBlocking<Unit> {
-//sampleStart
- simple()
- .onEach { value ->
- check(value <= 1) { "Collected $value" }
- println(value)
- }
- .catch { e -> println("Caught $e") }
- .collect()
-//sampleEnd
-}
-```
-
-</div>
-
-> You can get the full code from [here](../kotlinx-coroutines-core/jvm/test/guide/example-flow-30.kt).
-
-Now we can see that a "Caught ..." message is printed and so we can catch all the exceptions without explicitly
-using a `try/catch` block:
-
-```text
-Emitting 1
-1
-Emitting 2
-Caught java.lang.IllegalStateException: Collected 2
-```
-
-<!--- TEST EXCEPTION -->
-
-### Flow completion
-
-When flow collection completes (normally or exceptionally) it may need to execute an action.
-As you may have already noticed, it can be done in two ways: imperative or declarative.
-
-#### Imperative finally block
-
-In addition to `try`/`catch`, a collector can also use a `finally` block to execute an action
-upon `collect` completion.
-
-<div class="sample" markdown="1" theme="idea" data-min-compiler-version="1.3">
-
-```kotlin
-import kotlinx.coroutines.*
-import kotlinx.coroutines.flow.*
-
-//sampleStart
-fun simple(): Flow<Int> = (1..3).asFlow()
-
-fun main() = runBlocking<Unit> {
- try {
- simple().collect { value -> println(value) }
- } finally {
- println("Done")
- }
-}
-//sampleEnd
-```
-
-</div>
-
-> You can get the full code from [here](../kotlinx-coroutines-core/jvm/test/guide/example-flow-31.kt).
-
-This code prints three numbers produced by the `simple` flow followed by a "Done" string:
-
-```text
-1
-2
-3
-Done
-```
-
-<!--- TEST -->
-
-#### Declarative handling
-
-For the declarative approach, flow has [onCompletion] intermediate operator that is invoked
-when the flow has completely collected.
-
-The previous example can be rewritten using an [onCompletion] operator and produces the same output:
-
-<div class="sample" markdown="1" theme="idea" data-min-compiler-version="1.3">
-
-```kotlin
-import kotlinx.coroutines.*
-import kotlinx.coroutines.flow.*
-
-fun simple(): Flow<Int> = (1..3).asFlow()
-
-fun main() = runBlocking<Unit> {
-//sampleStart
- simple()
- .onCompletion { println("Done") }
- .collect { value -> println(value) }
-//sampleEnd
-}
-```
-</div>
-
-> You can get the full code from [here](../kotlinx-coroutines-core/jvm/test/guide/example-flow-32.kt).
-
-<!--- TEST
-1
-2
-3
-Done
--->
-
-The key advantage of [onCompletion] is a nullable `Throwable` parameter of the lambda that can be used
-to determine whether the flow collection was completed normally or exceptionally. In the following
-example the `simple` flow throws an exception after emitting the number 1:
-
-<div class="sample" markdown="1" theme="idea" data-min-compiler-version="1.3">
-
-```kotlin
-import kotlinx.coroutines.*
-import kotlinx.coroutines.flow.*
-
-//sampleStart
-fun simple(): Flow<Int> = flow {
- emit(1)
- throw RuntimeException()
-}
-
-fun main() = runBlocking<Unit> {
- simple()
- .onCompletion { cause -> if (cause != null) println("Flow completed exceptionally") }
- .catch { cause -> println("Caught exception") }
- .collect { value -> println(value) }
-}
-//sampleEnd
-```
-</div>
-
-> You can get the full code from [here](../kotlinx-coroutines-core/jvm/test/guide/example-flow-33.kt).
-
-As you may expect, it prints:
-
-```text
-1
-Flow completed exceptionally
-Caught exception
-```
-
-<!--- TEST -->
-
-The [onCompletion] operator, unlike [catch], does not handle the exception. As we can see from the above
-example code, the exception still flows downstream. It will be delivered to further `onCompletion` operators
-and can be handled with a `catch` operator.
-
-#### Successful completion
-
-Another difference with [catch] operator is that [onCompletion] sees all exceptions and receives
-a `null` exception only on successful completion of the upstream flow (without cancellation or failure).
-
-<div class="sample" markdown="1" theme="idea" data-min-compiler-version="1.3">
-
-```kotlin
-import kotlinx.coroutines.*
-import kotlinx.coroutines.flow.*
-
-//sampleStart
-fun simple(): Flow<Int> = (1..3).asFlow()
-
-fun main() = runBlocking<Unit> {
- simple()
- .onCompletion { cause -> println("Flow completed with $cause") }
- .collect { value ->
- check(value <= 1) { "Collected $value" }
- println(value)
- }
-}
-//sampleEnd
-```
-
-</div>
-
-> You can get the full code from [here](../kotlinx-coroutines-core/jvm/test/guide/example-flow-34.kt).
-
-We can see the completion cause is not null, because the flow was aborted due to downstream exception:
-
-```text
-1
-Flow completed with java.lang.IllegalStateException: Collected 2
-Exception in thread "main" java.lang.IllegalStateException: Collected 2
-```
-
-<!--- TEST EXCEPTION -->
-
-### Imperative versus declarative
-
-Now we know how to collect flow, and handle its completion and exceptions in both imperative and declarative ways.
-The natural question here is, which approach is preferred and why?
-As a library, we do not advocate for any particular approach and believe that both options
-are valid and should be selected according to your own preferences and code style.
-
-### Launching flow
-
-It is easy to use flows to represent asynchronous events that are coming from some source.
-In this case, we need an analogue of the `addEventListener` function that registers a piece of code with a reaction
-for incoming events and continues further work. The [onEach] operator can serve this role.
-However, `onEach` is an intermediate operator. We also need a terminal operator to collect the flow.
-Otherwise, just calling `onEach` has no effect.
-
-If we use the [collect] terminal operator after `onEach`, then the code after it will wait until the flow is collected:
-
-<div class="sample" markdown="1" theme="idea" data-min-compiler-version="1.3">
-
-```kotlin
-import kotlinx.coroutines.*
-import kotlinx.coroutines.flow.*
-
-//sampleStart
-// Imitate a flow of events
-fun events(): Flow<Int> = (1..3).asFlow().onEach { delay(100) }
-
-fun main() = runBlocking<Unit> {
- events()
- .onEach { event -> println("Event: $event") }
- .collect() // <--- Collecting the flow waits
- println("Done")
-}
-//sampleEnd
-```
-
-</div>
-
-> You can get the full code from [here](../kotlinx-coroutines-core/jvm/test/guide/example-flow-35.kt).
-
-As you can see, it prints:
-
-```text
-Event: 1
-Event: 2
-Event: 3
-Done
-```
-
-<!--- TEST -->
-
-The [launchIn] terminal operator comes in handy here. By replacing `collect` with `launchIn` we can
-launch a collection of the flow in a separate coroutine, so that execution of further code
-immediately continues:
-
-<div class="sample" markdown="1" theme="idea" data-min-compiler-version="1.3">
-
-```kotlin
-import kotlinx.coroutines.*
-import kotlinx.coroutines.flow.*
-
-// Imitate a flow of events
-fun events(): Flow<Int> = (1..3).asFlow().onEach { delay(100) }
-
-//sampleStart
-fun main() = runBlocking<Unit> {
- events()
- .onEach { event -> println("Event: $event") }
- .launchIn(this) // <--- Launching the flow in a separate coroutine
- println("Done")
-}
-//sampleEnd
-```
-
-</div>
-
-> You can get the full code from [here](../kotlinx-coroutines-core/jvm/test/guide/example-flow-36.kt).
-
-It prints:
-
-```text
-Done
-Event: 1
-Event: 2
-Event: 3
-```
-
-<!--- TEST -->
-
-The required parameter to `launchIn` must specify a [CoroutineScope] in which the coroutine to collect the flow is
-launched. In the above example this scope comes from the [runBlocking]
-coroutine builder, so while the flow is running, this [runBlocking] scope waits for completion of its child coroutine
-and keeps the main function from returning and terminating this example.
-
-In actual applications a scope will come from an entity with a limited
-lifetime. As soon as the lifetime of this entity is terminated the corresponding scope is cancelled, cancelling
-the collection of the corresponding flow. This way the pair of `onEach { ... }.launchIn(scope)` works
-like the `addEventListener`. However, there is no need for the corresponding `removeEventListener` function,
-as cancellation and structured concurrency serve this purpose.
-
-Note that [launchIn] also returns a [Job], which can be used to [cancel][Job.cancel] the corresponding flow collection
-coroutine only without cancelling the whole scope or to [join][Job.join] it.
-
-### Flow cancellation checks
-
-For convenience, the [flow][_flow] builder performs additional [ensureActive] checks for cancellation on each emitted value.
-It means that a busy loop emitting from a `flow { ... }` is cancellable:
-
-<div class="sample" markdown="1" theme="idea" data-min-compiler-version="1.3">
-
-```kotlin
-import kotlinx.coroutines.*
-import kotlinx.coroutines.flow.*
-
-//sampleStart
-fun foo(): Flow<Int> = flow {
- for (i in 1..5) {
- println("Emitting $i")
- emit(i)
- }
-}
-
-fun main() = runBlocking<Unit> {
- foo().collect { value ->
- if (value == 3) cancel()
- println(value)
- }
-}
-//sampleEnd
-```
-
-</div>
-
-> You can get the full code from [here](../kotlinx-coroutines-core/jvm/test/guide/example-flow-37.kt).
-
-We get only numbers up to 3 and a [CancellationException] after trying to emit number 4:
-
-```text
-Emitting 1
-1
-Emitting 2
-2
-Emitting 3
-3
-Emitting 4
-Exception in thread "main" kotlinx.coroutines.JobCancellationException: BlockingCoroutine was cancelled; job="coroutine#1":BlockingCoroutine{Cancelled}@6d7b4f4c
-```
-
-<!--- TEST EXCEPTION -->
-
-However, most other flow operators do not do additional cancellation checks on their own for performance reasons.
-For example, if you use [IntRange.asFlow] extension to write the same busy loop and don't suspend anywhere,
-then there are no checks for cancellation:
-
-<div class="sample" markdown="1" theme="idea" data-min-compiler-version="1.3">
-
-```kotlin
-import kotlinx.coroutines.*
-import kotlinx.coroutines.flow.*
-
-//sampleStart
-fun main() = runBlocking<Unit> {
- (1..5).asFlow().collect { value ->
- if (value == 3) cancel()
- println(value)
- }
-}
-//sampleEnd
-```
-
-</div>
-
-> You can get the full code from [here](../kotlinx-coroutines-core/jvm/test/guide/example-flow-38.kt).
-
-All numbers from 1 to 5 are collected and cancellation gets detected only before return from `runBlocking`:
-
-```text
-1
-2
-3
-4
-5
-Exception in thread "main" kotlinx.coroutines.JobCancellationException: BlockingCoroutine was cancelled; job="coroutine#1":BlockingCoroutine{Cancelled}@3327bd23
-```
-
-<!--- TEST EXCEPTION -->
-
-#### Making busy flow cancellable
-
-In the case where you have a busy loop with coroutines you must explicitly check for cancellation.
-You can add `.onEach { currentCoroutineContext().ensureActive() }`, but there is a ready-to-use
-[cancellable] operator provided to do that:
-
-<div class="sample" markdown="1" theme="idea" data-min-compiler-version="1.3">
-
-```kotlin
-import kotlinx.coroutines.*
-import kotlinx.coroutines.flow.*
-
-//sampleStart
-fun main() = runBlocking<Unit> {
- (1..5).asFlow().cancellable().collect { value ->
- if (value == 3) cancel()
- println(value)
- }
-}
-//sampleEnd
-```
-
-</div>
-
-> You can get the full code from [here](../kotlinx-coroutines-core/jvm/test/guide/example-flow-39.kt).
-
-With the `cancellable` operator only the numbers from 1 to 3 are collected:
-
-```text
-1
-2
-3
-Exception in thread "main" kotlinx.coroutines.JobCancellationException: BlockingCoroutine was cancelled; job="coroutine#1":BlockingCoroutine{Cancelled}@5ec0a365
-```
-
-<!--- TEST EXCEPTION -->
-
-### Flow and Reactive Streams
-
-For those who are familiar with [Reactive Streams](https://www.reactive-streams.org/) or reactive frameworks such as RxJava and project Reactor,
-design of the Flow may look very familiar.
-
-Indeed, its design was inspired by Reactive Streams and its various implementations. But Flow main goal is to have as simple design as possible,
-be Kotlin and suspension friendly and respect structured concurrency. Achieving this goal would be impossible without reactive pioneers and their tremendous work. You can read the complete story in [Reactive Streams and Kotlin Flows](https://medium.com/@elizarov/reactive-streams-and-kotlin-flows-bfd12772cda4) article.
-
-While being different, conceptually, Flow *is* a reactive stream and it is possible to convert it to the reactive (spec and TCK compliant) Publisher and vice versa.
-Such converters are provided by `kotlinx.coroutines` out-of-the-box and can be found in corresponding reactive modules (`kotlinx-coroutines-reactive` for Reactive Streams, `kotlinx-coroutines-reactor` for Project Reactor and `kotlinx-coroutines-rx2`/`kotlinx-coroutines-rx3` for RxJava2/RxJava3).
-Integration modules include conversions from and to `Flow`, integration with Reactor's `Context` and suspension-friendly ways to work with various reactive entities.
-
-<!-- stdlib references -->
-
-[collections]: https://kotlinlang.org/docs/reference/collections-overview.html
-[List]: https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/-list/index.html
-[forEach]: https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/for-each.html
-[Sequence]: https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.sequences/index.html
-[Sequence.zip]: https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.sequences/zip.html
-[Sequence.flatten]: https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.sequences/flatten.html
-[Sequence.flatMap]: https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.sequences/flat-map.html
-[exceptions]: https://kotlinlang.org/docs/reference/exceptions.html
-
-<!--- MODULE kotlinx-coroutines-core -->
-<!--- INDEX kotlinx.coroutines -->
-[delay]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/delay.html
-[withTimeoutOrNull]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/with-timeout-or-null.html
-[Dispatchers.Default]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-dispatchers/-default.html
-[Dispatchers.Main]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-dispatchers/-main.html
-[withContext]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/with-context.html
-[CoroutineDispatcher]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-dispatcher/index.html
-[CoroutineScope]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-scope/index.html
-[runBlocking]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/run-blocking.html
-[Job]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job/index.html
-[Job.cancel]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job/cancel.html
-[Job.join]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job/join.html
-[ensureActive]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/ensure-active.html
-[CancellationException]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-cancellation-exception/index.html
-<!--- INDEX kotlinx.coroutines.flow -->
-[Flow]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/-flow/index.html
-[_flow]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/flow.html
-[FlowCollector.emit]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/-flow-collector/emit.html
-[collect]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/collect.html
-[flowOf]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/flow-of.html
-[map]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/map.html
-[filter]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/filter.html
-[transform]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/transform.html
-[take]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/take.html
-[toList]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/to-list.html
-[toSet]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/to-set.html
-[first]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/first.html
-[single]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/single.html
-[reduce]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/reduce.html
-[fold]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/fold.html
-[flowOn]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/flow-on.html
-[buffer]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/buffer.html
-[conflate]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/conflate.html
-[collectLatest]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/collect-latest.html
-[zip]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/zip.html
-[combine]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/combine.html
-[onEach]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/on-each.html
-[flatMapConcat]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/flat-map-concat.html
-[flattenConcat]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/flatten-concat.html
-[flatMapMerge]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/flat-map-merge.html
-[flattenMerge]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/flatten-merge.html
-[DEFAULT_CONCURRENCY]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/-d-e-f-a-u-l-t_-c-o-n-c-u-r-r-e-n-c-y.html
-[flatMapLatest]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/flat-map-latest.html
-[catch]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/catch.html
-[onCompletion]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/on-completion.html
-[launchIn]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/launch-in.html
-[IntRange.asFlow]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/kotlin.ranges.-int-range/as-flow.html
-[cancellable]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/cancellable.html
-<!--- END -->
+To edit the documentation, open the [topics/flow.md](topics/flow.md) page. \ No newline at end of file
diff --git a/docs/images/coroutine-breakpoint.png b/docs/images/coroutine-breakpoint.png
new file mode 100644
index 00000000..b547e77d
--- /dev/null
+++ b/docs/images/coroutine-breakpoint.png
Binary files differ
diff --git a/docs/images/coroutine-debug-1.png b/docs/images/coroutine-debug-1.png
new file mode 100644
index 00000000..d92d0ff1
--- /dev/null
+++ b/docs/images/coroutine-debug-1.png
Binary files differ
diff --git a/docs/images/coroutine-debug-2.png b/docs/images/coroutine-debug-2.png
new file mode 100644
index 00000000..56f19c3d
--- /dev/null
+++ b/docs/images/coroutine-debug-2.png
Binary files differ
diff --git a/docs/images/coroutine-debug-3.png b/docs/images/coroutine-debug-3.png
new file mode 100644
index 00000000..43a1460a
--- /dev/null
+++ b/docs/images/coroutine-debug-3.png
Binary files differ
diff --git a/docs/images/flow-breakpoint.png b/docs/images/flow-breakpoint.png
new file mode 100644
index 00000000..aa98e18e
--- /dev/null
+++ b/docs/images/flow-breakpoint.png
Binary files differ
diff --git a/docs/images/flow-build-project.png b/docs/images/flow-build-project.png
new file mode 100644
index 00000000..22186213
--- /dev/null
+++ b/docs/images/flow-build-project.png
Binary files differ
diff --git a/docs/images/flow-debug-1.png b/docs/images/flow-debug-1.png
new file mode 100644
index 00000000..8a16984a
--- /dev/null
+++ b/docs/images/flow-debug-1.png
Binary files differ
diff --git a/docs/images/flow-debug-2.png b/docs/images/flow-debug-2.png
new file mode 100644
index 00000000..d06c86bf
--- /dev/null
+++ b/docs/images/flow-debug-2.png
Binary files differ
diff --git a/docs/images/flow-debug-3.png b/docs/images/flow-debug-3.png
new file mode 100644
index 00000000..af082c05
--- /dev/null
+++ b/docs/images/flow-debug-3.png
Binary files differ
diff --git a/docs/images/flow-debug-4.png b/docs/images/flow-debug-4.png
new file mode 100644
index 00000000..8d1c42df
--- /dev/null
+++ b/docs/images/flow-debug-4.png
Binary files differ
diff --git a/docs/images/flow-debug-project.png b/docs/images/flow-debug-project.png
new file mode 100644
index 00000000..98d392e2
--- /dev/null
+++ b/docs/images/flow-debug-project.png
Binary files differ
diff --git a/docs/images/flow-resume-debug.png b/docs/images/flow-resume-debug.png
new file mode 100644
index 00000000..adb12494
--- /dev/null
+++ b/docs/images/flow-resume-debug.png
Binary files differ
diff --git a/docs/images/new-gradle-project-jvm.png b/docs/images/new-gradle-project-jvm.png
new file mode 100644
index 00000000..108ce758
--- /dev/null
+++ b/docs/images/new-gradle-project-jvm.png
Binary files differ
diff --git a/docs/kc.tree b/docs/kc.tree
new file mode 100644
index 00000000..82877f44
--- /dev/null
+++ b/docs/kc.tree
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!DOCTYPE product-profile
+ SYSTEM "https://resources.jetbrains.com/stardust/product-profile.dtd">
+
+<product-profile id="kc"
+ name="Kotlin coroutines"
+ start-page="coroutines-guide.md">
+
+ <chunk include-id="coroutines">
+ <toc-element id="coroutines-guide.md"/>
+ <toc-element id="coroutines-basics.md" accepts-web-file-names="basics.html,coroutines-basic-jvm.html"/>
+ <toc-element toc-title="Intro to coroutines and channels – hands-on tutorial" href="https://play.kotlinlang.org/hands-on/Introduction%20to%20Coroutines%20and%20Channels/"/>
+ <toc-element id="cancellation-and-timeouts.md"/>
+ <toc-element id="composing-suspending-functions.md"/>
+ <toc-element id="coroutine-context-and-dispatchers.md"/>
+ <toc-element id="flow.md"/>
+ <toc-element id="channels.md"/>
+ <toc-element id="exception-handling.md"/>
+ <toc-element id="shared-mutable-state-and-concurrency.md"/>
+ <toc-element id="select-expression.md"/>
+ <toc-element id="debug-coroutines-with-idea.md"/>
+ <toc-element id="debug-flow-with-idea.md"/>
+ </chunk>
+</product-profile>
diff --git a/docs/knit.properties b/docs/knit.properties
deleted file mode 100644
index 2028ecb4..00000000
--- a/docs/knit.properties
+++ /dev/null
@@ -1,10 +0,0 @@
-#
-# Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
-#
-
-knit.package=kotlinx.coroutines.guide
-knit.dir=../kotlinx-coroutines-core/jvm/test/guide/
-
-test.package=kotlinx.coroutines.guide.test
-test.dir=../kotlinx-coroutines-core/jvm/test/guide/test/
-
diff --git a/docs/project.ihp b/docs/project.ihp
new file mode 100644
index 00000000..d8da718e
--- /dev/null
+++ b/docs/project.ihp
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE ihp SYSTEM "https://resources.jetbrains.com/stardust/ihp.dtd">
+
+<ihp version="2.0">
+ <categories src="c.list"/>
+ <module name="coroutines"/>
+ <topics dir="topics"/>
+ <images dir="images" web-path="/img/kotlin-coroutines/"/>
+ <vars src="v.list"/>
+ <product version="%coroutinesVersion%" id="help/kotlin-coroutines" src="kc.tree" web-path="/kotlin-coroutines/"/>
+ <settings>
+ <default-property element-name="toc-element" property-name="show-structure-depth" value="2"/>
+ </settings>
+</ihp> \ No newline at end of file
diff --git a/docs/select-expression.md b/docs/select-expression.md
index f0e5ae46..d0451680 100644
--- a/docs/select-expression.md
+++ b/docs/select-expression.md
@@ -1,549 +1,3 @@
-<!--- TEST_NAME SelectGuideTest -->
+The documentation has been moved to the [https://kotlinlang.org/docs/select-expression.html](https://kotlinlang.org/docs/select-expression.html) page.
-**Table of contents**
-
-<!--- TOC -->
-
-* [Select Expression (experimental)](#select-expression-experimental)
- * [Selecting from channels](#selecting-from-channels)
- * [Selecting on close](#selecting-on-close)
- * [Selecting to send](#selecting-to-send)
- * [Selecting deferred values](#selecting-deferred-values)
- * [Switch over a channel of deferred values](#switch-over-a-channel-of-deferred-values)
-
-<!--- END -->
-
-## Select Expression (experimental)
-
-Select expression makes it possible to await multiple suspending functions simultaneously and _select_
-the first one that becomes available.
-
-> Select expressions are an experimental feature of `kotlinx.coroutines`. Their API is expected to
-evolve in the upcoming updates of the `kotlinx.coroutines` library with potentially
-breaking changes.
-
-### Selecting from channels
-
-Let us have two producers of strings: `fizz` and `buzz`. The `fizz` produces "Fizz" string every 300 ms:
-
-<div class="sample" markdown="1" theme="idea" data-highlight-only>
-
-```kotlin
-fun CoroutineScope.fizz() = produce<String> {
- while (true) { // sends "Fizz" every 300 ms
- delay(300)
- send("Fizz")
- }
-}
-```
-
-</div>
-
-And the `buzz` produces "Buzz!" string every 500 ms:
-
-<div class="sample" markdown="1" theme="idea" data-highlight-only>
-
-```kotlin
-fun CoroutineScope.buzz() = produce<String> {
- while (true) { // sends "Buzz!" every 500 ms
- delay(500)
- send("Buzz!")
- }
-}
-```
-
-</div>
-
-Using [receive][ReceiveChannel.receive] suspending function we can receive _either_ from one channel or the
-other. But [select] expression allows us to receive from _both_ simultaneously using its
-[onReceive][ReceiveChannel.onReceive] clauses:
-
-<div class="sample" markdown="1" theme="idea" data-highlight-only>
-
-```kotlin
-suspend fun selectFizzBuzz(fizz: ReceiveChannel<String>, buzz: ReceiveChannel<String>) {
- select<Unit> { // <Unit> means that this select expression does not produce any result
- fizz.onReceive { value -> // this is the first select clause
- println("fizz -> '$value'")
- }
- buzz.onReceive { value -> // this is the second select clause
- println("buzz -> '$value'")
- }
- }
-}
-```
-
-</div>
-
-Let us run it all seven times:
-
-<!--- CLEAR -->
-
-<div class="sample" markdown="1" theme="idea" data-min-compiler-version="1.3">
-
-```kotlin
-import kotlinx.coroutines.*
-import kotlinx.coroutines.channels.*
-import kotlinx.coroutines.selects.*
-
-fun CoroutineScope.fizz() = produce<String> {
- while (true) { // sends "Fizz" every 300 ms
- delay(300)
- send("Fizz")
- }
-}
-
-fun CoroutineScope.buzz() = produce<String> {
- while (true) { // sends "Buzz!" every 500 ms
- delay(500)
- send("Buzz!")
- }
-}
-
-suspend fun selectFizzBuzz(fizz: ReceiveChannel<String>, buzz: ReceiveChannel<String>) {
- select<Unit> { // <Unit> means that this select expression does not produce any result
- fizz.onReceive { value -> // this is the first select clause
- println("fizz -> '$value'")
- }
- buzz.onReceive { value -> // this is the second select clause
- println("buzz -> '$value'")
- }
- }
-}
-
-fun main() = runBlocking<Unit> {
-//sampleStart
- val fizz = fizz()
- val buzz = buzz()
- repeat(7) {
- selectFizzBuzz(fizz, buzz)
- }
- coroutineContext.cancelChildren() // cancel fizz & buzz coroutines
-//sampleEnd
-}
-```
-
-</div>
-
-> You can get the full code [here](../kotlinx-coroutines-core/jvm/test/guide/example-select-01.kt).
-
-The result of this code is:
-
-```text
-fizz -> 'Fizz'
-buzz -> 'Buzz!'
-fizz -> 'Fizz'
-fizz -> 'Fizz'
-buzz -> 'Buzz!'
-fizz -> 'Fizz'
-buzz -> 'Buzz!'
-```
-
-<!--- TEST -->
-
-### Selecting on close
-
-The [onReceive][ReceiveChannel.onReceive] clause in `select` fails when the channel is closed causing the corresponding
-`select` to throw an exception. We can use [onReceiveOrNull][onReceiveOrNull] clause to perform a
-specific action when the channel is closed. The following example also shows that `select` is an expression that returns
-the result of its selected clause:
-
-<div class="sample" markdown="1" theme="idea" data-highlight-only>
-
-```kotlin
-suspend fun selectAorB(a: ReceiveChannel<String>, b: ReceiveChannel<String>): String =
- select<String> {
- a.onReceiveOrNull { value ->
- if (value == null)
- "Channel 'a' is closed"
- else
- "a -> '$value'"
- }
- b.onReceiveOrNull { value ->
- if (value == null)
- "Channel 'b' is closed"
- else
- "b -> '$value'"
- }
- }
-```
-
-</div>
-
-Note that [onReceiveOrNull][onReceiveOrNull] is an extension function defined only
-for channels with non-nullable elements so that there is no accidental confusion between a closed channel
-and a null value.
-
-Let's use it with channel `a` that produces "Hello" string four times and
-channel `b` that produces "World" four times:
-
-<!--- CLEAR -->
-
-<div class="sample" markdown="1" theme="idea" data-min-compiler-version="1.3">
-
-```kotlin
-import kotlinx.coroutines.*
-import kotlinx.coroutines.channels.*
-import kotlinx.coroutines.selects.*
-
-suspend fun selectAorB(a: ReceiveChannel<String>, b: ReceiveChannel<String>): String =
- select<String> {
- a.onReceiveOrNull { value ->
- if (value == null)
- "Channel 'a' is closed"
- else
- "a -> '$value'"
- }
- b.onReceiveOrNull { value ->
- if (value == null)
- "Channel 'b' is closed"
- else
- "b -> '$value'"
- }
- }
-
-fun main() = runBlocking<Unit> {
-//sampleStart
- val a = produce<String> {
- repeat(4) { send("Hello $it") }
- }
- val b = produce<String> {
- repeat(4) { send("World $it") }
- }
- repeat(8) { // print first eight results
- println(selectAorB(a, b))
- }
- coroutineContext.cancelChildren()
-//sampleEnd
-}
-```
-
-</div>
-
-> You can get the full code [here](../kotlinx-coroutines-core/jvm/test/guide/example-select-02.kt).
-
-The result of this code is quite interesting, so we'll analyze it in mode detail:
-
-```text
-a -> 'Hello 0'
-a -> 'Hello 1'
-b -> 'World 0'
-a -> 'Hello 2'
-a -> 'Hello 3'
-b -> 'World 1'
-Channel 'a' is closed
-Channel 'a' is closed
-```
-
-<!--- TEST -->
-
-There are couple of observations to make out of it.
-
-First of all, `select` is _biased_ to the first clause. When several clauses are selectable at the same time,
-the first one among them gets selected. Here, both channels are constantly producing strings, so `a` channel,
-being the first clause in select, wins. However, because we are using unbuffered channel, the `a` gets suspended from
-time to time on its [send][SendChannel.send] invocation and gives a chance for `b` to send, too.
-
-The second observation, is that [onReceiveOrNull][onReceiveOrNull] gets immediately selected when the
-channel is already closed.
-
-### Selecting to send
-
-Select expression has [onSend][SendChannel.onSend] clause that can be used for a great good in combination
-with a biased nature of selection.
-
-Let us write an example of producer of integers that sends its values to a `side` channel when
-the consumers on its primary channel cannot keep up with it:
-
-<div class="sample" markdown="1" theme="idea" data-highlight-only>
-
-```kotlin
-fun CoroutineScope.produceNumbers(side: SendChannel<Int>) = produce<Int> {
- for (num in 1..10) { // produce 10 numbers from 1 to 10
- delay(100) // every 100 ms
- select<Unit> {
- onSend(num) {} // Send to the primary channel
- side.onSend(num) {} // or to the side channel
- }
- }
-}
-```
-
-</div>
-
-Consumer is going to be quite slow, taking 250 ms to process each number:
-
-<!--- CLEAR -->
-
-<div class="sample" markdown="1" theme="idea" data-min-compiler-version="1.3">
-
-```kotlin
-import kotlinx.coroutines.*
-import kotlinx.coroutines.channels.*
-import kotlinx.coroutines.selects.*
-
-fun CoroutineScope.produceNumbers(side: SendChannel<Int>) = produce<Int> {
- for (num in 1..10) { // produce 10 numbers from 1 to 10
- delay(100) // every 100 ms
- select<Unit> {
- onSend(num) {} // Send to the primary channel
- side.onSend(num) {} // or to the side channel
- }
- }
-}
-
-fun main() = runBlocking<Unit> {
-//sampleStart
- val side = Channel<Int>() // allocate side channel
- launch { // this is a very fast consumer for the side channel
- side.consumeEach { println("Side channel has $it") }
- }
- produceNumbers(side).consumeEach {
- println("Consuming $it")
- delay(250) // let us digest the consumed number properly, do not hurry
- }
- println("Done consuming")
- coroutineContext.cancelChildren()
-//sampleEnd
-}
-```
-
-</div>
-
-> You can get the full code [here](../kotlinx-coroutines-core/jvm/test/guide/example-select-03.kt).
-
-So let us see what happens:
-
-```text
-Consuming 1
-Side channel has 2
-Side channel has 3
-Consuming 4
-Side channel has 5
-Side channel has 6
-Consuming 7
-Side channel has 8
-Side channel has 9
-Consuming 10
-Done consuming
-```
-
-<!--- TEST -->
-
-### Selecting deferred values
-
-Deferred values can be selected using [onAwait][Deferred.onAwait] clause.
-Let us start with an async function that returns a deferred string value after
-a random delay:
-
-<div class="sample" markdown="1" theme="idea" data-highlight-only>
-
-```kotlin
-fun CoroutineScope.asyncString(time: Int) = async {
- delay(time.toLong())
- "Waited for $time ms"
-}
-```
-
-</div>
-
-Let us start a dozen of them with a random delay.
-
-<div class="sample" markdown="1" theme="idea" data-highlight-only>
-
-```kotlin
-fun CoroutineScope.asyncStringsList(): List<Deferred<String>> {
- val random = Random(3)
- return List(12) { asyncString(random.nextInt(1000)) }
-}
-```
-
-</div>
-
-Now the main function awaits for the first of them to complete and counts the number of deferred values
-that are still active. Note that we've used here the fact that `select` expression is a Kotlin DSL,
-so we can provide clauses for it using an arbitrary code. In this case we iterate over a list
-of deferred values to provide `onAwait` clause for each deferred value.
-
-<!--- CLEAR -->
-
-<div class="sample" markdown="1" theme="idea" data-min-compiler-version="1.3">
-
-```kotlin
-import kotlinx.coroutines.*
-import kotlinx.coroutines.selects.*
-import java.util.*
-
-fun CoroutineScope.asyncString(time: Int) = async {
- delay(time.toLong())
- "Waited for $time ms"
-}
-
-fun CoroutineScope.asyncStringsList(): List<Deferred<String>> {
- val random = Random(3)
- return List(12) { asyncString(random.nextInt(1000)) }
-}
-
-fun main() = runBlocking<Unit> {
-//sampleStart
- val list = asyncStringsList()
- val result = select<String> {
- list.withIndex().forEach { (index, deferred) ->
- deferred.onAwait { answer ->
- "Deferred $index produced answer '$answer'"
- }
- }
- }
- println(result)
- val countActive = list.count { it.isActive }
- println("$countActive coroutines are still active")
-//sampleEnd
-}
-```
-
-</div>
-
-> You can get the full code [here](../kotlinx-coroutines-core/jvm/test/guide/example-select-04.kt).
-
-The output is:
-
-```text
-Deferred 4 produced answer 'Waited for 128 ms'
-11 coroutines are still active
-```
-
-<!--- TEST -->
-
-### Switch over a channel of deferred values
-
-Let us write a channel producer function that consumes a channel of deferred string values, waits for each received
-deferred value, but only until the next deferred value comes over or the channel is closed. This example puts together
-[onReceiveOrNull][onReceiveOrNull] and [onAwait][Deferred.onAwait] clauses in the same `select`:
-
-<div class="sample" markdown="1" theme="idea" data-highlight-only>
-
-```kotlin
-fun CoroutineScope.switchMapDeferreds(input: ReceiveChannel<Deferred<String>>) = produce<String> {
- var current = input.receive() // start with first received deferred value
- while (isActive) { // loop while not cancelled/closed
- val next = select<Deferred<String>?> { // return next deferred value from this select or null
- input.onReceiveOrNull { update ->
- update // replaces next value to wait
- }
- current.onAwait { value ->
- send(value) // send value that current deferred has produced
- input.receiveOrNull() // and use the next deferred from the input channel
- }
- }
- if (next == null) {
- println("Channel was closed")
- break // out of loop
- } else {
- current = next
- }
- }
-}
-```
-
-</div>
-
-To test it, we'll use a simple async function that resolves to a specified string after a specified time:
-
-
-<div class="sample" markdown="1" theme="idea" data-highlight-only>
-
-```kotlin
-fun CoroutineScope.asyncString(str: String, time: Long) = async {
- delay(time)
- str
-}
-```
-
-</div>
-
-The main function just launches a coroutine to print results of `switchMapDeferreds` and sends some test
-data to it:
-
-<!--- CLEAR -->
-
-<div class="sample" markdown="1" theme="idea" data-min-compiler-version="1.3">
-
-```kotlin
-import kotlinx.coroutines.*
-import kotlinx.coroutines.channels.*
-import kotlinx.coroutines.selects.*
-
-fun CoroutineScope.switchMapDeferreds(input: ReceiveChannel<Deferred<String>>) = produce<String> {
- var current = input.receive() // start with first received deferred value
- while (isActive) { // loop while not cancelled/closed
- val next = select<Deferred<String>?> { // return next deferred value from this select or null
- input.onReceiveOrNull { update ->
- update // replaces next value to wait
- }
- current.onAwait { value ->
- send(value) // send value that current deferred has produced
- input.receiveOrNull() // and use the next deferred from the input channel
- }
- }
- if (next == null) {
- println("Channel was closed")
- break // out of loop
- } else {
- current = next
- }
- }
-}
-
-fun CoroutineScope.asyncString(str: String, time: Long) = async {
- delay(time)
- str
-}
-
-fun main() = runBlocking<Unit> {
-//sampleStart
- val chan = Channel<Deferred<String>>() // the channel for test
- launch { // launch printing coroutine
- for (s in switchMapDeferreds(chan))
- println(s) // print each received string
- }
- chan.send(asyncString("BEGIN", 100))
- delay(200) // enough time for "BEGIN" to be produced
- chan.send(asyncString("Slow", 500))
- delay(100) // not enough time to produce slow
- chan.send(asyncString("Replace", 100))
- delay(500) // give it time before the last one
- chan.send(asyncString("END", 500))
- delay(1000) // give it time to process
- chan.close() // close the channel ...
- delay(500) // and wait some time to let it finish
-//sampleEnd
-}
-```
-
-</div>
-
-> You can get the full code [here](../kotlinx-coroutines-core/jvm/test/guide/example-select-05.kt).
-
-The result of this code:
-
-```text
-BEGIN
-Replace
-END
-Channel was closed
-```
-
-<!--- TEST -->
-
-<!--- MODULE kotlinx-coroutines-core -->
-<!--- INDEX kotlinx.coroutines -->
-[Deferred.onAwait]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-deferred/on-await.html
-<!--- INDEX kotlinx.coroutines.channels -->
-[ReceiveChannel.receive]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-receive-channel/receive.html
-[ReceiveChannel.onReceive]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-receive-channel/on-receive.html
-[onReceiveOrNull]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/on-receive-or-null.html
-[SendChannel.send]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-send-channel/send.html
-[SendChannel.onSend]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-send-channel/on-send.html
-<!--- INDEX kotlinx.coroutines.selects -->
-[select]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.selects/select.html
-<!--- END -->
+To edit the documentation, open the [topics/select-expression.md](topics/select-expression.md) page. \ No newline at end of file
diff --git a/docs/shared-mutable-state-and-concurrency.md b/docs/shared-mutable-state-and-concurrency.md
index 8b83ad0b..ca054368 100644
--- a/docs/shared-mutable-state-and-concurrency.md
+++ b/docs/shared-mutable-state-and-concurrency.md
@@ -1,539 +1,3 @@
-<!--- TEST_NAME SharedStateGuideTest -->
-
-**Table of contents**
+The documentation has been moved to the [https://kotlinlang.org/docs/shared-mutable-state-and-concurrency.html](https://kotlinlang.org/docs/shared-mutable-state-and-concurrency.html) page.
-<!--- TOC -->
-
-* [Shared mutable state and concurrency](#shared-mutable-state-and-concurrency)
- * [The problem](#the-problem)
- * [Volatiles are of no help](#volatiles-are-of-no-help)
- * [Thread-safe data structures](#thread-safe-data-structures)
- * [Thread confinement fine-grained](#thread-confinement-fine-grained)
- * [Thread confinement coarse-grained](#thread-confinement-coarse-grained)
- * [Mutual exclusion](#mutual-exclusion)
- * [Actors](#actors)
-
-<!--- END -->
-
-## Shared mutable state and concurrency
-
-Coroutines can be executed concurrently using a multi-threaded dispatcher like the [Dispatchers.Default]. It presents
-all the usual concurrency problems. The main problem being synchronization of access to **shared mutable state**.
-Some solutions to this problem in the land of coroutines are similar to the solutions in the multi-threaded world,
-but others are unique.
-
-### The problem
-
-Let us launch a hundred coroutines all doing the same action a thousand times.
-We'll also measure their completion time for further comparisons:
-
-<div class="sample" markdown="1" theme="idea" data-highlight-only>
-
-```kotlin
-suspend fun massiveRun(action: suspend () -> Unit) {
- val n = 100 // number of coroutines to launch
- val k = 1000 // times an action is repeated by each coroutine
- val time = measureTimeMillis {
- coroutineScope { // scope for coroutines
- repeat(n) {
- launch {
- repeat(k) { action() }
- }
- }
- }
- }
- println("Completed ${n * k} actions in $time ms")
-}
-```
-
-</div>
-
-We start with a very simple action that increments a shared mutable variable using
-multi-threaded [Dispatchers.Default].
-
-<!--- CLEAR -->
-
-<div class="sample" markdown="1" theme="idea" data-min-compiler-version="1.3">
-
-```kotlin
-import kotlinx.coroutines.*
-import kotlin.system.*
-
-suspend fun massiveRun(action: suspend () -> Unit) {
- val n = 100 // number of coroutines to launch
- val k = 1000 // times an action is repeated by each coroutine
- val time = measureTimeMillis {
- coroutineScope { // scope for coroutines
- repeat(n) {
- launch {
- repeat(k) { action() }
- }
- }
- }
- }
- println("Completed ${n * k} actions in $time ms")
-}
-
-//sampleStart
-var counter = 0
-
-fun main() = runBlocking {
- withContext(Dispatchers.Default) {
- massiveRun {
- counter++
- }
- }
- println("Counter = $counter")
-}
-//sampleEnd
-```
-
-</div>
-
-> You can get the full code [here](../kotlinx-coroutines-core/jvm/test/guide/example-sync-01.kt).
-
-<!--- TEST LINES_START
-Completed 100000 actions in
-Counter =
--->
-
-What does it print at the end? It is highly unlikely to ever print "Counter = 100000", because a hundred coroutines
-increment the `counter` concurrently from multiple threads without any synchronization.
-
-### Volatiles are of no help
-
-There is a common misconception that making a variable `volatile` solves concurrency problem. Let us try it:
-
-<!--- CLEAR -->
-
-<div class="sample" markdown="1" theme="idea" data-min-compiler-version="1.3">
-
-```kotlin
-import kotlinx.coroutines.*
-import kotlin.system.*
-
-suspend fun massiveRun(action: suspend () -> Unit) {
- val n = 100 // number of coroutines to launch
- val k = 1000 // times an action is repeated by each coroutine
- val time = measureTimeMillis {
- coroutineScope { // scope for coroutines
- repeat(n) {
- launch {
- repeat(k) { action() }
- }
- }
- }
- }
- println("Completed ${n * k} actions in $time ms")
-}
-
-//sampleStart
-@Volatile // in Kotlin `volatile` is an annotation
-var counter = 0
-
-fun main() = runBlocking {
- withContext(Dispatchers.Default) {
- massiveRun {
- counter++
- }
- }
- println("Counter = $counter")
-}
-//sampleEnd
-```
-
-</div>
-
-> You can get the full code [here](../kotlinx-coroutines-core/jvm/test/guide/example-sync-02.kt).
-
-<!--- TEST LINES_START
-Completed 100000 actions in
-Counter =
--->
-
-This code works slower, but we still don't get "Counter = 100000" at the end, because volatile variables guarantee
-linearizable (this is a technical term for "atomic") reads and writes to the corresponding variable, but
-do not provide atomicity of larger actions (increment in our case).
-
-### Thread-safe data structures
-
-The general solution that works both for threads and for coroutines is to use a thread-safe (aka synchronized,
-linearizable, or atomic) data structure that provides all the necessary synchronization for the corresponding
-operations that needs to be performed on a shared state.
-In the case of a simple counter we can use `AtomicInteger` class which has atomic `incrementAndGet` operations:
-
-<!--- CLEAR -->
-
-<div class="sample" markdown="1" theme="idea" data-min-compiler-version="1.3">
-
-```kotlin
-import kotlinx.coroutines.*
-import java.util.concurrent.atomic.*
-import kotlin.system.*
-
-suspend fun massiveRun(action: suspend () -> Unit) {
- val n = 100 // number of coroutines to launch
- val k = 1000 // times an action is repeated by each coroutine
- val time = measureTimeMillis {
- coroutineScope { // scope for coroutines
- repeat(n) {
- launch {
- repeat(k) { action() }
- }
- }
- }
- }
- println("Completed ${n * k} actions in $time ms")
-}
-
-//sampleStart
-val counter = AtomicInteger()
-
-fun main() = runBlocking {
- withContext(Dispatchers.Default) {
- massiveRun {
- counter.incrementAndGet()
- }
- }
- println("Counter = $counter")
-}
-//sampleEnd
-```
-
-</div>
-
-> You can get the full code [here](../kotlinx-coroutines-core/jvm/test/guide/example-sync-03.kt).
-
-<!--- TEST ARBITRARY_TIME
-Completed 100000 actions in xxx ms
-Counter = 100000
--->
-
-This is the fastest solution for this particular problem. It works for plain counters, collections, queues and other
-standard data structures and basic operations on them. However, it does not easily scale to complex
-state or to complex operations that do not have ready-to-use thread-safe implementations.
-
-### Thread confinement fine-grained
-
-_Thread confinement_ is an approach to the problem of shared mutable state where all access to the particular shared
-state is confined to a single thread. It is typically used in UI applications, where all UI state is confined to
-the single event-dispatch/application thread. It is easy to apply with coroutines by using a
-single-threaded context.
-
-<!--- CLEAR -->
-
-<div class="sample" markdown="1" theme="idea" data-min-compiler-version="1.3">
-
-```kotlin
-import kotlinx.coroutines.*
-import kotlin.system.*
-
-suspend fun massiveRun(action: suspend () -> Unit) {
- val n = 100 // number of coroutines to launch
- val k = 1000 // times an action is repeated by each coroutine
- val time = measureTimeMillis {
- coroutineScope { // scope for coroutines
- repeat(n) {
- launch {
- repeat(k) { action() }
- }
- }
- }
- }
- println("Completed ${n * k} actions in $time ms")
-}
-
-//sampleStart
-val counterContext = newSingleThreadContext("CounterContext")
-var counter = 0
-
-fun main() = runBlocking {
- withContext(Dispatchers.Default) {
- massiveRun {
- // confine each increment to a single-threaded context
- withContext(counterContext) {
- counter++
- }
- }
- }
- println("Counter = $counter")
-}
-//sampleEnd
-```
-
-</div>
-
-> You can get the full code [here](../kotlinx-coroutines-core/jvm/test/guide/example-sync-04.kt).
-
-<!--- TEST ARBITRARY_TIME
-Completed 100000 actions in xxx ms
-Counter = 100000
--->
-
-This code works very slowly, because it does _fine-grained_ thread-confinement. Each individual increment switches
-from multi-threaded [Dispatchers.Default] context to the single-threaded context using
-[withContext(counterContext)][withContext] block.
-
-### Thread confinement coarse-grained
-
-In practice, thread confinement is performed in large chunks, e.g. big pieces of state-updating business logic
-are confined to the single thread. The following example does it like that, running each coroutine in
-the single-threaded context to start with.
-
-<!--- CLEAR -->
-
-<div class="sample" markdown="1" theme="idea" data-min-compiler-version="1.3">
-
-```kotlin
-import kotlinx.coroutines.*
-import kotlin.system.*
-
-suspend fun massiveRun(action: suspend () -> Unit) {
- val n = 100 // number of coroutines to launch
- val k = 1000 // times an action is repeated by each coroutine
- val time = measureTimeMillis {
- coroutineScope { // scope for coroutines
- repeat(n) {
- launch {
- repeat(k) { action() }
- }
- }
- }
- }
- println("Completed ${n * k} actions in $time ms")
-}
-
-//sampleStart
-val counterContext = newSingleThreadContext("CounterContext")
-var counter = 0
-
-fun main() = runBlocking {
- // confine everything to a single-threaded context
- withContext(counterContext) {
- massiveRun {
- counter++
- }
- }
- println("Counter = $counter")
-}
-//sampleEnd
-```
-
-</div>
-
-> You can get the full code [here](../kotlinx-coroutines-core/jvm/test/guide/example-sync-05.kt).
-
-<!--- TEST ARBITRARY_TIME
-Completed 100000 actions in xxx ms
-Counter = 100000
--->
-
-This now works much faster and produces correct result.
-
-### Mutual exclusion
-
-Mutual exclusion solution to the problem is to protect all modifications of the shared state with a _critical section_
-that is never executed concurrently. In a blocking world you'd typically use `synchronized` or `ReentrantLock` for that.
-Coroutine's alternative is called [Mutex]. It has [lock][Mutex.lock] and [unlock][Mutex.unlock] functions to
-delimit a critical section. The key difference is that `Mutex.lock()` is a suspending function. It does not block a thread.
-
-There is also [withLock] extension function that conveniently represents
-`mutex.lock(); try { ... } finally { mutex.unlock() }` pattern:
-
-<!--- CLEAR -->
-
-<div class="sample" markdown="1" theme="idea" data-min-compiler-version="1.3">
-
-```kotlin
-import kotlinx.coroutines.*
-import kotlinx.coroutines.sync.*
-import kotlin.system.*
-
-suspend fun massiveRun(action: suspend () -> Unit) {
- val n = 100 // number of coroutines to launch
- val k = 1000 // times an action is repeated by each coroutine
- val time = measureTimeMillis {
- coroutineScope { // scope for coroutines
- repeat(n) {
- launch {
- repeat(k) { action() }
- }
- }
- }
- }
- println("Completed ${n * k} actions in $time ms")
-}
-
-//sampleStart
-val mutex = Mutex()
-var counter = 0
-
-fun main() = runBlocking {
- withContext(Dispatchers.Default) {
- massiveRun {
- // protect each increment with lock
- mutex.withLock {
- counter++
- }
- }
- }
- println("Counter = $counter")
-}
-//sampleEnd
-```
-
-</div>
-
-> You can get the full code [here](../kotlinx-coroutines-core/jvm/test/guide/example-sync-06.kt).
-
-<!--- TEST ARBITRARY_TIME
-Completed 100000 actions in xxx ms
-Counter = 100000
--->
-
-The locking in this example is fine-grained, so it pays the price. However, it is a good choice for some situations
-where you absolutely must modify some shared state periodically, but there is no natural thread that this state
-is confined to.
-
-### Actors
-
-An [actor](https://en.wikipedia.org/wiki/Actor_model) is an entity made up of a combination of a coroutine,
-the state that is confined and encapsulated into this coroutine,
-and a channel to communicate with other coroutines. A simple actor can be written as a function,
-but an actor with a complex state is better suited for a class.
-
-There is an [actor] coroutine builder that conveniently combines actor's mailbox channel into its
-scope to receive messages from and combines the send channel into the resulting job object, so that a
-single reference to the actor can be carried around as its handle.
-
-The first step of using an actor is to define a class of messages that an actor is going to process.
-Kotlin's [sealed classes](https://kotlinlang.org/docs/reference/sealed-classes.html) are well suited for that purpose.
-We define `CounterMsg` sealed class with `IncCounter` message to increment a counter and `GetCounter` message
-to get its value. The later needs to send a response. A [CompletableDeferred] communication
-primitive, that represents a single value that will be known (communicated) in the future,
-is used here for that purpose.
-
-<div class="sample" markdown="1" theme="idea" data-highlight-only>
-
-```kotlin
-// Message types for counterActor
-sealed class CounterMsg
-object IncCounter : CounterMsg() // one-way message to increment counter
-class GetCounter(val response: CompletableDeferred<Int>) : CounterMsg() // a request with reply
-```
-
-</div>
-
-Then we define a function that launches an actor using an [actor] coroutine builder:
-
-<div class="sample" markdown="1" theme="idea" data-highlight-only>
-
-```kotlin
-// This function launches a new counter actor
-fun CoroutineScope.counterActor() = actor<CounterMsg> {
- var counter = 0 // actor state
- for (msg in channel) { // iterate over incoming messages
- when (msg) {
- is IncCounter -> counter++
- is GetCounter -> msg.response.complete(counter)
- }
- }
-}
-```
-
-</div>
-
-The main code is straightforward:
-
-<!--- CLEAR -->
-
-<div class="sample" markdown="1" theme="idea" data-min-compiler-version="1.3">
-
-```kotlin
-import kotlinx.coroutines.*
-import kotlinx.coroutines.channels.*
-import kotlin.system.*
-
-suspend fun massiveRun(action: suspend () -> Unit) {
- val n = 100 // number of coroutines to launch
- val k = 1000 // times an action is repeated by each coroutine
- val time = measureTimeMillis {
- coroutineScope { // scope for coroutines
- repeat(n) {
- launch {
- repeat(k) { action() }
- }
- }
- }
- }
- println("Completed ${n * k} actions in $time ms")
-}
-
-// Message types for counterActor
-sealed class CounterMsg
-object IncCounter : CounterMsg() // one-way message to increment counter
-class GetCounter(val response: CompletableDeferred<Int>) : CounterMsg() // a request with reply
-
-// This function launches a new counter actor
-fun CoroutineScope.counterActor() = actor<CounterMsg> {
- var counter = 0 // actor state
- for (msg in channel) { // iterate over incoming messages
- when (msg) {
- is IncCounter -> counter++
- is GetCounter -> msg.response.complete(counter)
- }
- }
-}
-
-//sampleStart
-fun main() = runBlocking<Unit> {
- val counter = counterActor() // create the actor
- withContext(Dispatchers.Default) {
- massiveRun {
- counter.send(IncCounter)
- }
- }
- // send a message to get a counter value from an actor
- val response = CompletableDeferred<Int>()
- counter.send(GetCounter(response))
- println("Counter = ${response.await()}")
- counter.close() // shutdown the actor
-}
-//sampleEnd
-```
-
-</div>
-
-> You can get the full code [here](../kotlinx-coroutines-core/jvm/test/guide/example-sync-07.kt).
-
-<!--- TEST ARBITRARY_TIME
-Completed 100000 actions in xxx ms
-Counter = 100000
--->
-
-It does not matter (for correctness) what context the actor itself is executed in. An actor is
-a coroutine and a coroutine is executed sequentially, so confinement of the state to the specific coroutine
-works as a solution to the problem of shared mutable state. Indeed, actors may modify their own private state,
-but can only affect each other through messages (avoiding the need for any locks).
-
-Actor is more efficient than locking under load, because in this case it always has work to do and it does not
-have to switch to a different context at all.
-
-> Note that an [actor] coroutine builder is a dual of [produce] coroutine builder. An actor is associated
- with the channel that it receives messages from, while a producer is associated with the channel that it
- sends elements to.
-
-<!--- MODULE kotlinx-coroutines-core -->
-<!--- INDEX kotlinx.coroutines -->
-[Dispatchers.Default]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-dispatchers/-default.html
-[withContext]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/with-context.html
-[CompletableDeferred]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-completable-deferred/index.html
-<!--- INDEX kotlinx.coroutines.sync -->
-[Mutex]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.sync/-mutex/index.html
-[Mutex.lock]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.sync/-mutex/lock.html
-[Mutex.unlock]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.sync/-mutex/unlock.html
-[withLock]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.sync/with-lock.html
-<!--- INDEX kotlinx.coroutines.channels -->
-[actor]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/actor.html
-[produce]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/produce.html
-<!--- END -->
+To edit the documentation, open the [topics/shared-mutable-state-and-concurrency.md](topics/shared-mutable-state-and-concurrency.md) page. \ No newline at end of file
diff --git a/docs/topics/cancellation-and-timeouts.md b/docs/topics/cancellation-and-timeouts.md
new file mode 100644
index 00000000..5221db92
--- /dev/null
+++ b/docs/topics/cancellation-and-timeouts.md
@@ -0,0 +1,461 @@
+<!--- TEST_NAME CancellationGuideTest -->
+
+[//]: # (title: Cancellation and timeouts)
+
+This section covers coroutine cancellation and timeouts.
+
+## Cancelling coroutine execution
+
+In a long-running application you might need fine-grained control on your background coroutines.
+For example, a user might have closed the page that launched a coroutine and now its result
+is no longer needed and its operation can be cancelled.
+The [launch] function returns a [Job] that can be used to cancel the running coroutine:
+
+```kotlin
+import kotlinx.coroutines.*
+
+fun main() = runBlocking {
+//sampleStart
+ val job = launch {
+ repeat(1000) { i ->
+ println("job: I'm sleeping $i ...")
+ delay(500L)
+ }
+ }
+ delay(1300L) // delay a bit
+ println("main: I'm tired of waiting!")
+ job.cancel() // cancels the job
+ job.join() // waits for job's completion
+ println("main: Now I can quit.")
+//sampleEnd
+}
+```
+{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
+
+> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-cancel-01.kt).
+>
+{type="note"}
+
+It produces the following output:
+
+```text
+job: I'm sleeping 0 ...
+job: I'm sleeping 1 ...
+job: I'm sleeping 2 ...
+main: I'm tired of waiting!
+main: Now I can quit.
+```
+
+<!--- TEST -->
+
+As soon as main invokes `job.cancel`, we don't see any output from the other coroutine because it was cancelled.
+There is also a [Job] extension function [cancelAndJoin]
+that combines [cancel][Job.cancel] and [join][Job.join] invocations.
+
+## Cancellation is cooperative
+
+Coroutine cancellation is _cooperative_. A coroutine code has to cooperate to be cancellable.
+All the suspending functions in `kotlinx.coroutines` are _cancellable_. They check for cancellation of
+coroutine and throw [CancellationException] when cancelled. However, if a coroutine is working in
+a computation and does not check for cancellation, then it cannot be cancelled, like the following
+example shows:
+
+```kotlin
+import kotlinx.coroutines.*
+
+fun main() = runBlocking {
+//sampleStart
+ val startTime = System.currentTimeMillis()
+ val job = launch(Dispatchers.Default) {
+ var nextPrintTime = startTime
+ var i = 0
+ while (i < 5) { // computation loop, just wastes CPU
+ // print a message twice a second
+ if (System.currentTimeMillis() >= nextPrintTime) {
+ println("job: I'm sleeping ${i++} ...")
+ nextPrintTime += 500L
+ }
+ }
+ }
+ delay(1300L) // delay a bit
+ println("main: I'm tired of waiting!")
+ job.cancelAndJoin() // cancels the job and waits for its completion
+ println("main: Now I can quit.")
+//sampleEnd
+}
+```
+{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
+
+> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-cancel-02.kt).
+>
+{type="note"}
+
+Run it to see that it continues to print "I'm sleeping" even after cancellation
+until the job completes by itself after five iterations.
+
+<!--- TEST
+job: I'm sleeping 0 ...
+job: I'm sleeping 1 ...
+job: I'm sleeping 2 ...
+main: I'm tired of waiting!
+job: I'm sleeping 3 ...
+job: I'm sleeping 4 ...
+main: Now I can quit.
+-->
+
+## Making computation code cancellable
+
+There are two approaches to making computation code cancellable. The first one is to periodically
+invoke a suspending function that checks for cancellation. There is a [yield] function that is a good choice for that purpose.
+The other one is to explicitly check the cancellation status. Let us try the latter approach.
+
+Replace `while (i < 5)` in the previous example with `while (isActive)` and rerun it.
+
+```kotlin
+import kotlinx.coroutines.*
+
+fun main() = runBlocking {
+//sampleStart
+ val startTime = System.currentTimeMillis()
+ val job = launch(Dispatchers.Default) {
+ var nextPrintTime = startTime
+ var i = 0
+ while (isActive) { // cancellable computation loop
+ // print a message twice a second
+ if (System.currentTimeMillis() >= nextPrintTime) {
+ println("job: I'm sleeping ${i++} ...")
+ nextPrintTime += 500L
+ }
+ }
+ }
+ delay(1300L) // delay a bit
+ println("main: I'm tired of waiting!")
+ job.cancelAndJoin() // cancels the job and waits for its completion
+ println("main: Now I can quit.")
+//sampleEnd
+}
+```
+{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
+
+> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-cancel-03.kt).
+>
+{type="note"}
+
+As you can see, now this loop is cancelled. [isActive] is an extension property
+available inside the coroutine via the [CoroutineScope] object.
+
+<!--- TEST
+job: I'm sleeping 0 ...
+job: I'm sleeping 1 ...
+job: I'm sleeping 2 ...
+main: I'm tired of waiting!
+main: Now I can quit.
+-->
+
+## Closing resources with `finally`
+
+Cancellable suspending functions throw [CancellationException] on cancellation which can be handled in
+the usual way. For example, `try {...} finally {...}` expression and Kotlin `use` function execute their
+finalization actions normally when a coroutine is cancelled:
+
+```kotlin
+import kotlinx.coroutines.*
+
+fun main() = runBlocking {
+//sampleStart
+ val job = launch {
+ try {
+ repeat(1000) { i ->
+ println("job: I'm sleeping $i ...")
+ delay(500L)
+ }
+ } finally {
+ println("job: I'm running finally")
+ }
+ }
+ delay(1300L) // delay a bit
+ println("main: I'm tired of waiting!")
+ job.cancelAndJoin() // cancels the job and waits for its completion
+ println("main: Now I can quit.")
+//sampleEnd
+}
+```
+{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
+
+> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-cancel-04.kt).
+>
+{type="note"}
+
+Both [join][Job.join] and [cancelAndJoin] wait for all finalization actions to complete,
+so the example above produces the following output:
+
+```text
+job: I'm sleeping 0 ...
+job: I'm sleeping 1 ...
+job: I'm sleeping 2 ...
+main: I'm tired of waiting!
+job: I'm running finally
+main: Now I can quit.
+```
+
+<!--- TEST -->
+
+## Run non-cancellable block
+
+Any attempt to use a suspending function in the `finally` block of the previous example causes
+[CancellationException], because the coroutine running this code is cancelled. Usually, this is not a
+problem, since all well-behaving closing operations (closing a file, cancelling a job, or closing any kind of a
+communication channel) are usually non-blocking and do not involve any suspending functions. However, in the
+rare case when you need to suspend in a cancelled coroutine you can wrap the corresponding code in
+`withContext(NonCancellable) {...}` using [withContext] function and [NonCancellable] context as the following example shows:
+
+```kotlin
+import kotlinx.coroutines.*
+
+fun main() = runBlocking {
+//sampleStart
+ val job = launch {
+ try {
+ repeat(1000) { i ->
+ println("job: I'm sleeping $i ...")
+ delay(500L)
+ }
+ } finally {
+ withContext(NonCancellable) {
+ println("job: I'm running finally")
+ delay(1000L)
+ println("job: And I've just delayed for 1 sec because I'm non-cancellable")
+ }
+ }
+ }
+ delay(1300L) // delay a bit
+ println("main: I'm tired of waiting!")
+ job.cancelAndJoin() // cancels the job and waits for its completion
+ println("main: Now I can quit.")
+//sampleEnd
+}
+```
+{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
+
+> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-cancel-05.kt).
+>
+{type="note"}
+
+<!--- TEST
+job: I'm sleeping 0 ...
+job: I'm sleeping 1 ...
+job: I'm sleeping 2 ...
+main: I'm tired of waiting!
+job: I'm running finally
+job: And I've just delayed for 1 sec because I'm non-cancellable
+main: Now I can quit.
+-->
+
+## Timeout
+
+The most obvious practical reason to cancel execution of a coroutine
+is because its execution time has exceeded some timeout.
+While you can manually track the reference to the corresponding [Job] and launch a separate coroutine to cancel
+the tracked one after delay, there is a ready to use [withTimeout] function that does it.
+Look at the following example:
+
+```kotlin
+import kotlinx.coroutines.*
+
+fun main() = runBlocking {
+//sampleStart
+ withTimeout(1300L) {
+ repeat(1000) { i ->
+ println("I'm sleeping $i ...")
+ delay(500L)
+ }
+ }
+//sampleEnd
+}
+```
+{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
+
+> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-cancel-06.kt).
+>
+{type="note"}
+
+It produces the following output:
+
+```text
+I'm sleeping 0 ...
+I'm sleeping 1 ...
+I'm sleeping 2 ...
+Exception in thread "main" kotlinx.coroutines.TimeoutCancellationException: Timed out waiting for 1300 ms
+```
+
+<!--- TEST STARTS_WITH -->
+
+The `TimeoutCancellationException` that is thrown by [withTimeout] is a subclass of [CancellationException].
+We have not seen its stack trace printed on the console before. That is because
+inside a cancelled coroutine `CancellationException` is considered to be a normal reason for coroutine completion.
+However, in this example we have used `withTimeout` right inside the `main` function.
+
+Since cancellation is just an exception, all resources are closed in the usual way.
+You can wrap the code with timeout in a `try {...} catch (e: TimeoutCancellationException) {...}` block if
+you need to do some additional action specifically on any kind of timeout or use the [withTimeoutOrNull] function
+that is similar to [withTimeout] but returns `null` on timeout instead of throwing an exception:
+
+```kotlin
+import kotlinx.coroutines.*
+
+fun main() = runBlocking {
+//sampleStart
+ val result = withTimeoutOrNull(1300L) {
+ repeat(1000) { i ->
+ println("I'm sleeping $i ...")
+ delay(500L)
+ }
+ "Done" // will get cancelled before it produces this result
+ }
+ println("Result is $result")
+//sampleEnd
+}
+```
+{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
+
+> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-cancel-07.kt).
+>
+{type="note"}
+
+There is no longer an exception when running this code:
+
+```text
+I'm sleeping 0 ...
+I'm sleeping 1 ...
+I'm sleeping 2 ...
+Result is null
+```
+
+<!--- TEST -->
+
+## Asynchronous timeout and resources
+
+<!--
+ NOTE: Don't change this section name. It is being referenced to from within KDoc of withTimeout functions.
+-->
+
+The timeout event in [withTimeout] is asynchronous with respect to the code running in its block and may happen at any time,
+even right before the return from inside of the timeout block. Keep this in mind if you open or acquire some
+resource inside the block that needs closing or release outside of the block.
+
+For example, here we imitate a closeable resource with the `Resource` class, that simply keeps track of how many times
+it was created by incrementing the `acquired` counter and decrementing this counter from its `close` function.
+Let us run a lot of coroutines with the small timeout try acquire this resource from inside
+of the `withTimeout` block after a bit of delay and release it from outside.
+
+```kotlin
+import kotlinx.coroutines.*
+
+//sampleStart
+var acquired = 0
+
+class Resource {
+ init { acquired++ } // Acquire the resource
+ fun close() { acquired-- } // Release the resource
+}
+
+fun main() {
+ runBlocking {
+ repeat(100_000) { // Launch 100K coroutines
+ launch {
+ val resource = withTimeout(60) { // Timeout of 60 ms
+ delay(50) // Delay for 50 ms
+ Resource() // Acquire a resource and return it from withTimeout block
+ }
+ resource.close() // Release the resource
+ }
+ }
+ }
+ // Outside of runBlocking all coroutines have completed
+ println(acquired) // Print the number of resources still acquired
+}
+//sampleEnd
+```
+{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
+
+> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-cancel-08.kt).
+>
+{type="note"}
+
+<!--- CLEAR -->
+
+If you run the above code you'll see that it does not always print zero, though it may depend on the timings
+of your machine you may need to tweak timeouts in this example to actually see non-zero values.
+
+> Note, that incrementing and decrementing `acquired` counter here from 100K coroutines is completely safe,
+> since it always happens from the same main thread. More on that will be explained in the next chapter
+> on coroutine context.
+>
+{type="note"}
+
+To workaround this problem you can store a reference to the resource in the variable as opposed to returning it
+from the `withTimeout` block.
+
+```kotlin
+import kotlinx.coroutines.*
+
+var acquired = 0
+
+class Resource {
+ init { acquired++ } // Acquire the resource
+ fun close() { acquired-- } // Release the resource
+}
+
+fun main() {
+//sampleStart
+ runBlocking {
+ repeat(100_000) { // Launch 100K coroutines
+ launch {
+ var resource: Resource? = null // Not acquired yet
+ try {
+ withTimeout(60) { // Timeout of 60 ms
+ delay(50) // Delay for 50 ms
+ resource = Resource() // Store a resource to the variable if acquired
+ }
+ // We can do something else with the resource here
+ } finally {
+ resource?.close() // Release the resource if it was acquired
+ }
+ }
+ }
+ }
+ // Outside of runBlocking all coroutines have completed
+ println(acquired) // Print the number of resources still acquired
+//sampleEnd
+}
+```
+{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
+
+> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-cancel-09.kt).
+>
+{type="note"}
+
+This example always prints zero. Resources do not leak.
+
+<!--- TEST
+0
+-->
+
+<!--- MODULE kotlinx-coroutines-core -->
+<!--- INDEX kotlinx.coroutines -->
+
+[launch]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/launch.html
+[Job]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job/index.html
+[cancelAndJoin]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/cancel-and-join.html
+[Job.cancel]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/cancel.html
+[Job.join]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job/join.html
+[CancellationException]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-cancellation-exception/index.html
+[yield]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/yield.html
+[isActive]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/is-active.html
+[CoroutineScope]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-scope/index.html
+[withContext]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/with-context.html
+[NonCancellable]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-non-cancellable/index.html
+[withTimeout]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/with-timeout.html
+[withTimeoutOrNull]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/with-timeout-or-null.html
+
+<!--- END -->
diff --git a/docs/topics/channels.md b/docs/topics/channels.md
new file mode 100644
index 00000000..7f41eaec
--- /dev/null
+++ b/docs/topics/channels.md
@@ -0,0 +1,649 @@
+<!--- TEST_NAME ChannelsGuideTest -->
+
+[//]: # (title: Channels)
+
+Deferred values provide a convenient way to transfer a single value between coroutines.
+Channels provide a way to transfer a stream of values.
+
+## Channel basics
+
+A [Channel] is conceptually very similar to `BlockingQueue`. One key difference is that
+instead of a blocking `put` operation it has a suspending [send][SendChannel.send], and instead of
+a blocking `take` operation it has a suspending [receive][ReceiveChannel.receive].
+
+```kotlin
+import kotlinx.coroutines.*
+import kotlinx.coroutines.channels.*
+
+fun main() = runBlocking {
+//sampleStart
+ val channel = Channel<Int>()
+ launch {
+ // this might be heavy CPU-consuming computation or async logic, we'll just send five squares
+ for (x in 1..5) channel.send(x * x)
+ }
+ // here we print five received integers:
+ repeat(5) { println(channel.receive()) }
+ println("Done!")
+//sampleEnd
+}
+```
+{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
+
+> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-channel-01.kt).
+>
+{type="note"}
+
+The output of this code is:
+
+```text
+1
+4
+9
+16
+25
+Done!
+```
+
+<!--- TEST -->
+
+## Closing and iteration over channels
+
+Unlike a queue, a channel can be closed to indicate that no more elements are coming.
+On the receiver side it is convenient to use a regular `for` loop to receive elements
+from the channel.
+
+Conceptually, a [close][SendChannel.close] is like sending a special close token to the channel.
+The iteration stops as soon as this close token is received, so there is a guarantee
+that all previously sent elements before the close are received:
+
+```kotlin
+import kotlinx.coroutines.*
+import kotlinx.coroutines.channels.*
+
+fun main() = runBlocking {
+//sampleStart
+ val channel = Channel<Int>()
+ launch {
+ for (x in 1..5) channel.send(x * x)
+ channel.close() // we're done sending
+ }
+ // here we print received values using `for` loop (until the channel is closed)
+ for (y in channel) println(y)
+ println("Done!")
+//sampleEnd
+}
+```
+{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
+
+> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-channel-02.kt).
+>
+{type="note"}
+
+<!--- TEST
+1
+4
+9
+16
+25
+Done!
+-->
+
+## Building channel producers
+
+The pattern where a coroutine is producing a sequence of elements is quite common.
+This is a part of _producer-consumer_ pattern that is often found in concurrent code.
+You could abstract such a producer into a function that takes channel as its parameter, but this goes contrary
+to common sense that results must be returned from functions.
+
+There is a convenient coroutine builder named [produce] that makes it easy to do it right on producer side,
+and an extension function [consumeEach], that replaces a `for` loop on the consumer side:
+
+```kotlin
+import kotlinx.coroutines.*
+import kotlinx.coroutines.channels.*
+
+fun CoroutineScope.produceSquares(): ReceiveChannel<Int> = produce {
+ for (x in 1..5) send(x * x)
+}
+
+fun main() = runBlocking {
+//sampleStart
+ val squares = produceSquares()
+ squares.consumeEach { println(it) }
+ println("Done!")
+//sampleEnd
+}
+```
+{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
+
+> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-channel-03.kt).
+>
+{type="note"}
+
+<!--- TEST
+1
+4
+9
+16
+25
+Done!
+-->
+
+## Pipelines
+
+A pipeline is a pattern where one coroutine is producing, possibly infinite, stream of values:
+
+```kotlin
+fun CoroutineScope.produceNumbers() = produce<Int> {
+ var x = 1
+ while (true) send(x++) // infinite stream of integers starting from 1
+}
+```
+
+And another coroutine or coroutines are consuming that stream, doing some processing, and producing some other results.
+In the example below, the numbers are just squared:
+
+```kotlin
+fun CoroutineScope.square(numbers: ReceiveChannel<Int>): ReceiveChannel<Int> = produce {
+ for (x in numbers) send(x * x)
+}
+```
+
+The main code starts and connects the whole pipeline:
+
+<!--- CLEAR -->
+
+```kotlin
+import kotlinx.coroutines.*
+import kotlinx.coroutines.channels.*
+
+fun main() = runBlocking {
+//sampleStart
+ val numbers = produceNumbers() // produces integers from 1 and on
+ val squares = square(numbers) // squares integers
+ repeat(5) {
+ println(squares.receive()) // print first five
+ }
+ println("Done!") // we are done
+ coroutineContext.cancelChildren() // cancel children coroutines
+//sampleEnd
+}
+
+fun CoroutineScope.produceNumbers() = produce<Int> {
+ var x = 1
+ while (true) send(x++) // infinite stream of integers starting from 1
+}
+
+fun CoroutineScope.square(numbers: ReceiveChannel<Int>): ReceiveChannel<Int> = produce {
+ for (x in numbers) send(x * x)
+}
+```
+{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
+
+> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-channel-04.kt).
+>
+{type="note"}
+
+<!--- TEST
+1
+4
+9
+16
+25
+Done!
+-->
+
+> All functions that create coroutines are defined as extensions on [CoroutineScope],
+> so that we can rely on [structured concurrency](composing-suspending-functions.md#structured-concurrency-with-async) to make
+> sure that we don't have lingering global coroutines in our application.
+>
+{type="note"}
+
+## Prime numbers with pipeline
+
+Let's take pipelines to the extreme with an example that generates prime numbers using a pipeline
+of coroutines. We start with an infinite sequence of numbers.
+
+```kotlin
+fun CoroutineScope.numbersFrom(start: Int) = produce<Int> {
+ var x = start
+ while (true) send(x++) // infinite stream of integers from start
+}
+```
+
+The following pipeline stage filters an incoming stream of numbers, removing all the numbers
+that are divisible by the given prime number:
+
+```kotlin
+fun CoroutineScope.filter(numbers: ReceiveChannel<Int>, prime: Int) = produce<Int> {
+ for (x in numbers) if (x % prime != 0) send(x)
+}
+```
+
+Now we build our pipeline by starting a stream of numbers from 2, taking a prime number from the current channel,
+and launching new pipeline stage for each prime number found:
+
+```Plain Text
+numbersFrom(2) -> filter(2) -> filter(3) -> filter(5) -> filter(7) ...
+```
+
+The following example prints the first ten prime numbers,
+running the whole pipeline in the context of the main thread. Since all the coroutines are launched in
+the scope of the main [runBlocking] coroutine
+we don't have to keep an explicit list of all the coroutines we have started.
+We use [cancelChildren][kotlin.coroutines.CoroutineContext.cancelChildren]
+extension function to cancel all the children coroutines after we have printed
+the first ten prime numbers.
+
+<!--- CLEAR -->
+
+```kotlin
+import kotlinx.coroutines.*
+import kotlinx.coroutines.channels.*
+
+fun main() = runBlocking {
+//sampleStart
+ var cur = numbersFrom(2)
+ repeat(10) {
+ val prime = cur.receive()
+ println(prime)
+ cur = filter(cur, prime)
+ }
+ coroutineContext.cancelChildren() // cancel all children to let main finish
+//sampleEnd
+}
+
+fun CoroutineScope.numbersFrom(start: Int) = produce<Int> {
+ var x = start
+ while (true) send(x++) // infinite stream of integers from start
+}
+
+fun CoroutineScope.filter(numbers: ReceiveChannel<Int>, prime: Int) = produce<Int> {
+ for (x in numbers) if (x % prime != 0) send(x)
+}
+```
+{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
+
+> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-channel-05.kt).
+>
+{type="note"}
+
+The output of this code is:
+
+```text
+2
+3
+5
+7
+11
+13
+17
+19
+23
+29
+```
+
+<!--- TEST -->
+
+Note that you can build the same pipeline using
+[`iterator`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.sequences/iterator.html)
+coroutine builder from the standard library.
+Replace `produce` with `iterator`, `send` with `yield`, `receive` with `next`,
+`ReceiveChannel` with `Iterator`, and get rid of the coroutine scope. You will not need `runBlocking` either.
+However, the benefit of a pipeline that uses channels as shown above is that it can actually use
+multiple CPU cores if you run it in [Dispatchers.Default] context.
+
+Anyway, this is an extremely impractical way to find prime numbers. In practice, pipelines do involve some
+other suspending invocations (like asynchronous calls to remote services) and these pipelines cannot be
+built using `sequence`/`iterator`, because they do not allow arbitrary suspension, unlike
+`produce`, which is fully asynchronous.
+
+## Fan-out
+
+Multiple coroutines may receive from the same channel, distributing work between themselves.
+Let us start with a producer coroutine that is periodically producing integers
+(ten numbers per second):
+
+```kotlin
+fun CoroutineScope.produceNumbers() = produce<Int> {
+ var x = 1 // start from 1
+ while (true) {
+ send(x++) // produce next
+ delay(100) // wait 0.1s
+ }
+}
+```
+
+Then we can have several processor coroutines. In this example, they just print their id and
+received number:
+
+```kotlin
+fun CoroutineScope.launchProcessor(id: Int, channel: ReceiveChannel<Int>) = launch {
+ for (msg in channel) {
+ println("Processor #$id received $msg")
+ }
+}
+```
+
+Now let us launch five processors and let them work for almost a second. See what happens:
+
+<!--- CLEAR -->
+
+```kotlin
+import kotlinx.coroutines.*
+import kotlinx.coroutines.channels.*
+
+fun main() = runBlocking<Unit> {
+//sampleStart
+ val producer = produceNumbers()
+ repeat(5) { launchProcessor(it, producer) }
+ delay(950)
+ producer.cancel() // cancel producer coroutine and thus kill them all
+//sampleEnd
+}
+
+fun CoroutineScope.produceNumbers() = produce<Int> {
+ var x = 1 // start from 1
+ while (true) {
+ send(x++) // produce next
+ delay(100) // wait 0.1s
+ }
+}
+
+fun CoroutineScope.launchProcessor(id: Int, channel: ReceiveChannel<Int>) = launch {
+ for (msg in channel) {
+ println("Processor #$id received $msg")
+ }
+}
+```
+{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
+
+> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-channel-06.kt).
+>
+{type="note"}
+
+The output will be similar to the the following one, albeit the processor ids that receive
+each specific integer may be different:
+
+```text
+Processor #2 received 1
+Processor #4 received 2
+Processor #0 received 3
+Processor #1 received 4
+Processor #3 received 5
+Processor #2 received 6
+Processor #4 received 7
+Processor #0 received 8
+Processor #1 received 9
+Processor #3 received 10
+```
+
+<!--- TEST lines.size == 10 && lines.withIndex().all { (i, line) -> line.startsWith("Processor #") && line.endsWith(" received ${i + 1}") } -->
+
+Note that cancelling a producer coroutine closes its channel, thus eventually terminating iteration
+over the channel that processor coroutines are doing.
+
+Also, pay attention to how we explicitly iterate over channel with `for` loop to perform fan-out in `launchProcessor` code.
+Unlike `consumeEach`, this `for` loop pattern is perfectly safe to use from multiple coroutines. If one of the processor
+coroutines fails, then others would still be processing the channel, while a processor that is written via `consumeEach`
+always consumes (cancels) the underlying channel on its normal or abnormal completion.
+
+## Fan-in
+
+Multiple coroutines may send to the same channel.
+For example, let us have a channel of strings, and a suspending function that
+repeatedly sends a specified string to this channel with a specified delay:
+
+```kotlin
+suspend fun sendString(channel: SendChannel<String>, s: String, time: Long) {
+ while (true) {
+ delay(time)
+ channel.send(s)
+ }
+}
+```
+
+Now, let us see what happens if we launch a couple of coroutines sending strings
+(in this example we launch them in the context of the main thread as main coroutine's children):
+
+<!--- CLEAR -->
+
+```kotlin
+import kotlinx.coroutines.*
+import kotlinx.coroutines.channels.*
+
+fun main() = runBlocking {
+//sampleStart
+ val channel = Channel<String>()
+ launch { sendString(channel, "foo", 200L) }
+ launch { sendString(channel, "BAR!", 500L) }
+ repeat(6) { // receive first six
+ println(channel.receive())
+ }
+ coroutineContext.cancelChildren() // cancel all children to let main finish
+//sampleEnd
+}
+
+suspend fun sendString(channel: SendChannel<String>, s: String, time: Long) {
+ while (true) {
+ delay(time)
+ channel.send(s)
+ }
+}
+```
+{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
+
+> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-channel-07.kt).
+>
+{type="note"}
+
+The output is:
+
+```text
+foo
+foo
+BAR!
+foo
+foo
+BAR!
+```
+
+<!--- TEST -->
+
+## Buffered channels
+
+The channels shown so far had no buffer. Unbuffered channels transfer elements when sender and receiver
+meet each other (aka rendezvous). If send is invoked first, then it is suspended until receive is invoked,
+if receive is invoked first, it is suspended until send is invoked.
+
+Both [Channel()] factory function and [produce] builder take an optional `capacity` parameter to
+specify _buffer size_. Buffer allows senders to send multiple elements before suspending,
+similar to the `BlockingQueue` with a specified capacity, which blocks when buffer is full.
+
+Take a look at the behavior of the following code:
+
+```kotlin
+import kotlinx.coroutines.*
+import kotlinx.coroutines.channels.*
+
+fun main() = runBlocking<Unit> {
+//sampleStart
+ val channel = Channel<Int>(4) // create buffered channel
+ val sender = launch { // launch sender coroutine
+ repeat(10) {
+ println("Sending $it") // print before sending each element
+ channel.send(it) // will suspend when buffer is full
+ }
+ }
+ // don't receive anything... just wait....
+ delay(1000)
+ sender.cancel() // cancel sender coroutine
+//sampleEnd
+}
+```
+{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
+
+> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-channel-08.kt).
+>
+{type="note"}
+
+It prints "sending" _five_ times using a buffered channel with capacity of _four_:
+
+```text
+Sending 0
+Sending 1
+Sending 2
+Sending 3
+Sending 4
+```
+
+<!--- TEST -->
+
+The first four elements are added to the buffer and the sender suspends when trying to send the fifth one.
+
+## Channels are fair
+
+Send and receive operations to channels are _fair_ with respect to the order of their invocation from
+multiple coroutines. They are served in first-in first-out order, e.g. the first coroutine to invoke `receive`
+gets the element. In the following example two coroutines "ping" and "pong" are
+receiving the "ball" object from the shared "table" channel.
+
+```kotlin
+import kotlinx.coroutines.*
+import kotlinx.coroutines.channels.*
+
+//sampleStart
+data class Ball(var hits: Int)
+
+fun main() = runBlocking {
+ val table = Channel<Ball>() // a shared table
+ launch { player("ping", table) }
+ launch { player("pong", table) }
+ table.send(Ball(0)) // serve the ball
+ delay(1000) // delay 1 second
+ coroutineContext.cancelChildren() // game over, cancel them
+}
+
+suspend fun player(name: String, table: Channel<Ball>) {
+ for (ball in table) { // receive the ball in a loop
+ ball.hits++
+ println("$name $ball")
+ delay(300) // wait a bit
+ table.send(ball) // send the ball back
+ }
+}
+//sampleEnd
+```
+{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
+
+> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-channel-09.kt).
+>
+{type="note"}
+
+The "ping" coroutine is started first, so it is the first one to receive the ball. Even though "ping"
+coroutine immediately starts receiving the ball again after sending it back to the table, the ball gets
+received by the "pong" coroutine, because it was already waiting for it:
+
+```text
+ping Ball(hits=1)
+pong Ball(hits=2)
+ping Ball(hits=3)
+pong Ball(hits=4)
+```
+
+<!--- TEST -->
+
+Note that sometimes channels may produce executions that look unfair due to the nature of the executor
+that is being used. See [this issue](https://github.com/Kotlin/kotlinx.coroutines/issues/111) for details.
+
+## Ticker channels
+
+Ticker channel is a special rendezvous channel that produces `Unit` every time given delay passes since last consumption from this channel.
+Though it may seem to be useless standalone, it is a useful building block to create complex time-based [produce]
+pipelines and operators that do windowing and other time-dependent processing.
+Ticker channel can be used in [select] to perform "on tick" action.
+
+To create such channel use a factory method [ticker].
+To indicate that no further elements are needed use [ReceiveChannel.cancel] method on it.
+
+Now let's see how it works in practice:
+
+```kotlin
+import kotlinx.coroutines.*
+import kotlinx.coroutines.channels.*
+
+fun main() = runBlocking<Unit> {
+ val tickerChannel = ticker(delayMillis = 100, initialDelayMillis = 0) // create ticker channel
+ var nextElement = withTimeoutOrNull(1) { tickerChannel.receive() }
+ println("Initial element is available immediately: $nextElement") // no initial delay
+
+ nextElement = withTimeoutOrNull(50) { tickerChannel.receive() } // all subsequent elements have 100ms delay
+ println("Next element is not ready in 50 ms: $nextElement")
+
+ nextElement = withTimeoutOrNull(60) { tickerChannel.receive() }
+ println("Next element is ready in 100 ms: $nextElement")
+
+ // Emulate large consumption delays
+ println("Consumer pauses for 150ms")
+ delay(150)
+ // Next element is available immediately
+ nextElement = withTimeoutOrNull(1) { tickerChannel.receive() }
+ println("Next element is available immediately after large consumer delay: $nextElement")
+ // Note that the pause between `receive` calls is taken into account and next element arrives faster
+ nextElement = withTimeoutOrNull(60) { tickerChannel.receive() }
+ println("Next element is ready in 50ms after consumer pause in 150ms: $nextElement")
+
+ tickerChannel.cancel() // indicate that no more elements are needed
+}
+```
+
+> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-channel-10.kt).
+>
+{type="note"}
+
+It prints following lines:
+
+```text
+Initial element is available immediately: kotlin.Unit
+Next element is not ready in 50 ms: null
+Next element is ready in 100 ms: kotlin.Unit
+Consumer pauses for 150ms
+Next element is available immediately after large consumer delay: kotlin.Unit
+Next element is ready in 50ms after consumer pause in 150ms: kotlin.Unit
+```
+
+<!--- TEST -->
+
+Note that [ticker] is aware of possible consumer pauses and, by default, adjusts next produced element
+delay if a pause occurs, trying to maintain a fixed rate of produced elements.
+
+Optionally, a `mode` parameter equal to [TickerMode.FIXED_DELAY] can be specified to maintain a fixed
+delay between elements.
+
+<!--- MODULE kotlinx-coroutines-core -->
+<!--- INDEX kotlinx.coroutines -->
+
+[CoroutineScope]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-scope/index.html
+[runBlocking]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/run-blocking.html
+[kotlin.coroutines.CoroutineContext.cancelChildren]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/cancel-children.html
+[Dispatchers.Default]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-dispatchers/-default.html
+
+<!--- INDEX kotlinx.coroutines.channels -->
+
+[Channel]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-channel/index.html
+[SendChannel.send]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-send-channel/send.html
+[ReceiveChannel.receive]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-receive-channel/receive.html
+[SendChannel.close]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-send-channel/close.html
+[produce]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/produce.html
+[consumeEach]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/consume-each.html
+[Channel()]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-channel.html
+[ticker]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/ticker.html
+[ReceiveChannel.cancel]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-receive-channel/cancel.html
+[TickerMode.FIXED_DELAY]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-ticker-mode/-f-i-x-e-d_-d-e-l-a-y/index.html
+
+<!--- INDEX kotlinx.coroutines.selects -->
+
+[select]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.selects/select.html
+
+<!--- END -->
diff --git a/docs/topics/compatibility.md b/docs/topics/compatibility.md
new file mode 100644
index 00000000..97448116
--- /dev/null
+++ b/docs/topics/compatibility.md
@@ -0,0 +1,129 @@
+<!--- TOC -->
+
+* [Compatibility](#compatibility)
+* [Public API types](#public-api-types)
+ * [Experimental API](#experimental-api)
+ * [Flow preview API](#flow-preview-api)
+ * [Obsolete API](#obsolete-api)
+ * [Internal API](#internal-api)
+ * [Stable API](#stable-api)
+ * [Deprecation cycle](#deprecation-cycle)
+* [Using annotated API](#using-annotated-api)
+ * [Programmatically](#programmatically)
+ * [Gradle](#gradle)
+ * [Maven](#maven)
+
+<!--- END -->
+
+## Compatibility
+This document describes the compatibility policy of `kotlinx.coroutines` library since version 1.0.0 and semantics of compatibility-specific annotations.
+
+
+## Public API types
+`kotlinx.coroutines` public API comes in five flavours: stable, experimental, obsolete, internal and deprecated.
+All public API except stable is marked with the corresponding annotation.
+
+### Experimental API
+Experimental API is marked with [@ExperimentalCoroutinesApi][ExperimentalCoroutinesApi] annotation.
+API is marked experimental when its design has potential open questions which may eventually lead to
+either semantics changes of the API or its deprecation.
+
+By default, most of the new API is marked as experimental and becomes stable in one of the next major releases if no new issues arise.
+Otherwise, either semantics is fixed without changes in ABI or API goes through deprecation cycle.
+
+When using experimental API may be dangerous:
+* You are writing a library which depends on `kotlinx.coroutines` and want to use experimental coroutines API in a stable library API.
+It may lead to undesired consequences when end users of your library update their `kotlinx.coroutines` version where experimental API
+has slightly different semantics.
+* You want to build core infrastructure of the application around experimental API.
+
+### Flow preview API
+All [Flow]-related API is marked with [@FlowPreview][FlowPreview] annotation.
+This annotation indicates that Flow API is in preview status.
+We provide no compatibility guarantees between releases for preview features, including binary, source and semantics compatibility.
+
+When using preview API may be dangerous:
+* You are writing a library/framework and want to use [Flow] API in a stable release or in a stable API.
+* You want to use [Flow] in the core infrastructure of your application.
+* You want to use [Flow] as "write-and-forget" solution and cannot afford additional maintenance cost when
+ it comes to `kotlinx.coroutines` updates.
+
+
+### Obsolete API
+Obsolete API is marked with [@ObsoleteCoroutinesApi][ObsoleteCoroutinesApi] annotation.
+Obsolete API is similar to experimental, but already known to have serious design flaws and its potential replacement,
+but replacement is not yet implemented.
+
+The semantics of this API won't be changed, but it will go through a deprecation cycle as soon as the replacement is ready.
+
+### Internal API
+Internal API is marked with [@InternalCoroutinesApi][InternalCoroutinesApi] or is part of `kotlinx.coroutines.internal` package.
+This API has no guarantees on its stability, can and will be changed and/or removed in the future releases.
+If you can't avoid using internal API, please report it to [issue tracker](https://github.com/Kotlin/kotlinx.coroutines/issues/new).
+
+### Stable API
+Stable API is guaranteed to preserve its ABI and documented semantics. If at some point unfixable design flaws will be discovered,
+this API will go through a deprecation cycle and remain binary compatible as long as possible.
+
+### Deprecation cycle
+When some API is deprecated, it goes through multiple stages and there is at least one major release between stages.
+* Feature is deprecated with compilation warning. Most of the time, proper replacement
+(and corresponding `replaceWith` declaration) is provided to automatically migrate deprecated usages with a help of IntelliJ IDEA.
+* Deprecation level is increased to `error` or `hidden`. It is no longer possible to compile new code against deprecated API,
+ though it is still present in the ABI.
+* API is completely removed. While we give our best efforts not to do so and have no plans of removing any API, we still are leaving
+this option in case of unforeseen problems such as security holes.
+
+## Using annotated API
+All API annotations are [kotlin.Experimental](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-experimental/index.html).
+It is done in order to produce compilation warning about using experimental or obsolete API.
+Warnings can be disabled either programmatically for a specific call site or globally for the whole module.
+
+### Programmatically
+For a specific call-site, warning can be disabled by using [OptIn](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-opt-in/) annotation:
+```kotlin
+@OptIn(ExperimentalCoroutinesApi::class) // Disables warning about experimental coroutines API
+fun experimentalApiUsage() {
+ someKotlinxCoroutinesExperimentalMethod()
+}
+```
+
+### Gradle
+For the Gradle project, a warning can be disabled by passing a compiler flag in your `build.gradle` file:
+
+```groovy
+tasks.withType(org.jetbrains.kotlin.gradle.tasks.AbstractKotlinCompile).all {
+ kotlinOptions.freeCompilerArgs += ["-Xuse-experimental=kotlinx.coroutines.ExperimentalCoroutinesApi"]
+}
+
+```
+
+### Maven
+For the Maven project, a warning can be disabled by passing a compiler flag in your `pom.xml` file:
+```xml
+<plugin>
+ <artifactId>kotlin-maven-plugin</artifactId>
+ <groupId>org.jetbrains.kotlin</groupId>
+ ... your configuration ...
+ <configuration>
+ <args>
+ <arg>-Xuse-experimental=kotlinx.coroutines.ExperimentalCoroutinesApi</arg>
+ </args>
+ </configuration>
+</plugin>
+```
+
+
+<!--- MODULE kotlinx-coroutines-core -->
+<!--- INDEX kotlinx.coroutines.flow -->
+
+[Flow]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/-flow/index.html
+
+<!--- INDEX kotlinx.coroutines -->
+
+[ExperimentalCoroutinesApi]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-experimental-coroutines-api/index.html
+[FlowPreview]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-flow-preview/index.html
+[ObsoleteCoroutinesApi]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-obsolete-coroutines-api/index.html
+[InternalCoroutinesApi]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-internal-coroutines-api/index.html
+
+<!--- END -->
diff --git a/docs/topics/composing-suspending-functions.md b/docs/topics/composing-suspending-functions.md
new file mode 100644
index 00000000..e244d8c2
--- /dev/null
+++ b/docs/topics/composing-suspending-functions.md
@@ -0,0 +1,416 @@
+<!--- TEST_NAME ComposingGuideTest -->
+
+[//]: # (title: Composing suspending functions)
+
+This section covers various approaches to composition of suspending functions.
+
+## Sequential by default
+
+Assume that we have two suspending functions defined elsewhere that do something useful like some kind of
+remote service call or computation. We just pretend they are useful, but actually each one just
+delays for a second for the purpose of this example:
+
+```kotlin
+suspend fun doSomethingUsefulOne(): Int {
+ delay(1000L) // pretend we are doing something useful here
+ return 13
+}
+
+suspend fun doSomethingUsefulTwo(): Int {
+ delay(1000L) // pretend we are doing something useful here, too
+ return 29
+}
+```
+
+What do we do if we need them to be invoked _sequentially_ &mdash; first `doSomethingUsefulOne` _and then_
+`doSomethingUsefulTwo`, and compute the sum of their results?
+In practice, we do this if we use the result of the first function to make a decision on whether we need
+to invoke the second one or to decide on how to invoke it.
+
+We use a normal sequential invocation, because the code in the coroutine, just like in the regular
+code, is _sequential_ by default. The following example demonstrates it by measuring the total
+time it takes to execute both suspending functions:
+
+<!--- CLEAR -->
+
+```kotlin
+import kotlinx.coroutines.*
+import kotlin.system.*
+
+fun main() = runBlocking<Unit> {
+//sampleStart
+ val time = measureTimeMillis {
+ val one = doSomethingUsefulOne()
+ val two = doSomethingUsefulTwo()
+ println("The answer is ${one + two}")
+ }
+ println("Completed in $time ms")
+//sampleEnd
+}
+
+suspend fun doSomethingUsefulOne(): Int {
+ delay(1000L) // pretend we are doing something useful here
+ return 13
+}
+
+suspend fun doSomethingUsefulTwo(): Int {
+ delay(1000L) // pretend we are doing something useful here, too
+ return 29
+}
+```
+{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
+
+> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-compose-01.kt).
+>
+{type="note"}
+
+It produces something like this:
+
+```text
+The answer is 42
+Completed in 2017 ms
+```
+
+<!--- TEST ARBITRARY_TIME -->
+
+## Concurrent using async
+
+What if there are no dependencies between invocations of `doSomethingUsefulOne` and `doSomethingUsefulTwo` and
+we want to get the answer faster, by doing both _concurrently_? This is where [async] comes to help.
+
+Conceptually, [async] is just like [launch]. It starts a separate coroutine which is a light-weight thread
+that works concurrently with all the other coroutines. The difference is that `launch` returns a [Job] and
+does not carry any resulting value, while `async` returns a [Deferred] &mdash; a light-weight non-blocking future
+that represents a promise to provide a result later. You can use `.await()` on a deferred value to get its eventual result,
+but `Deferred` is also a `Job`, so you can cancel it if needed.
+
+```kotlin
+import kotlinx.coroutines.*
+import kotlin.system.*
+
+fun main() = runBlocking<Unit> {
+//sampleStart
+ val time = measureTimeMillis {
+ val one = async { doSomethingUsefulOne() }
+ val two = async { doSomethingUsefulTwo() }
+ println("The answer is ${one.await() + two.await()}")
+ }
+ println("Completed in $time ms")
+//sampleEnd
+}
+
+suspend fun doSomethingUsefulOne(): Int {
+ delay(1000L) // pretend we are doing something useful here
+ return 13
+}
+
+suspend fun doSomethingUsefulTwo(): Int {
+ delay(1000L) // pretend we are doing something useful here, too
+ return 29
+}
+```
+{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
+
+> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-compose-02.kt).
+>
+{type="note"}
+
+It produces something like this:
+
+```text
+The answer is 42
+Completed in 1017 ms
+```
+
+<!--- TEST ARBITRARY_TIME -->
+
+This is twice as fast, because the two coroutines execute concurrently.
+Note that concurrency with coroutines is always explicit.
+
+## Lazily started async
+
+Optionally, [async] can be made lazy by setting its `start` parameter to [CoroutineStart.LAZY].
+In this mode it only starts the coroutine when its result is required by
+[await][Deferred.await], or if its `Job`'s [start][Job.start] function
+is invoked. Run the following example:
+
+```kotlin
+import kotlinx.coroutines.*
+import kotlin.system.*
+
+fun main() = runBlocking<Unit> {
+//sampleStart
+ val time = measureTimeMillis {
+ val one = async(start = CoroutineStart.LAZY) { doSomethingUsefulOne() }
+ val two = async(start = CoroutineStart.LAZY) { doSomethingUsefulTwo() }
+ // some computation
+ one.start() // start the first one
+ two.start() // start the second one
+ println("The answer is ${one.await() + two.await()}")
+ }
+ println("Completed in $time ms")
+//sampleEnd
+}
+
+suspend fun doSomethingUsefulOne(): Int {
+ delay(1000L) // pretend we are doing something useful here
+ return 13
+}
+
+suspend fun doSomethingUsefulTwo(): Int {
+ delay(1000L) // pretend we are doing something useful here, too
+ return 29
+}
+```
+{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
+
+> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-compose-03.kt).
+>
+{type="note"}
+
+It produces something like this:
+
+```text
+The answer is 42
+Completed in 1017 ms
+```
+
+<!--- TEST ARBITRARY_TIME -->
+
+So, here the two coroutines are defined but not executed as in the previous example, but the control is given to
+the programmer on when exactly to start the execution by calling [start][Job.start]. We first
+start `one`, then start `two`, and then await for the individual coroutines to finish.
+
+Note that if we just call [await][Deferred.await] in `println` without first calling [start][Job.start] on individual
+coroutines, this will lead to sequential behavior, since [await][Deferred.await] starts the coroutine
+execution and waits for its finish, which is not the intended use-case for laziness.
+The use-case for `async(start = CoroutineStart.LAZY)` is a replacement for the
+standard `lazy` function in cases when computation of the value involves suspending functions.
+
+## Async-style functions
+
+We can define async-style functions that invoke `doSomethingUsefulOne` and `doSomethingUsefulTwo`
+_asynchronously_ using the [async] coroutine builder using a [GlobalScope] reference to
+opt-out of the structured concurrency.
+We name such functions with the
+"...Async" suffix to highlight the fact that they only start asynchronous computation and one needs
+to use the resulting deferred value to get the result.
+
+> [GlobalScope] is a delicate API that can backfire in non-trivial ways, one of which will be explained
+> below, so you must explicitly opt-in into using `GlobalScope` with `@OptIn(DelicateCoroutinesApi::class)`.
+>
+{type="note"}
+
+```kotlin
+// The result type of somethingUsefulOneAsync is Deferred<Int>
+@OptIn(DelicateCoroutinesApi::class)
+fun somethingUsefulOneAsync() = GlobalScope.async {
+ doSomethingUsefulOne()
+}
+
+// The result type of somethingUsefulTwoAsync is Deferred<Int>
+@OptIn(DelicateCoroutinesApi::class)
+fun somethingUsefulTwoAsync() = GlobalScope.async {
+ doSomethingUsefulTwo()
+}
+```
+
+Note that these `xxxAsync` functions are **not** _suspending_ functions. They can be used from anywhere.
+However, their use always implies asynchronous (here meaning _concurrent_) execution of their action
+with the invoking code.
+
+The following example shows their use outside of coroutine:
+
+<!--- CLEAR -->
+
+```kotlin
+import kotlinx.coroutines.*
+import kotlin.system.*
+
+//sampleStart
+// note that we don't have `runBlocking` to the right of `main` in this example
+fun main() {
+ val time = measureTimeMillis {
+ // we can initiate async actions outside of a coroutine
+ val one = somethingUsefulOneAsync()
+ val two = somethingUsefulTwoAsync()
+ // but waiting for a result must involve either suspending or blocking.
+ // here we use `runBlocking { ... }` to block the main thread while waiting for the result
+ runBlocking {
+ println("The answer is ${one.await() + two.await()}")
+ }
+ }
+ println("Completed in $time ms")
+}
+//sampleEnd
+
+@OptIn(DelicateCoroutinesApi::class)
+fun somethingUsefulOneAsync() = GlobalScope.async {
+ doSomethingUsefulOne()
+}
+
+@OptIn(DelicateCoroutinesApi::class)
+fun somethingUsefulTwoAsync() = GlobalScope.async {
+ doSomethingUsefulTwo()
+}
+
+suspend fun doSomethingUsefulOne(): Int {
+ delay(1000L) // pretend we are doing something useful here
+ return 13
+}
+
+suspend fun doSomethingUsefulTwo(): Int {
+ delay(1000L) // pretend we are doing something useful here, too
+ return 29
+}
+```
+{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
+
+> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-compose-04.kt).
+>
+{type="note"}
+
+<!--- TEST ARBITRARY_TIME
+The answer is 42
+Completed in 1085 ms
+-->
+
+> This programming style with async functions is provided here only for illustration, because it is a popular style
+> in other programming languages. Using this style with Kotlin coroutines is **strongly discouraged** for the
+> reasons explained below.
+>
+{type="note"}
+
+Consider what happens if between the `val one = somethingUsefulOneAsync()` line and `one.await()` expression there is some logic
+error in the code, and the program throws an exception, and the operation that was being performed by the program aborts.
+Normally, a global error-handler could catch this exception, log and report the error for developers, but the program
+could otherwise continue doing other operations. However, here we have `somethingUsefulOneAsync` still running in the background,
+even though the operation that initiated it was aborted. This problem does not happen with structured
+concurrency, as shown in the section below.
+
+## Structured concurrency with async
+
+Let us take the [Concurrent using async](#concurrent-using-async) example and extract a function that
+concurrently performs `doSomethingUsefulOne` and `doSomethingUsefulTwo` and returns the sum of their results.
+Because the [async] coroutine builder is defined as an extension on [CoroutineScope], we need to have it in the
+scope and that is what the [coroutineScope][_coroutineScope] function provides:
+
+```kotlin
+suspend fun concurrentSum(): Int = coroutineScope {
+ val one = async { doSomethingUsefulOne() }
+ val two = async { doSomethingUsefulTwo() }
+ one.await() + two.await()
+}
+```
+
+This way, if something goes wrong inside the code of the `concurrentSum` function, and it throws an exception,
+all the coroutines that were launched in its scope will be cancelled.
+
+<!--- CLEAR -->
+
+```kotlin
+import kotlinx.coroutines.*
+import kotlin.system.*
+
+fun main() = runBlocking<Unit> {
+//sampleStart
+ val time = measureTimeMillis {
+ println("The answer is ${concurrentSum()}")
+ }
+ println("Completed in $time ms")
+//sampleEnd
+}
+
+suspend fun concurrentSum(): Int = coroutineScope {
+ val one = async { doSomethingUsefulOne() }
+ val two = async { doSomethingUsefulTwo() }
+ one.await() + two.await()
+}
+
+suspend fun doSomethingUsefulOne(): Int {
+ delay(1000L) // pretend we are doing something useful here
+ return 13
+}
+
+suspend fun doSomethingUsefulTwo(): Int {
+ delay(1000L) // pretend we are doing something useful here, too
+ return 29
+}
+```
+{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
+
+> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-compose-05.kt).
+>
+{type="note"}
+
+We still have concurrent execution of both operations, as evident from the output of the above `main` function:
+
+```text
+The answer is 42
+Completed in 1017 ms
+```
+
+<!--- TEST ARBITRARY_TIME -->
+
+Cancellation is always propagated through coroutines hierarchy:
+
+<!--- CLEAR -->
+
+```kotlin
+import kotlinx.coroutines.*
+
+fun main() = runBlocking<Unit> {
+ try {
+ failedConcurrentSum()
+ } catch(e: ArithmeticException) {
+ println("Computation failed with ArithmeticException")
+ }
+}
+
+suspend fun failedConcurrentSum(): Int = coroutineScope {
+ val one = async<Int> {
+ try {
+ delay(Long.MAX_VALUE) // Emulates very long computation
+ 42
+ } finally {
+ println("First child was cancelled")
+ }
+ }
+ val two = async<Int> {
+ println("Second child throws an exception")
+ throw ArithmeticException()
+ }
+ one.await() + two.await()
+}
+```
+{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
+
+> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-compose-06.kt).
+>
+{type="note"}
+
+Note how both the first `async` and the awaiting parent are cancelled on failure of one of the children
+(namely, `two`):
+```text
+Second child throws an exception
+First child was cancelled
+Computation failed with ArithmeticException
+```
+
+<!--- TEST -->
+
+<!--- MODULE kotlinx-coroutines-core -->
+<!--- INDEX kotlinx.coroutines -->
+
+[async]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/async.html
+[launch]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/launch.html
+[Job]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job/index.html
+[Deferred]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-deferred/index.html
+[CoroutineStart.LAZY]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-start/-l-a-z-y/index.html
+[Deferred.await]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-deferred/await.html
+[Job.start]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job/start.html
+[GlobalScope]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-global-scope/index.html
+[CoroutineScope]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-scope/index.html
+[_coroutineScope]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/coroutine-scope.html
+
+<!--- END -->
diff --git a/docs/topics/coroutine-context-and-dispatchers.md b/docs/topics/coroutine-context-and-dispatchers.md
new file mode 100644
index 00000000..96482148
--- /dev/null
+++ b/docs/topics/coroutine-context-and-dispatchers.md
@@ -0,0 +1,685 @@
+<!--- TEST_NAME DispatcherGuideTest -->
+
+[//]: # (title: Coroutine context and dispatchers)
+
+Coroutines always execute in some context represented by a value of the
+[CoroutineContext](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.coroutines/-coroutine-context/)
+type, defined in the Kotlin standard library.
+
+The coroutine context is a set of various elements. The main elements are the [Job] of the coroutine,
+which we've seen before, and its dispatcher, which is covered in this section.
+
+## Dispatchers and threads
+
+The coroutine context includes a _coroutine dispatcher_ (see [CoroutineDispatcher]) that determines what thread or threads
+the corresponding coroutine uses for its execution. The coroutine dispatcher can confine coroutine execution
+to a specific thread, dispatch it to a thread pool, or let it run unconfined.
+
+All coroutine builders like [launch] and [async] accept an optional
+[CoroutineContext](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.coroutines/-coroutine-context/)
+parameter that can be used to explicitly specify the dispatcher for the new coroutine and other context elements.
+
+Try the following example:
+
+```kotlin
+import kotlinx.coroutines.*
+
+fun main() = runBlocking<Unit> {
+//sampleStart
+ launch { // context of the parent, main runBlocking coroutine
+ println("main runBlocking : I'm working in thread ${Thread.currentThread().name}")
+ }
+ launch(Dispatchers.Unconfined) { // not confined -- will work with main thread
+ println("Unconfined : I'm working in thread ${Thread.currentThread().name}")
+ }
+ launch(Dispatchers.Default) { // will get dispatched to DefaultDispatcher
+ println("Default : I'm working in thread ${Thread.currentThread().name}")
+ }
+ launch(newSingleThreadContext("MyOwnThread")) { // will get its own new thread
+ println("newSingleThreadContext: I'm working in thread ${Thread.currentThread().name}")
+ }
+//sampleEnd
+}
+```
+{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
+
+> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-context-01.kt).
+>
+{type="note"}
+
+It produces the following output (maybe in different order):
+
+```text
+Unconfined : I'm working in thread main
+Default : I'm working in thread DefaultDispatcher-worker-1
+newSingleThreadContext: I'm working in thread MyOwnThread
+main runBlocking : I'm working in thread main
+```
+
+<!--- TEST LINES_START_UNORDERED -->
+
+When `launch { ... }` is used without parameters, it inherits the context (and thus dispatcher)
+from the [CoroutineScope] it is being launched from. In this case, it inherits the
+context of the main `runBlocking` coroutine which runs in the `main` thread.
+
+[Dispatchers.Unconfined] is a special dispatcher that also appears to run in the `main` thread, but it is,
+in fact, a different mechanism that is explained later.
+
+The default dispatcher is used when no other dispatcher is explicitly specified in the scope.
+It is represented by [Dispatchers.Default] and uses a shared background pool of threads.
+
+[newSingleThreadContext] creates a thread for the coroutine to run.
+A dedicated thread is a very expensive resource.
+In a real application it must be either released, when no longer needed, using the [close][ExecutorCoroutineDispatcher.close]
+function, or stored in a top-level variable and reused throughout the application.
+
+## Unconfined vs confined dispatcher
+
+The [Dispatchers.Unconfined] coroutine dispatcher starts a coroutine in the caller thread, but only until the
+first suspension point. After suspension it resumes the coroutine in the thread that is fully determined by the
+suspending function that was invoked. The unconfined dispatcher is appropriate for coroutines which neither
+consume CPU time nor update any shared data (like UI) confined to a specific thread.
+
+On the other side, the dispatcher is inherited from the outer [CoroutineScope] by default.
+The default dispatcher for the [runBlocking] coroutine, in particular,
+is confined to the invoker thread, so inheriting it has the effect of confining execution to
+this thread with predictable FIFO scheduling.
+
+```kotlin
+import kotlinx.coroutines.*
+
+fun main() = runBlocking<Unit> {
+//sampleStart
+ launch(Dispatchers.Unconfined) { // not confined -- will work with main thread
+ println("Unconfined : I'm working in thread ${Thread.currentThread().name}")
+ delay(500)
+ println("Unconfined : After delay in thread ${Thread.currentThread().name}")
+ }
+ launch { // context of the parent, main runBlocking coroutine
+ println("main runBlocking: I'm working in thread ${Thread.currentThread().name}")
+ delay(1000)
+ println("main runBlocking: After delay in thread ${Thread.currentThread().name}")
+ }
+//sampleEnd
+}
+```
+{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
+
+> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-context-02.kt).
+>
+{type="note"}
+
+Produces the output:
+
+```text
+Unconfined : I'm working in thread main
+main runBlocking: I'm working in thread main
+Unconfined : After delay in thread kotlinx.coroutines.DefaultExecutor
+main runBlocking: After delay in thread main
+```
+
+<!--- TEST LINES_START -->
+
+So, the coroutine with the context inherited from `runBlocking {...}` continues to execute
+in the `main` thread, while the unconfined one resumes in the default executor thread that the [delay]
+function is using.
+
+> The unconfined dispatcher is an advanced mechanism that can be helpful in certain corner cases where
+> dispatching of a coroutine for its execution later is not needed or produces undesirable side-effects,
+> because some operation in a coroutine must be performed right away.
+> The unconfined dispatcher should not be used in general code.
+>
+{type="note"}
+
+## Debugging coroutines and threads
+
+Coroutines can suspend on one thread and resume on another thread.
+Even with a single-threaded dispatcher it might be hard to
+figure out what the coroutine was doing, where, and when if you don't have special tooling.
+
+### Debugging with IDEA
+
+The Coroutine Debugger of the Kotlin plugin simplifies debugging coroutines in IntelliJ IDEA.
+
+> Debugging works for versions 1.3.8 or later of `kotlinx-coroutines-core`.
+>
+{type="note"}
+
+The **Debug** tool window contains the **Coroutines** tab. In this tab, you can find information about both currently running and suspended coroutines.
+The coroutines are grouped by the dispatcher they are running on.
+
+![Debugging coroutines](coroutine-idea-debugging-1.png)
+
+With the coroutine debugger, you can:
+* Check the state of each coroutine.
+* See the values of local and captured variables for both running and suspended coroutines.
+* See a full coroutine creation stack, as well as a call stack inside the coroutine. The stack includes all frames with
+variable values, even those that would be lost during standard debugging.
+* Get a full report that contains the state of each coroutine and its stack. To obtain it, right-click inside the **Coroutines** tab, and then click **Get Coroutines Dump**.
+
+To start coroutine debugging, you just need to set breakpoints and run the application in debug mode.
+
+Learn more about coroutines debugging in the [tutorial](https://kotlinlang.org/docs/tutorials/coroutines/debug-coroutines-with-idea.html).
+
+### Debugging using logging
+
+Another approach to debugging applications with
+threads without Coroutine Debugger is to print the thread name in the log file on each log statement. This feature is universally supported
+by logging frameworks. When using coroutines, the thread name alone does not give much of a context, so
+`kotlinx.coroutines` includes debugging facilities to make it easier.
+
+Run the following code with `-Dkotlinx.coroutines.debug` JVM option:
+
+```kotlin
+import kotlinx.coroutines.*
+
+fun log(msg: String) = println("[${Thread.currentThread().name}] $msg")
+
+fun main() = runBlocking<Unit> {
+//sampleStart
+ val a = async {
+ log("I'm computing a piece of the answer")
+ 6
+ }
+ val b = async {
+ log("I'm computing another piece of the answer")
+ 7
+ }
+ log("The answer is ${a.await() * b.await()}")
+//sampleEnd
+}
+```
+{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
+
+> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-context-03.kt).
+>
+{type="note"}
+
+There are three coroutines. The main coroutine (#1) inside `runBlocking`
+and two coroutines computing the deferred values `a` (#2) and `b` (#3).
+They are all executing in the context of `runBlocking` and are confined to the main thread.
+The output of this code is:
+
+```text
+[main @coroutine#2] I'm computing a piece of the answer
+[main @coroutine#3] I'm computing another piece of the answer
+[main @coroutine#1] The answer is 42
+```
+
+<!--- TEST FLEXIBLE_THREAD -->
+
+The `log` function prints the name of the thread in square brackets, and you can see that it is the `main`
+thread with the identifier of the currently executing coroutine appended to it. This identifier
+is consecutively assigned to all created coroutines when the debugging mode is on.
+
+> Debugging mode is also turned on when JVM is run with `-ea` option.
+> You can read more about debugging facilities in the documentation of the [DEBUG_PROPERTY_NAME] property.
+>
+{type="note"}
+
+## Jumping between threads
+
+Run the following code with the `-Dkotlinx.coroutines.debug` JVM option (see [debug](#debugging-coroutines-and-threads)):
+
+```kotlin
+import kotlinx.coroutines.*
+
+fun log(msg: String) = println("[${Thread.currentThread().name}] $msg")
+
+fun main() {
+//sampleStart
+ newSingleThreadContext("Ctx1").use { ctx1 ->
+ newSingleThreadContext("Ctx2").use { ctx2 ->
+ runBlocking(ctx1) {
+ log("Started in ctx1")
+ withContext(ctx2) {
+ log("Working in ctx2")
+ }
+ log("Back to ctx1")
+ }
+ }
+ }
+//sampleEnd
+}
+```
+{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
+
+> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-context-04.kt).
+>
+{type="note"}
+
+It demonstrates several new techniques. One is using [runBlocking] with an explicitly specified context, and
+the other one is using the [withContext] function to change the context of a coroutine while still staying in the
+same coroutine, as you can see in the output below:
+
+```text
+[Ctx1 @coroutine#1] Started in ctx1
+[Ctx2 @coroutine#1] Working in ctx2
+[Ctx1 @coroutine#1] Back to ctx1
+```
+
+<!--- TEST -->
+
+Note that this example also uses the `use` function from the Kotlin standard library to release threads
+created with [newSingleThreadContext] when they are no longer needed.
+
+## Job in the context
+
+The coroutine's [Job] is part of its context, and can be retrieved from it
+using the `coroutineContext[Job]` expression:
+
+```kotlin
+import kotlinx.coroutines.*
+
+fun main() = runBlocking<Unit> {
+//sampleStart
+ println("My job is ${coroutineContext[Job]}")
+//sampleEnd
+}
+```
+{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
+
+> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-context-05.kt).
+>
+{type="note"}
+
+In the [debug mode](#debugging-coroutines-and-threads), it outputs something like this:
+
+```
+My job is "coroutine#1":BlockingCoroutine{Active}@6d311334
+```
+
+<!--- TEST lines.size == 1 && lines[0].startsWith("My job is \"coroutine#1\":BlockingCoroutine{Active}@") -->
+
+Note that [isActive] in [CoroutineScope] is just a convenient shortcut for
+`coroutineContext[Job]?.isActive == true`.
+
+## Children of a coroutine
+
+When a coroutine is launched in the [CoroutineScope] of another coroutine,
+it inherits its context via [CoroutineScope.coroutineContext] and
+the [Job] of the new coroutine becomes
+a _child_ of the parent coroutine's job. When the parent coroutine is cancelled, all its children
+are recursively cancelled, too.
+
+However, this parent-child relation can be explicitly overriden in one of two ways:
+
+1. When a different scope is explicitly specified when launching a coroutine (for example, `GlobalScope.launch`),
+ then it does not inherit a `Job` from the parent scope.
+2. When a different `Job` object is passed as the context for the new coroutine (as show in the example below),
+ then it overrides the `Job` of the parent scope.
+
+In both cases, the launched coroutine is not tied to the scope it was launched from and operates independently.
+
+```kotlin
+import kotlinx.coroutines.*
+
+fun main() = runBlocking<Unit> {
+//sampleStart
+ // launch a coroutine to process some kind of incoming request
+ val request = launch {
+ // it spawns two other jobs
+ launch(Job()) {
+ println("job1: I run in my own Job and execute independently!")
+ delay(1000)
+ println("job1: I am not affected by cancellation of the request")
+ }
+ // and the other inherits the parent context
+ launch {
+ delay(100)
+ println("job2: I am a child of the request coroutine")
+ delay(1000)
+ println("job2: I will not execute this line if my parent request is cancelled")
+ }
+ }
+ delay(500)
+ request.cancel() // cancel processing of the request
+ delay(1000) // delay a second to see what happens
+ println("main: Who has survived request cancellation?")
+//sampleEnd
+}
+```
+{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
+
+> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-context-06.kt).
+>
+{type="note"}
+
+The output of this code is:
+
+```text
+job1: I run in my own Job and execute independently!
+job2: I am a child of the request coroutine
+job1: I am not affected by cancellation of the request
+main: Who has survived request cancellation?
+```
+
+<!--- TEST -->
+
+## Parental responsibilities
+
+A parent coroutine always waits for completion of all its children. A parent does not have to explicitly track
+all the children it launches, and it does not have to use [Job.join] to wait for them at the end:
+
+```kotlin
+import kotlinx.coroutines.*
+
+fun main() = runBlocking<Unit> {
+//sampleStart
+ // launch a coroutine to process some kind of incoming request
+ val request = launch {
+ repeat(3) { i -> // launch a few children jobs
+ launch {
+ delay((i + 1) * 200L) // variable delay 200ms, 400ms, 600ms
+ println("Coroutine $i is done")
+ }
+ }
+ println("request: I'm done and I don't explicitly join my children that are still active")
+ }
+ request.join() // wait for completion of the request, including all its children
+ println("Now processing of the request is complete")
+//sampleEnd
+}
+```
+{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
+
+> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-context-07.kt).
+>
+{type="note"}
+
+The result is going to be:
+
+```text
+request: I'm done and I don't explicitly join my children that are still active
+Coroutine 0 is done
+Coroutine 1 is done
+Coroutine 2 is done
+Now processing of the request is complete
+```
+
+<!--- TEST -->
+
+## Naming coroutines for debugging
+
+Automatically assigned ids are good when coroutines log often and you just need to correlate log records
+coming from the same coroutine. However, when a coroutine is tied to the processing of a specific request
+or doing some specific background task, it is better to name it explicitly for debugging purposes.
+The [CoroutineName] context element serves the same purpose as the thread name. It is included in the thread name that
+is executing this coroutine when the [debugging mode](#debugging-coroutines-and-threads) is turned on.
+
+The following example demonstrates this concept:
+
+```kotlin
+import kotlinx.coroutines.*
+
+fun log(msg: String) = println("[${Thread.currentThread().name}] $msg")
+
+fun main() = runBlocking(CoroutineName("main")) {
+//sampleStart
+ log("Started main coroutine")
+ // run two background value computations
+ val v1 = async(CoroutineName("v1coroutine")) {
+ delay(500)
+ log("Computing v1")
+ 252
+ }
+ val v2 = async(CoroutineName("v2coroutine")) {
+ delay(1000)
+ log("Computing v2")
+ 6
+ }
+ log("The answer for v1 / v2 = ${v1.await() / v2.await()}")
+//sampleEnd
+}
+```
+{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
+
+> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-context-08.kt).
+>
+{type="note"}
+
+The output it produces with `-Dkotlinx.coroutines.debug` JVM option is similar to:
+
+```text
+[main @main#1] Started main coroutine
+[main @v1coroutine#2] Computing v1
+[main @v2coroutine#3] Computing v2
+[main @main#1] The answer for v1 / v2 = 42
+```
+
+<!--- TEST FLEXIBLE_THREAD -->
+
+## Combining context elements
+
+Sometimes we need to define multiple elements for a coroutine context. We can use the `+` operator for that.
+For example, we can launch a coroutine with an explicitly specified dispatcher and an explicitly specified
+name at the same time:
+
+```kotlin
+import kotlinx.coroutines.*
+
+fun main() = runBlocking<Unit> {
+//sampleStart
+ launch(Dispatchers.Default + CoroutineName("test")) {
+ println("I'm working in thread ${Thread.currentThread().name}")
+ }
+//sampleEnd
+}
+```
+{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
+
+> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-context-09.kt).
+>
+{type="note"}
+
+The output of this code with the `-Dkotlinx.coroutines.debug` JVM option is:
+
+```text
+I'm working in thread DefaultDispatcher-worker-1 @test#2
+```
+
+<!--- TEST FLEXIBLE_THREAD -->
+
+## Coroutine scope
+
+Let us put our knowledge about contexts, children and jobs together. Assume that our application has
+an object with a lifecycle, but that object is not a coroutine. For example, we are writing an Android application
+and launch various coroutines in the context of an Android activity to perform asynchronous operations to fetch
+and update data, do animations, etc. All of these coroutines must be cancelled when the activity is destroyed
+to avoid memory leaks. We, of course, can manipulate contexts and jobs manually to tie the lifecycles of the activity
+and its coroutines, but `kotlinx.coroutines` provides an abstraction encapsulating that: [CoroutineScope].
+You should be already familiar with the coroutine scope as all coroutine builders are declared as extensions on it.
+
+We manage the lifecycles of our coroutines by creating an instance of [CoroutineScope] tied to
+the lifecycle of our activity. A `CoroutineScope` instance can be created by the [CoroutineScope()] or [MainScope()]
+factory functions. The former creates a general-purpose scope, while the latter creates a scope for UI applications and uses
+[Dispatchers.Main] as the default dispatcher:
+
+```kotlin
+class Activity {
+ private val mainScope = MainScope()
+
+ fun destroy() {
+ mainScope.cancel()
+ }
+ // to be continued ...
+```
+
+Now, we can launch coroutines in the scope of this `Activity` using the defined `scope`.
+For the demo, we launch ten coroutines that delay for a different time:
+
+```kotlin
+ // class Activity continues
+ fun doSomething() {
+ // launch ten coroutines for a demo, each working for a different time
+ repeat(10) { i ->
+ mainScope.launch {
+ delay((i + 1) * 200L) // variable delay 200ms, 400ms, ... etc
+ println("Coroutine $i is done")
+ }
+ }
+ }
+} // class Activity ends
+```
+
+In our main function we create the activity, call our test `doSomething` function, and destroy the activity after 500ms.
+This cancels all the coroutines that were launched from `doSomething`. We can see that because after the destruction
+of the activity no more messages are printed, even if we wait a little longer.
+
+<!--- CLEAR -->
+
+```kotlin
+import kotlinx.coroutines.*
+
+class Activity {
+ private val mainScope = CoroutineScope(Dispatchers.Default) // use Default for test purposes
+
+ fun destroy() {
+ mainScope.cancel()
+ }
+
+ fun doSomething() {
+ // launch ten coroutines for a demo, each working for a different time
+ repeat(10) { i ->
+ mainScope.launch {
+ delay((i + 1) * 200L) // variable delay 200ms, 400ms, ... etc
+ println("Coroutine $i is done")
+ }
+ }
+ }
+} // class Activity ends
+
+fun main() = runBlocking<Unit> {
+//sampleStart
+ val activity = Activity()
+ activity.doSomething() // run test function
+ println("Launched coroutines")
+ delay(500L) // delay for half a second
+ println("Destroying activity!")
+ activity.destroy() // cancels all coroutines
+ delay(1000) // visually confirm that they don't work
+//sampleEnd
+}
+```
+{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
+
+> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-context-10.kt).
+>
+{type="note"}
+
+The output of this example is:
+
+```text
+Launched coroutines
+Coroutine 0 is done
+Coroutine 1 is done
+Destroying activity!
+```
+
+<!--- TEST -->
+
+As you can see, only the first two coroutines print a message and the others are cancelled
+by a single invocation of `job.cancel()` in `Activity.destroy()`.
+
+> Note, that Android has first-party support for coroutine scope in all entities with the lifecycle.
+> See [the corresponding documentation](https://developer.android.com/topic/libraries/architecture/coroutines#lifecyclescope).
+>
+{type="note"}
+
+### Thread-local data
+
+Sometimes it is convenient to have an ability to pass some thread-local data to or between coroutines.
+However, since they are not bound to any particular thread, this will likely lead to boilerplate if done manually.
+
+For [`ThreadLocal`](https://docs.oracle.com/javase/8/docs/api/java/lang/ThreadLocal.html),
+the [asContextElement] extension function is here for the rescue. It creates an additional context element
+which keeps the value of the given `ThreadLocal` and restores it every time the coroutine switches its context.
+
+It is easy to demonstrate it in action:
+
+```kotlin
+import kotlinx.coroutines.*
+
+val threadLocal = ThreadLocal<String?>() // declare thread-local variable
+
+fun main() = runBlocking<Unit> {
+//sampleStart
+ threadLocal.set("main")
+ println("Pre-main, current thread: ${Thread.currentThread()}, thread local value: '${threadLocal.get()}'")
+ val job = launch(Dispatchers.Default + threadLocal.asContextElement(value = "launch")) {
+ println("Launch start, current thread: ${Thread.currentThread()}, thread local value: '${threadLocal.get()}'")
+ yield()
+ println("After yield, current thread: ${Thread.currentThread()}, thread local value: '${threadLocal.get()}'")
+ }
+ job.join()
+ println("Post-main, current thread: ${Thread.currentThread()}, thread local value: '${threadLocal.get()}'")
+//sampleEnd
+}
+```
+{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
+
+> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-context-11.kt).
+>
+{type="note"}
+
+In this example we launch a new coroutine in a background thread pool using [Dispatchers.Default], so
+it works on a different thread from the thread pool, but it still has the value of the thread local variable
+that we specified using `threadLocal.asContextElement(value = "launch")`,
+no matter which thread the coroutine is executed on.
+Thus, the output (with [debug](#debugging-coroutines-and-threads)) is:
+
+```text
+Pre-main, current thread: Thread[main @coroutine#1,5,main], thread local value: 'main'
+Launch start, current thread: Thread[DefaultDispatcher-worker-1 @coroutine#2,5,main], thread local value: 'launch'
+After yield, current thread: Thread[DefaultDispatcher-worker-2 @coroutine#2,5,main], thread local value: 'launch'
+Post-main, current thread: Thread[main @coroutine#1,5,main], thread local value: 'main'
+```
+
+<!--- TEST FLEXIBLE_THREAD -->
+
+It's easy to forget to set the corresponding context element. The thread-local variable accessed from the coroutine may
+then have an unexpected value, if the thread running the coroutine is different.
+To avoid such situations, it is recommended to use the [ensurePresent] method
+and fail-fast on improper usages.
+
+`ThreadLocal` has first-class support and can be used with any primitive `kotlinx.coroutines` provides.
+It has one key limitation, though: when a thread-local is mutated, a new value is not propagated to the coroutine caller
+(because a context element cannot track all `ThreadLocal` object accesses), and the updated value is lost on the next suspension.
+Use [withContext] to update the value of the thread-local in a coroutine, see [asContextElement] for more details.
+
+Alternatively, a value can be stored in a mutable box like `class Counter(var i: Int)`, which is, in turn,
+stored in a thread-local variable. However, in this case you are fully responsible to synchronize
+potentially concurrent modifications to the variable in this mutable box.
+
+For advanced usage, for example for integration with logging MDC, transactional contexts or any other libraries
+which internally use thread-locals for passing data, see the documentation of the [ThreadContextElement] interface
+that should be implemented.
+
+<!--- MODULE kotlinx-coroutines-core -->
+<!--- INDEX kotlinx.coroutines -->
+
+[Job]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job/index.html
+[CoroutineDispatcher]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-dispatcher/index.html
+[launch]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/launch.html
+[async]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/async.html
+[CoroutineScope]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-scope/index.html
+[Dispatchers.Unconfined]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-dispatchers/-unconfined.html
+[Dispatchers.Default]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-dispatchers/-default.html
+[newSingleThreadContext]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/new-single-thread-context.html
+[ExecutorCoroutineDispatcher.close]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-executor-coroutine-dispatcher/close.html
+[runBlocking]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/run-blocking.html
+[delay]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/delay.html
+[DEBUG_PROPERTY_NAME]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-d-e-b-u-g_-p-r-o-p-e-r-t-y_-n-a-m-e.html
+[withContext]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/with-context.html
+[isActive]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/is-active.html
+[CoroutineScope.coroutineContext]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-scope/coroutine-context.html
+[Job.join]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job/join.html
+[CoroutineName]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-name/index.html
+[CoroutineScope()]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-scope.html
+[MainScope()]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-main-scope.html
+[Dispatchers.Main]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-dispatchers/-main.html
+[asContextElement]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/as-context-element.html
+[ensurePresent]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/ensure-present.html
+[ThreadContextElement]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-thread-context-element/index.html
+
+<!--- END -->
diff --git a/docs/topics/coroutines-basics.md b/docs/topics/coroutines-basics.md
new file mode 100644
index 00000000..5d9d0e6d
--- /dev/null
+++ b/docs/topics/coroutines-basics.md
@@ -0,0 +1,288 @@
+<!--- TEST_NAME BasicsGuideTest -->
+
+[//]: # (title: Coroutines basics)
+
+This section covers basic coroutine concepts.
+
+## Your first coroutine
+
+A _coroutine_ is an instance of suspendable computation. It is conceptually similar to a thread, in the sense that it
+takes a block of code to run that works concurrently with the rest of the code.
+However, a coroutine is not bound to any particular thread. It may suspend its execution in one thread and resume in another one.
+
+Coroutines can be thought of as light-weight threads, but there is a number
+of important differences that make their real-life usage very different from threads.
+
+Run the following code to get to your first working coroutine:
+
+```kotlin
+import kotlinx.coroutines.*
+
+//sampleStart
+fun main() = runBlocking { // this: CoroutineScope
+ launch { // launch a new coroutine and continue
+ delay(1000L) // non-blocking delay for 1 second (default time unit is ms)
+ println("World!") // print after delay
+ }
+ println("Hello") // main coroutine continues while a previous one is delayed
+}
+//sampleEnd
+```
+{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
+
+> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-basic-01.kt).
+>
+{type="note"}
+
+You will see the following result:
+
+```text
+Hello
+World!
+```
+
+<!--- TEST -->
+
+Let's dissect what this code does.
+
+[launch] is a _coroutine builder_. It launches a new coroutine concurrently with
+the rest of the code, which continues to work independently. That's why `Hello` has been printed first.
+
+[delay] is a special _suspending function_. It _suspends_ the coroutine for a specific time. Suspending a coroutine
+does not _block_ the underlying thread, but allows other coroutines to run and use the underlying thread for
+their code.
+
+[runBlocking] is also a coroutine builder that bridges the non-coroutine world of a regular `fun main()` and
+the code with coroutines inside of `runBlocking { ... }` curly braces. This is highlighted in an IDE by
+`this: CoroutineScope` hint right after the `runBlocking` opening curly brace.
+
+If you remove or forget `runBlocking` in this code, you'll get an error on the [launch] call, since `launch`
+is declared only in the [CoroutineScope]:
+
+```Plain Text
+Unresolved reference: launch
+```
+
+The name of `runBlocking` means that the thread that runs it (in this case &mdash; the main thread) gets _blocked_ for
+the duration of the call, until all the coroutines inside `runBlocking { ... }` complete their execution. You will
+often see `runBlocking` used like that at the very top-level of the application and quite rarely inside the real code,
+as threads are expensive resources and blocking them is inefficient and is often not desired.
+
+### Structured concurrency
+
+Coroutines follow a principle of
+**structured concurrency** which means that new coroutines can be only launched in a specific [CoroutineScope]
+which delimits the lifetime of the coroutine. The above example shows that [runBlocking] establishes the corresponding
+scope and that is why the previous example waits until `World!` is printed after a second's delay and only then exits.
+
+In the real application, you will be launching a lot of coroutines. Structured concurrency ensures that they are not
+lost and do not leak. An outer scope cannot complete until all its children coroutines complete.
+Structured concurrency also ensures that any errors in the code are properly reported and are never lost.
+
+## Extract function refactoring
+
+Let's extract the block of code inside `launch { ... }` into a separate function. When you
+perform "Extract function" refactoring on this code, you get a new function with the `suspend` modifier.
+This is your first _suspending function_. Suspending functions can be used inside coroutines
+just like regular functions, but their additional feature is that they can, in turn,
+use other suspending functions (like `delay` in this example) to _suspend_ execution of a coroutine.
+
+```kotlin
+import kotlinx.coroutines.*
+
+//sampleStart
+fun main() = runBlocking { // this: CoroutineScope
+ launch { doWorld() }
+ println("Hello")
+}
+
+// this is your first suspending function
+suspend fun doWorld() {
+ delay(1000L)
+ println("World!")
+}
+//sampleEnd
+```
+{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
+
+> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-basic-02.kt).
+>
+{type="note"}
+
+<!--- TEST
+Hello
+World!
+-->
+
+## Scope builder
+
+In addition to the coroutine scope provided by different builders, it is possible to declare your own scope using the
+[coroutineScope][_coroutineScope] builder. It creates a coroutine scope and does not complete until all launched children complete.
+
+[runBlocking] and [coroutineScope][_coroutineScope] builders may look similar because they both wait for their body and all its children to complete.
+The main difference is that the [runBlocking] method _blocks_ the current thread for waiting,
+while [coroutineScope][_coroutineScope] just suspends, releasing the underlying thread for other usages.
+Because of that difference, [runBlocking] is a regular function and [coroutineScope][_coroutineScope] is a suspending function.
+
+You can use `coroutineScope` from any suspending function.
+For example, you can move the concurrent printing of `Hello` and `World` into a `suspend fun doWorld()` function:
+
+```kotlin
+import kotlinx.coroutines.*
+
+//sampleStart
+fun main() = runBlocking {
+ doWorld()
+}
+
+suspend fun doWorld() = coroutineScope { // this: CoroutineScope
+ launch {
+ delay(1000L)
+ println("World!")
+ }
+ println("Hello")
+}
+//sampleEnd
+```
+{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
+
+> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-basic-03.kt).
+>
+{type="note"}
+
+This code also prints:
+
+```text
+Hello
+World!
+```
+
+<!--- TEST -->
+
+## Scope builder and concurrency
+
+A [coroutineScope][_coroutineScope] builder can be used inside any suspending function to perform multiple concurrent operations.
+Let's launch two concurrent coroutines inside a `doWorld` suspending function:
+
+```kotlin
+import kotlinx.coroutines.*
+
+//sampleStart
+// Sequentially executes doWorld followed by "Done"
+fun main() = runBlocking {
+ doWorld()
+ println("Done")
+}
+
+// Concurrently executes both sections
+suspend fun doWorld() = coroutineScope { // this: CoroutineScope
+ launch {
+ delay(2000L)
+ println("World 2")
+ }
+ launch {
+ delay(1000L)
+ println("World 1")
+ }
+ println("Hello")
+}
+//sampleEnd
+```
+{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
+
+> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-basic-04.kt).
+>
+{type="note"}
+
+Both pieces of code inside `launch { ... }` blocks execute _concurrently_, with
+`World 1` printed first, after a second from start, and `World 2` printed next, after two seconds from start.
+A [coroutineScope][_coroutineScope] in `doWorld` completes only after both are complete, so `doWorld` returns and
+allows `Done` string to be printed only after that:
+
+```text
+Hello
+World 1
+World 2
+Done
+```
+
+<!--- TEST -->
+
+## An explicit job
+
+A [launch] coroutine builder returns a [Job] object that is a handle to the launched coroutine and can be
+used to explicitly wait for its completion. For example, you can wait for completion of the child coroutine
+and then print "Done" string:
+
+```kotlin
+import kotlinx.coroutines.*
+
+fun main() = runBlocking {
+//sampleStart
+ val job = launch { // launch a new coroutine and keep a reference to its Job
+ delay(1000L)
+ println("World!")
+ }
+ println("Hello")
+ job.join() // wait until child coroutine completes
+ println("Done")
+//sampleEnd
+}
+```
+{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
+
+> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-basic-05.kt).
+>
+{type="note"}
+
+This code produces:
+
+```text
+Hello
+World!
+Done
+```
+
+<!--- TEST -->
+
+## Coroutines ARE light-weight
+
+Run the following code:
+
+```kotlin
+import kotlinx.coroutines.*
+
+//sampleStart
+fun main() = runBlocking {
+ repeat(100_000) { // launch a lot of coroutines
+ launch {
+ delay(5000L)
+ print(".")
+ }
+ }
+}
+//sampleEnd
+```
+
+> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-basic-06.kt).
+>
+{type="note"}
+
+<!--- TEST lines.size == 1 && lines[0] == ".".repeat(100_000) -->
+
+It launches 100K coroutines and, after 5 seconds, each coroutine prints a dot.
+
+Now, try that with threads (remove `runBlocking`, replace `launch` with `thread`, and replace `delay` with `Thread.sleep`).
+What would happen? (Most likely your code will produce some sort of out-of-memory error)
+
+<!--- MODULE kotlinx-coroutines-core -->
+<!--- INDEX kotlinx.coroutines -->
+
+[launch]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/launch.html
+[delay]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/delay.html
+[runBlocking]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/run-blocking.html
+[CoroutineScope]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-scope/index.html
+[_coroutineScope]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/coroutine-scope.html
+[Job]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job/index.html
+
+<!--- END -->
diff --git a/docs/topics/coroutines-guide.md b/docs/topics/coroutines-guide.md
new file mode 100644
index 00000000..0c6432f6
--- /dev/null
+++ b/docs/topics/coroutines-guide.md
@@ -0,0 +1,38 @@
+[//]: # (title: Coroutines guide)
+
+Kotlin, as a language, provides only minimal low-level APIs in its standard library to enable various other
+libraries to utilize coroutines. Unlike many other languages with similar capabilities, `async` and `await`
+are not keywords in Kotlin and are not even part of its standard library. Moreover, Kotlin's concept
+of _suspending function_ provides a safer and less error-prone abstraction for asynchronous
+operations than futures and promises.
+
+`kotlinx.coroutines` is a rich library for coroutines developed by JetBrains. It contains a number of high-level
+coroutine-enabled primitives that this guide covers, including `launch`, `async` and others.
+
+This is a guide on core features of `kotlinx.coroutines` with a series of examples, divided up into different topics.
+
+In order to use coroutines as well as follow the examples in this guide, you need to add a dependency on the `kotlinx-coroutines-core` module as explained
+[in the project README](https://github.com/Kotlin/kotlinx.coroutines/blob/master/README.md#using-in-your-projects).
+
+## Table of contents
+
+* [Coroutines basics](coroutines-basics.md)
+* [Hands-on: Intro to coroutines and channels](https://play.kotlinlang.org/hands-on/Introduction%20to%20Coroutines%20and%20Channels)
+* [Cancellation and timeouts](cancellation-and-timeouts.md)
+* [Composing suspending functions](composing-suspending-functions.md)
+* [Coroutine context and dispatchers](coroutine-context-and-dispatchers.md)
+* [Asynchronous Flow](flow.md)
+* [Channels](channels.md)
+* [Coroutine exceptions handling](exception-handling.md)
+* [Shared mutable state and concurrency](shared-mutable-state-and-concurrency.md)
+* [Select expression (experimental)](select-expression.md)
+* [Tutorial: Debug coroutines using IntelliJ IDEA](debug-coroutines-with-idea.md)
+* [Tutorial: Debug Kotlin Flow using IntelliJ IDEA](debug-flow-with-idea.md)
+
+## Additional references
+
+* [Guide to UI programming with coroutines](https://github.com/Kotlin/kotlinx.coroutines/blob/master/ui/coroutines-guide-ui.md)
+* [Coroutines design document (KEEP)](https://github.com/Kotlin/KEEP/blob/master/proposals/coroutines.md)
+* [Full kotlinx.coroutines API reference](https://kotlin.github.io/kotlinx.coroutines)
+* [Best practices for coroutines in Android](https://developer.android.com/kotlin/coroutines/coroutines-best-practices)
+* [Additional Android resources for Kotlin coroutines and flow](https://developer.android.com/kotlin/coroutines/additional-resources)
diff --git a/docs/topics/debug-coroutines-with-idea.md b/docs/topics/debug-coroutines-with-idea.md
new file mode 100644
index 00000000..e59075e0
--- /dev/null
+++ b/docs/topics/debug-coroutines-with-idea.md
@@ -0,0 +1,82 @@
+[//]: # (title: Debug coroutines using IntelliJ IDEA – tutorial)
+
+This tutorial demonstrates how to create Kotlin coroutines and debug them using IntelliJ IDEA.
+
+The tutorial assumes you have prior knowledge of the [coroutines](coroutines-guide.md) concept.
+
+> Debugging works for `kotlinx-coroutines-core` version 1.3.8 or later.
+>
+{type="note"}
+
+## Create coroutines
+
+1. Open a Kotlin project in IntelliJ IDEA. If you don't have a project, [create one](jvm-get-started.md#create-an-application).
+
+2. Open the `main.kt` file in `src/main/kotlin`.
+
+ The `src` directory contains Kotlin source files and resources. The `main.kt` file contains sample code that will print `Hello World!`.
+
+2. Change code in the `main()` function:
+
+ * Use the [`runBlocking()`](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/run-blocking.html) block to wrap a coroutine.
+ * Use the [`async()`](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/async.html) function to create coroutines that compute deferred values `a` and `b`.
+ * Use the [`await()`](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-deferred/await.html) function to await the computation result.
+ * Use the [`println()`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.io/println.html) function to print computing status and the result of multiplication to the output.
+
+ ```kotlin
+ import kotlinx.coroutines.*
+
+ fun main() = runBlocking<Unit> {
+ val a = async {
+ println("I'm computing part of the answer")
+ 6
+ }
+ val b = async {
+ println("I'm computing another part of the answer")
+ 7
+ }
+ println("The answer is ${a.await() * b.await()}")
+ }
+ ```
+
+4. Build the code by clicking **Build Project**.
+
+ ![Build an application](flow-build-project.png)
+
+## Debug coroutines
+
+1. Set breakpoints at the lines with the `println()` function call:
+
+ ![Build a console application](coroutine-breakpoint.png)
+
+2. Run the code in debug mode by clicking **Debug** next to the run configuration at the top of the screen.
+
+ ![Build a console application](flow-debug-project.png)
+
+ The **Debug** tool window appears:
+ * The **Frames** tab contains the call stack.
+ * The **Variables** tab contains variables in the current context.
+ * The **Coroutines** tab contains information on running or suspended coroutines. It shows that there are three coroutines.
+ The first one has the **RUNNING** status, and the other two have the **CREATED** status.
+
+ ![Debug the coroutine](coroutine-debug-1.png)
+
+3. Resume the debugger session by clicking **Resume program** in the **Debug** tool window:
+
+ ![Debug the coroutine](coroutine-debug-2.png)
+
+ Now the **Coroutines** tab shows the following:
+ * The first coroutine has the **SUSPENDED** status – it is waiting for the values so it can multiply them.
+ * The second coroutine is calculating the `a` value – it has the **RUNNING** status.
+ * The third coroutine has the **CREATED** status and isn’t calculating the value of `b`.
+
+4. Resume the debugger session by clicking **Resume program** in the **Debug** tool window:
+
+ ![Build a console application](coroutine-debug-3.png)
+
+ Now the **Coroutines** tab shows the following:
+ * The first coroutine has the **SUSPENDED** status – it is waiting for the values so it can multiply them.
+ * The second coroutine has computed its value and disappeared.
+ * The third coroutine is calculating the value of `b` – it has the **RUNNING** status.
+
+Using IntelliJ IDEA debugger, you can dig deeper into each coroutine to debug your code.
diff --git a/docs/topics/debug-flow-with-idea.md b/docs/topics/debug-flow-with-idea.md
new file mode 100644
index 00000000..745dcb17
--- /dev/null
+++ b/docs/topics/debug-flow-with-idea.md
@@ -0,0 +1,126 @@
+[//]: # (title: Debug Kotlin Flow using IntelliJ IDEA – tutorial)
+
+This tutorial demonstrates how to create Kotlin Flow and debug it using IntelliJ IDEA.
+
+The tutorial assumes you have prior knowledge of the [coroutines](coroutines-guide.md) and [Kotlin Flow](flow.md#flows) concepts.
+
+> Debugging works for `kotlinx-coroutines-core` version 1.3.8 or later.
+>
+{type="note"}
+
+## Create a Kotlin flow
+
+Create a Kotlin [flow](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/flow.html) with a slow emitter and a slow collector:
+
+1. Open a Kotlin project in IntelliJ IDEA. If you don't have a project, [create one](jvm-get-started.md#create-an-application).
+
+2. Open the `main.kt` file in `src/main/kotlin`.
+
+ The `src` directory contains Kotlin source files and resources. The `main.kt` file contains sample code that will print `Hello World!`.
+
+2. Create the `simple()` function that returns a flow of three numbers:
+
+ * Use the [`delay()`](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/delay.html) function to imitate CPU-consuming blocking code. It suspends the coroutine for 100 ms without blocking the thread.
+ * Produce the values in the `for` loop using the [`emit()`](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/-flow-collector/emit.html) function.
+
+ ```kotlin
+ import kotlinx.coroutines.*
+ import kotlinx.coroutines.flow.*
+ import kotlin.system.*
+
+ fun simple(): Flow<Int> = flow {
+ for (i in 1..3) {
+ delay(100)
+ emit(i)
+ }
+ }
+ ```
+
+3. Change the code in the `main()` function:
+
+ * Use the [`runBlocking()`](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/run-blocking.html) block to wrap a coroutine.
+ * Collect the emitted values using the [`collect()`](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/collect.html) function.
+ * Use the [`delay()`](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/delay.html) function to imitate CPU-consuming code. It suspends the coroutine for 300 ms without blocking the thread.
+ * Print the collected value from the flow using the [`println()`](https://kotlinlang.org/api/latest/jvm/stdlib/stdlib/kotlin.io/println.html) function.
+
+ ```kotlin
+ fun main() = runBlocking {
+ simple()
+ .collect { value ->
+ delay(300)
+ println(value)
+ }
+ }
+ ```
+
+4. Build the code by clicking **Build Project**.
+
+ ![Build an application](flow-build-project.png)
+
+## Debug the coroutine
+
+1. Set a breakpoint at the at the line where the `emit()` function is called:
+
+ ![Build a console application](flow-breakpoint.png)
+
+2. Run the code in debug mode by clicking **Debug** next to the run configuration at the top of the screen.
+
+ ![Build a console application](flow-debug-project.png)
+
+ The **Debug** tool window appears:
+ * The **Frames** tab contains the call stack.
+ * The **Variables** tab contains variables in the current context. It tells us that the flow is emitting the first value.
+ * The **Coroutines** tab contains information on running or suspended coroutines.
+
+ ![Debug the coroutine](flow-debug-1.png)
+
+3. Resume the debugger session by clicking **Resume program** in the **Debug** tool window. The program stops at the same breakpoint.
+
+ ![Debug the coroutine](flow-resume-debug.png)
+
+ Now the flow emits the second value.
+
+ ![Debug the coroutine](flow-debug-2.png)
+
+## Add a concurrently running coroutine
+
+1. Open the `main.kt` file in `src/main/kotlin`.
+
+2. Enhance the code to run the emitter and collector concurrently:
+
+ * Add a call to the [`buffer()`](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/buffer.html) function to run the emitter and collector concurrently. `buffer()` stores emitted values and runs the flow collector in a separate coroutine.
+
+ ```kotlin
+ fun main() = runBlocking<Unit> {
+ simple()
+ .buffer()
+ .collect { value ->
+ delay(300)
+ println(value)
+ }
+ }
+ ```
+
+4. Build the code by clicking **Build Project**.
+
+## Debug a Kotlin flow with two coroutines
+
+1. Set a new breakpoint at `println(value)`.
+
+2. Run the code in debug mode by clicking **Debug** next to the run configuration at the top of the screen.
+
+ ![Build a console application](flow-debug-3.png)
+
+ The **Debug** tool window appears.
+
+ In the **Coroutines** tab, you can see that there are two coroutines running concurrently. The flow collector and emitter run in separate coroutines because of the `buffer()` function.
+ The `buffer()` function buffers emitted values from the flow.
+ The emitter coroutine has the **RUNNING** status, and the collector coroutine has the **SUSPENDED** status.
+
+2. Resume the debugger session by clicking **Resume program** in the **Debug** tool window.
+
+ ![Debugging coroutines](flow-debug-4.png)
+
+ Now the collector coroutine has the **RUNNING** status, while the emitter coroutine has the **SUSPENDED** status.
+
+ You can dig deeper into each coroutine to debug your code. \ No newline at end of file
diff --git a/docs/topics/debugging.md b/docs/topics/debugging.md
new file mode 100644
index 00000000..5ff4d549
--- /dev/null
+++ b/docs/topics/debugging.md
@@ -0,0 +1,115 @@
+**Table of contents**
+
+<!--- TOC -->
+
+* [Debugging coroutines](#debugging-coroutines)
+* [Debug mode](#debug-mode)
+* [Stacktrace recovery](#stacktrace-recovery)
+ * [Stacktrace recovery machinery](#stacktrace-recovery-machinery)
+* [Debug agent](#debug-agent)
+ * [Debug agent and Android](#debug-agent-and-android)
+* [Android optimization](#android-optimization)
+
+<!--- END -->
+
+## Debugging coroutines
+
+Debugging asynchronous programs is challenging, because multiple concurrent coroutines are typically working at the same time.
+To help with that, `kotlinx.coroutines` comes with additional features for debugging: debug mode, stacktrace recovery
+and debug agent.
+
+## Debug mode
+
+The first debugging feature of `kotlinx.coroutines` is debug mode.
+It can be enabled either by setting system property [DEBUG_PROPERTY_NAME] or by running Java with enabled assertions (`-ea` flag).
+The latter is helpful to have debug mode enabled by default in unit tests.
+
+Debug mode attaches a unique [name][CoroutineName] to every launched coroutine.
+Coroutine name can be seen in a regular Java debugger,
+in a string representation of the coroutine or in the thread name executing named coroutine.
+Overhead of this feature is negligible and it can be safely turned on by default to simplify logging and diagnostic.
+
+## Stacktrace recovery
+
+Stacktrace recovery is another useful feature of debug mode. It is enabled by default in the debug mode,
+but can be separately disabled by setting `kotlinx.coroutines.stacktrace.recovery` system property to `false`.
+
+Stacktrace recovery tries to stitch asynchronous exception stacktrace with a stacktrace of the receiver by copying it, providing
+not only information where an exception was thrown, but also where it was asynchronously rethrown or caught.
+
+It is easy to demonstrate with actual stacktraces of the same program that awaits asynchronous operation in `main` function
+(runnable code is [here](../../kotlinx-coroutines-debug/test/RecoveryExample.kt)):
+
+| Without recovery | With recovery |
+| - | - |
+| ![before](../images/before.png "before") | ![after](../images/after.png "after") |
+
+The only downside of this approach is losing referential transparency of the exception.
+
+> Note that suppressed exceptions are not copied and are left intact in the cause
+> in order to prevent cycles in the exceptions chain, obscure`[CIRCULAR REFERENCE]` messages
+> and even [crashes](https://jira.qos.ch/browse/LOGBACK-1027) in some frameworks
+
+### Stacktrace recovery machinery
+
+This section explains the inner mechanism of stacktrace recovery and can be skipped.
+
+When an exception is rethrown between coroutines (e.g. through `withContext` or `Deferred.await` boundary), stacktrace recovery
+machinery tries to create a copy of the original exception (with the original exception as the cause), then rewrite stacktrace
+of the copy with coroutine-related stack frames (using [Throwable.setStackTrace](https://docs.oracle.com/javase/9/docs/api/java/lang/Throwable.html#setStackTrace-java.lang.StackTraceElement:A-))
+and then throws the resulting exception instead of the original one.
+
+Exception copy logic is straightforward:
+ 1) If the exception class implements [CopyableThrowable], [CopyableThrowable.createCopy] is used.
+ `null` can be returned from `createCopy` to opt-out specific exception from being recovered.
+ 2) If the exception class has class-specific fields not inherited from Throwable, the exception is not copied.
+ 3) Otherwise, one of the public exception's constructor is invoked reflectively with an optional `initCause` call.
+ 4) If the reflective copy has a changed message (exception constructor passed a modified `message` parameter to the superclass),
+ the exception is not copied in order to preserve a human-readable message. [CopyableThrowable] does not have such a limitation
+ and allows the copy to have a `message` different from that of the original.
+
+## Debug agent
+
+[kotlinx-coroutines-debug](../../kotlinx-coroutines-debug) module provides one of the most powerful debug capabilities in `kotlinx.coroutines`.
+
+This is a separate module with a JVM agent that keeps track of all alive coroutines, introspects and dumps them similar to thread dump command,
+additionally enhancing stacktraces with information where coroutine was created.
+
+The full tutorial of how to use debug agent can be found in the corresponding [readme](../../kotlinx-coroutines-debug/README.md).
+
+### Debug agent and Android
+
+Unfortunately, Android runtime does not support Instrument API necessary for `kotlinx-coroutines-debug` to function, triggering `java.lang.NoClassDefFoundError: Failed resolution of: Ljava/lang/management/ManagementFactory;`.
+
+Nevertheless, it will be possible to support debug agent on Android as soon as [GradleAspectJ-Android](https://github.com/Archinamon/android-gradle-aspectj) will support android-gradle 3.3
+
+<!---
+Make an exception googlable
+java.lang.NoClassDefFoundError: Failed resolution of: Ljava/lang/management/ManagementFactory;
+ at kotlinx.coroutines.repackaged.net.bytebuddy.agent.ByteBuddyAgent$ProcessProvider$ForCurrentVm$ForLegacyVm.resolve(ByteBuddyAgent.java:1055)
+ at kotlinx.coroutines.repackaged.net.bytebuddy.agent.ByteBuddyAgent$ProcessProvider$ForCurrentVm.resolve(ByteBuddyAgent.java:1038)
+ at kotlinx.coroutines.repackaged.net.bytebuddy.agent.ByteBuddyAgent.install(ByteBuddyAgent.java:374)
+ at kotlinx.coroutines.repackaged.net.bytebuddy.agent.ByteBuddyAgent.install(ByteBuddyAgent.java:342)
+ at kotlinx.coroutines.repackaged.net.bytebuddy.agent.ByteBuddyAgent.install(ByteBuddyAgent.java:328)
+ at kotlinx.coroutines.debug.internal.DebugProbesImpl.install(DebugProbesImpl.kt:39)
+ at kotlinx.coroutines.debug.DebugProbes.install(DebugProbes.kt:49)
+-->
+
+## Android optimization
+
+In optimized (release) builds with R8 version 1.6.0 or later both
+[Debugging mode](../../docs/debugging.md#debug-mode) and
+[Stacktrace recovery](../../docs/debugging.md#stacktrace-recovery)
+are permanently turned off.
+For more details see ["Optimization" section for Android](../../ui/kotlinx-coroutines-android/README.md#optimization).
+
+<!--- MODULE kotlinx-coroutines-core -->
+<!--- INDEX kotlinx.coroutines -->
+
+[DEBUG_PROPERTY_NAME]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-d-e-b-u-g_-p-r-o-p-e-r-t-y_-n-a-m-e.html
+[CoroutineName]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-name/index.html
+[CopyableThrowable]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-copyable-throwable/index.html
+[CopyableThrowable.createCopy]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-copyable-throwable/create-copy.html
+
+<!--- MODULE kotlinx-coroutines-debug -->
+<!--- END -->
diff --git a/docs/topics/exception-handling.md b/docs/topics/exception-handling.md
new file mode 100644
index 00000000..35e645f4
--- /dev/null
+++ b/docs/topics/exception-handling.md
@@ -0,0 +1,533 @@
+<!--- TEST_NAME ExceptionsGuideTest -->
+
+[//]: # (title: Coroutine exceptions handling)
+
+This section covers exception handling and cancellation on exceptions.
+We already know that a cancelled coroutine throws [CancellationException] in suspension points and that it
+is ignored by the coroutines' machinery. Here we look at what happens if an exception is thrown during cancellation or multiple children of the same
+coroutine throw an exception.
+
+## Exception propagation
+
+Coroutine builders come in two flavors: propagating exceptions automatically ([launch] and [actor]) or
+exposing them to users ([async] and [produce]).
+When these builders are used to create a _root_ coroutine, that is not a _child_ of another coroutine,
+the former builders treat exceptions as **uncaught** exceptions, similar to Java's `Thread.uncaughtExceptionHandler`,
+while the latter are relying on the user to consume the final
+exception, for example via [await][Deferred.await] or [receive][ReceiveChannel.receive]
+([produce] and [receive][ReceiveChannel.receive] are covered later in [Channels](https://github.com/Kotlin/kotlinx.coroutines/blob/master/docs/channels.md) section).
+
+It can be demonstrated by a simple example that creates root coroutines using the [GlobalScope]:
+
+> [GlobalScope] is a delicate API that can backfire in non-trivial ways. Creating a root coroutine for the
+> whole application is one of the rare legitimate uses for `GlobalScope`, so you must explicitly opt-in into
+> using `GlobalScope` with `@OptIn(DelicateCoroutinesApi::class)`.
+>
+{type="note"}
+
+```kotlin
+import kotlinx.coroutines.*
+
+@OptIn(DelicateCoroutinesApi::class)
+fun main() = runBlocking {
+ val job = GlobalScope.launch { // root coroutine with launch
+ println("Throwing exception from launch")
+ throw IndexOutOfBoundsException() // Will be printed to the console by Thread.defaultUncaughtExceptionHandler
+ }
+ job.join()
+ println("Joined failed job")
+ val deferred = GlobalScope.async { // root coroutine with async
+ println("Throwing exception from async")
+ throw ArithmeticException() // Nothing is printed, relying on user to call await
+ }
+ try {
+ deferred.await()
+ println("Unreached")
+ } catch (e: ArithmeticException) {
+ println("Caught ArithmeticException")
+ }
+}
+```
+
+> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-exceptions-01.kt).
+>
+{type="note"}
+
+The output of this code is (with [debug](https://github.com/Kotlin/kotlinx.coroutines/blob/master/docs/coroutine-context-and-dispatchers.md#debugging-coroutines-and-threads)):
+
+```text
+Throwing exception from launch
+Exception in thread "DefaultDispatcher-worker-2 @coroutine#2" java.lang.IndexOutOfBoundsException
+Joined failed job
+Throwing exception from async
+Caught ArithmeticException
+```
+
+<!--- TEST EXCEPTION-->
+
+## CoroutineExceptionHandler
+
+It is possible to customize the default behavior of printing **uncaught** exceptions to the console.
+[CoroutineExceptionHandler] context element on a _root_ coroutine can be used as generic `catch` block for
+this root coroutine and all its children where custom exception handling may take place.
+It is similar to [`Thread.uncaughtExceptionHandler`](https://docs.oracle.com/javase/8/docs/api/java/lang/Thread.html#setUncaughtExceptionHandler(java.lang.Thread.UncaughtExceptionHandler)).
+You cannot recover from the exception in the `CoroutineExceptionHandler`. The coroutine had already completed
+with the corresponding exception when the handler is called. Normally, the handler is used to
+log the exception, show some kind of error message, terminate, and/or restart the application.
+
+On JVM it is possible to redefine global exception handler for all coroutines by registering [CoroutineExceptionHandler] via
+[`ServiceLoader`](https://docs.oracle.com/javase/8/docs/api/java/util/ServiceLoader.html).
+Global exception handler is similar to
+[`Thread.defaultUncaughtExceptionHandler`](https://docs.oracle.com/javase/8/docs/api/java/lang/Thread.html#setDefaultUncaughtExceptionHandler(java.lang.Thread.UncaughtExceptionHandler))
+which is used when no more specific handlers are registered.
+On Android, `uncaughtExceptionPreHandler` is installed as a global coroutine exception handler.
+
+`CoroutineExceptionHandler` is invoked only on **uncaught** exceptions &mdash; exceptions that were not handled in any other way.
+In particular, all _children_ coroutines (coroutines created in the context of another [Job]) delegate handling of
+their exceptions to their parent coroutine, which also delegates to the parent, and so on until the root,
+so the `CoroutineExceptionHandler` installed in their context is never used.
+In addition to that, [async] builder always catches all exceptions and represents them in the resulting [Deferred] object,
+so its `CoroutineExceptionHandler` has no effect either.
+
+> Coroutines running in supervision scope do not propagate exceptions to their parent and are
+> excluded from this rule. A further [Supervision](#supervision) section of this document gives more details.
+>
+{type="note"}
+
+```kotlin
+import kotlinx.coroutines.*
+
+@OptIn(DelicateCoroutinesApi::class)
+fun main() = runBlocking {
+//sampleStart
+ val handler = CoroutineExceptionHandler { _, exception ->
+ println("CoroutineExceptionHandler got $exception")
+ }
+ val job = GlobalScope.launch(handler) { // root coroutine, running in GlobalScope
+ throw AssertionError()
+ }
+ val deferred = GlobalScope.async(handler) { // also root, but async instead of launch
+ throw ArithmeticException() // Nothing will be printed, relying on user to call deferred.await()
+ }
+ joinAll(job, deferred)
+//sampleEnd
+}
+```
+{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
+
+> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-exceptions-02.kt).
+>
+{type="note"}
+
+The output of this code is:
+
+```text
+CoroutineExceptionHandler got java.lang.AssertionError
+```
+
+<!--- TEST-->
+
+## Cancellation and exceptions
+
+Cancellation is closely related to exceptions. Coroutines internally use `CancellationException` for cancellation, these
+exceptions are ignored by all handlers, so they should be used only as the source of additional debug information, which can
+be obtained by `catch` block.
+When a coroutine is cancelled using [Job.cancel], it terminates, but it does not cancel its parent.
+
+```kotlin
+import kotlinx.coroutines.*
+
+fun main() = runBlocking {
+//sampleStart
+ val job = launch {
+ val child = launch {
+ try {
+ delay(Long.MAX_VALUE)
+ } finally {
+ println("Child is cancelled")
+ }
+ }
+ yield()
+ println("Cancelling child")
+ child.cancel()
+ child.join()
+ yield()
+ println("Parent is not cancelled")
+ }
+ job.join()
+//sampleEnd
+}
+```
+{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
+
+> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-exceptions-03.kt).
+>
+{type="note"}
+
+The output of this code is:
+
+```text
+Cancelling child
+Child is cancelled
+Parent is not cancelled
+```
+
+<!--- TEST-->
+
+If a coroutine encounters an exception other than `CancellationException`, it cancels its parent with that exception.
+This behaviour cannot be overridden and is used to provide stable coroutines hierarchies for
+[structured concurrency](https://github.com/Kotlin/kotlinx.coroutines/blob/master/docs/composing-suspending-functions.md#structured-concurrency-with-async).
+[CoroutineExceptionHandler] implementation is not used for child coroutines.
+
+> In these examples, [CoroutineExceptionHandler] is always installed to a coroutine
+> that is created in [GlobalScope]. It does not make sense to install an exception handler to a coroutine that
+> is launched in the scope of the main [runBlocking], since the main coroutine is going to be always cancelled
+> when its child completes with exception despite the installed handler.
+>
+{type="note"}
+
+The original exception is handled by the parent only when all its children terminate,
+which is demonstrated by the following example.
+
+```kotlin
+import kotlinx.coroutines.*
+
+@OptIn(DelicateCoroutinesApi::class)
+fun main() = runBlocking {
+//sampleStart
+ val handler = CoroutineExceptionHandler { _, exception ->
+ println("CoroutineExceptionHandler got $exception")
+ }
+ val job = GlobalScope.launch(handler) {
+ launch { // the first child
+ try {
+ delay(Long.MAX_VALUE)
+ } finally {
+ withContext(NonCancellable) {
+ println("Children are cancelled, but exception is not handled until all children terminate")
+ delay(100)
+ println("The first child finished its non cancellable block")
+ }
+ }
+ }
+ launch { // the second child
+ delay(10)
+ println("Second child throws an exception")
+ throw ArithmeticException()
+ }
+ }
+ job.join()
+//sampleEnd
+}
+```
+{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
+
+> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-exceptions-04.kt).
+>
+{type="note"}
+
+The output of this code is:
+
+```text
+Second child throws an exception
+Children are cancelled, but exception is not handled until all children terminate
+The first child finished its non cancellable block
+CoroutineExceptionHandler got java.lang.ArithmeticException
+```
+
+<!--- TEST-->
+
+## Exceptions aggregation
+
+When multiple children of a coroutine fail with an exception, the
+general rule is "the first exception wins", so the first exception gets handled.
+All additional exceptions that happen after the first one are attached to the first exception as suppressed ones.
+
+<!--- INCLUDE
+import kotlinx.coroutines.exceptions.*
+-->
+
+```kotlin
+import kotlinx.coroutines.*
+import java.io.*
+
+@OptIn(DelicateCoroutinesApi::class)
+fun main() = runBlocking {
+ val handler = CoroutineExceptionHandler { _, exception ->
+ println("CoroutineExceptionHandler got $exception with suppressed ${exception.suppressed.contentToString()}")
+ }
+ val job = GlobalScope.launch(handler) {
+ launch {
+ try {
+ delay(Long.MAX_VALUE) // it gets cancelled when another sibling fails with IOException
+ } finally {
+ throw ArithmeticException() // the second exception
+ }
+ }
+ launch {
+ delay(100)
+ throw IOException() // the first exception
+ }
+ delay(Long.MAX_VALUE)
+ }
+ job.join()
+}
+```
+{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
+
+> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-exceptions-05.kt).
+>
+{type="note"}
+
+> Note: This above code will work properly only on JDK7+ that supports `suppressed` exceptions
+>
+{type="note"}
+
+The output of this code is:
+
+```text
+CoroutineExceptionHandler got java.io.IOException with suppressed [java.lang.ArithmeticException]
+```
+
+<!--- TEST-->
+
+> Note that this mechanism currently only works on Java version 1.7+.
+> The JS and Native restrictions are temporary and will be lifted in the future.
+>
+{type="note"}
+
+Cancellation exceptions are transparent and are unwrapped by default:
+
+```kotlin
+import kotlinx.coroutines.*
+import java.io.*
+
+@OptIn(DelicateCoroutinesApi::class)
+fun main() = runBlocking {
+//sampleStart
+ val handler = CoroutineExceptionHandler { _, exception ->
+ println("CoroutineExceptionHandler got $exception")
+ }
+ val job = GlobalScope.launch(handler) {
+ val inner = launch { // all this stack of coroutines will get cancelled
+ launch {
+ launch {
+ throw IOException() // the original exception
+ }
+ }
+ }
+ try {
+ inner.join()
+ } catch (e: CancellationException) {
+ println("Rethrowing CancellationException with original cause")
+ throw e // cancellation exception is rethrown, yet the original IOException gets to the handler
+ }
+ }
+ job.join()
+//sampleEnd
+}
+```
+{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
+
+> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-exceptions-06.kt).
+>
+{type="note"}
+
+The output of this code is:
+
+```text
+Rethrowing CancellationException with original cause
+CoroutineExceptionHandler got java.io.IOException
+```
+
+<!--- TEST-->
+
+## Supervision
+
+As we have studied before, cancellation is a bidirectional relationship propagating through the whole
+hierarchy of coroutines. Let us take a look at the case when unidirectional cancellation is required.
+
+A good example of such a requirement is a UI component with the job defined in its scope. If any of the UI's child tasks
+have failed, it is not always necessary to cancel (effectively kill) the whole UI component,
+but if UI component is destroyed (and its job is cancelled), then it is necessary to fail all child jobs as their results are no longer needed.
+
+Another example is a server process that spawns multiple child jobs and needs to _supervise_
+their execution, tracking their failures and only restarting the failed ones.
+
+### Supervision job
+
+The [SupervisorJob][SupervisorJob()] can be used for these purposes.
+It is similar to a regular [Job][Job()] with the only exception that cancellation is propagated
+only downwards. This can easily be demonstrated using the following example:
+
+```kotlin
+import kotlinx.coroutines.*
+
+fun main() = runBlocking {
+//sampleStart
+ val supervisor = SupervisorJob()
+ with(CoroutineScope(coroutineContext + supervisor)) {
+ // launch the first child -- its exception is ignored for this example (don't do this in practice!)
+ val firstChild = launch(CoroutineExceptionHandler { _, _ -> }) {
+ println("The first child is failing")
+ throw AssertionError("The first child is cancelled")
+ }
+ // launch the second child
+ val secondChild = launch {
+ firstChild.join()
+ // Cancellation of the first child is not propagated to the second child
+ println("The first child is cancelled: ${firstChild.isCancelled}, but the second one is still active")
+ try {
+ delay(Long.MAX_VALUE)
+ } finally {
+ // But cancellation of the supervisor is propagated
+ println("The second child is cancelled because the supervisor was cancelled")
+ }
+ }
+ // wait until the first child fails & completes
+ firstChild.join()
+ println("Cancelling the supervisor")
+ supervisor.cancel()
+ secondChild.join()
+ }
+//sampleEnd
+}
+```
+{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
+
+> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-supervision-01.kt).
+>
+{type="note"}
+
+The output of this code is:
+
+```text
+The first child is failing
+The first child is cancelled: true, but the second one is still active
+Cancelling the supervisor
+The second child is cancelled because the supervisor was cancelled
+```
+
+<!--- TEST-->
+
+### Supervision scope
+
+Instead of [coroutineScope][_coroutineScope], we can use [supervisorScope][_supervisorScope] for _scoped_ concurrency. It propagates the cancellation
+in one direction only and cancels all its children only if it failed itself. It also waits for all children before completion
+just like [coroutineScope][_coroutineScope] does.
+
+```kotlin
+import kotlin.coroutines.*
+import kotlinx.coroutines.*
+
+fun main() = runBlocking {
+//sampleStart
+ try {
+ supervisorScope {
+ val child = launch {
+ try {
+ println("The child is sleeping")
+ delay(Long.MAX_VALUE)
+ } finally {
+ println("The child is cancelled")
+ }
+ }
+ // Give our child a chance to execute and print using yield
+ yield()
+ println("Throwing an exception from the scope")
+ throw AssertionError()
+ }
+ } catch(e: AssertionError) {
+ println("Caught an assertion error")
+ }
+//sampleEnd
+}
+```
+{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
+
+> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-supervision-02.kt).
+>
+{type="note"}
+
+The output of this code is:
+
+```text
+The child is sleeping
+Throwing an exception from the scope
+The child is cancelled
+Caught an assertion error
+```
+
+<!--- TEST-->
+
+#### Exceptions in supervised coroutines
+
+Another crucial difference between regular and supervisor jobs is exception handling.
+Every child should handle its exceptions by itself via the exception handling mechanism.
+This difference comes from the fact that child's failure does not propagate to the parent.
+It means that coroutines launched directly inside the [supervisorScope][_supervisorScope] _do_ use the [CoroutineExceptionHandler]
+that is installed in their scope in the same way as root coroutines do
+(see the [CoroutineExceptionHandler](#coroutineexceptionhandler) section for details).
+
+```kotlin
+import kotlin.coroutines.*
+import kotlinx.coroutines.*
+
+fun main() = runBlocking {
+//sampleStart
+ val handler = CoroutineExceptionHandler { _, exception ->
+ println("CoroutineExceptionHandler got $exception")
+ }
+ supervisorScope {
+ val child = launch(handler) {
+ println("The child throws an exception")
+ throw AssertionError()
+ }
+ println("The scope is completing")
+ }
+ println("The scope is completed")
+//sampleEnd
+}
+```
+{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
+
+> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-supervision-03.kt).
+>
+{type="note"}
+
+The output of this code is:
+
+```text
+The scope is completing
+The child throws an exception
+CoroutineExceptionHandler got java.lang.AssertionError
+The scope is completed
+```
+
+<!--- TEST-->
+
+<!--- MODULE kotlinx-coroutines-core -->
+<!--- INDEX kotlinx.coroutines -->
+
+[CancellationException]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-cancellation-exception/index.html
+[launch]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/launch.html
+[async]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/async.html
+[Deferred.await]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-deferred/await.html
+[GlobalScope]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-global-scope/index.html
+[CoroutineExceptionHandler]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-exception-handler/index.html
+[Job]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job/index.html
+[Deferred]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-deferred/index.html
+[Job.cancel]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/cancel.html
+[runBlocking]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/run-blocking.html
+[SupervisorJob()]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-supervisor-job.html
+[Job()]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job.html
+[_coroutineScope]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/coroutine-scope.html
+[_supervisorScope]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/supervisor-scope.html
+
+<!--- INDEX kotlinx.coroutines.channels -->
+
+[actor]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/actor.html
+[produce]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/produce.html
+[ReceiveChannel.receive]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-receive-channel/receive.html
+
+<!--- END -->
diff --git a/docs/topics/flow.md b/docs/topics/flow.md
new file mode 100644
index 00000000..311411f8
--- /dev/null
+++ b/docs/topics/flow.md
@@ -0,0 +1,1896 @@
+<!--- TEST_NAME FlowGuideTest -->
+
+[//]: # (title: Asynchronous Flow)
+
+A suspending function asynchronously returns a single value, but how can we return
+multiple asynchronously computed values? This is where Kotlin Flows come in.
+
+## Representing multiple values
+
+Multiple values can be represented in Kotlin using [collections].
+For example, we can have a `simple` function that returns a [List]
+of three numbers and then print them all using [forEach]:
+
+```kotlin
+fun simple(): List<Int> = listOf(1, 2, 3)
+
+fun main() {
+ simple().forEach { value -> println(value) }
+}
+```
+{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
+
+> You can get the full code from [here](../../kotlinx-coroutines-core/jvm/test/guide/example-flow-01.kt).
+>
+{type="note"}
+
+This code outputs:
+
+```text
+1
+2
+3
+```
+
+<!--- TEST -->
+
+### Sequences
+
+If we are computing the numbers with some CPU-consuming blocking code
+(each computation taking 100ms), then we can represent the numbers using a [Sequence]:
+
+```kotlin
+fun simple(): Sequence<Int> = sequence { // sequence builder
+ for (i in 1..3) {
+ Thread.sleep(100) // pretend we are computing it
+ yield(i) // yield next value
+ }
+}
+
+fun main() {
+ simple().forEach { value -> println(value) }
+}
+```
+{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
+
+> You can get the full code from [here](../../kotlinx-coroutines-core/jvm/test/guide/example-flow-02.kt).
+>
+{type="note"}
+
+This code outputs the same numbers, but it waits 100ms before printing each one.
+
+<!--- TEST
+1
+2
+3
+-->
+
+### Suspending functions
+
+However, this computation blocks the main thread that is running the code.
+When these values are computed by asynchronous code we can mark the `simple` function with a `suspend` modifier,
+so that it can perform its work without blocking and return the result as a list:
+
+```kotlin
+import kotlinx.coroutines.*
+
+//sampleStart
+suspend fun simple(): List<Int> {
+ delay(1000) // pretend we are doing something asynchronous here
+ return listOf(1, 2, 3)
+}
+
+fun main() = runBlocking<Unit> {
+ simple().forEach { value -> println(value) }
+}
+//sampleEnd
+```
+{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
+
+> You can get the full code from [here](../../kotlinx-coroutines-core/jvm/test/guide/example-flow-03.kt).
+>
+{type="note"}
+
+This code prints the numbers after waiting for a second.
+
+<!--- TEST
+1
+2
+3
+-->
+
+### Flows
+
+Using the `List<Int>` result type, means we can only return all the values at once. To represent
+the stream of values that are being asynchronously computed, we can use a [`Flow<Int>`][Flow] type just like we would use the `Sequence<Int>` type for synchronously computed values:
+
+```kotlin
+import kotlinx.coroutines.*
+import kotlinx.coroutines.flow.*
+
+//sampleStart
+fun simple(): Flow<Int> = flow { // flow builder
+ for (i in 1..3) {
+ delay(100) // pretend we are doing something useful here
+ emit(i) // emit next value
+ }
+}
+
+fun main() = runBlocking<Unit> {
+ // Launch a concurrent coroutine to check if the main thread is blocked
+ launch {
+ for (k in 1..3) {
+ println("I'm not blocked $k")
+ delay(100)
+ }
+ }
+ // Collect the flow
+ simple().collect { value -> println(value) }
+}
+//sampleEnd
+```
+{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
+
+> You can get the full code from [here](../../kotlinx-coroutines-core/jvm/test/guide/example-flow-04.kt).
+>
+{type="note"}
+
+This code waits 100ms before printing each number without blocking the main thread. This is verified
+by printing "I'm not blocked" every 100ms from a separate coroutine that is running in the main thread:
+
+```text
+I'm not blocked 1
+1
+I'm not blocked 2
+2
+I'm not blocked 3
+3
+```
+
+<!--- TEST -->
+
+Notice the following differences in the code with the [Flow] from the earlier examples:
+
+* A builder function for [Flow] type is called [flow][_flow].
+* Code inside the `flow { ... }` builder block can suspend.
+* The `simple` function is no longer marked with `suspend` modifier.
+* Values are _emitted_ from the flow using [emit][FlowCollector.emit] function.
+* Values are _collected_ from the flow using [collect][collect] function.
+
+> We can replace [delay] with `Thread.sleep` in the body of `simple`'s `flow { ... }` and see that the main
+> thread is blocked in this case.
+>
+{type="note"}
+
+## Flows are cold
+
+Flows are _cold_ streams similar to sequences &mdash; the code inside a [flow][_flow] builder does not
+run until the flow is collected. This becomes clear in the following example:
+
+```kotlin
+import kotlinx.coroutines.*
+import kotlinx.coroutines.flow.*
+
+//sampleStart
+fun simple(): Flow<Int> = flow {
+ println("Flow started")
+ for (i in 1..3) {
+ delay(100)
+ emit(i)
+ }
+}
+
+fun main() = runBlocking<Unit> {
+ println("Calling simple function...")
+ val flow = simple()
+ println("Calling collect...")
+ flow.collect { value -> println(value) }
+ println("Calling collect again...")
+ flow.collect { value -> println(value) }
+}
+//sampleEnd
+```
+{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
+
+> You can get the full code from [here](../../kotlinx-coroutines-core/jvm/test/guide/example-flow-05.kt).
+>
+{type="note"}
+
+Which prints:
+
+```text
+Calling simple function...
+Calling collect...
+Flow started
+1
+2
+3
+Calling collect again...
+Flow started
+1
+2
+3
+```
+
+<!--- TEST -->
+
+This is a key reason the `simple` function (which returns a flow) is not marked with `suspend` modifier.
+By itself, `simple()` call returns quickly and does not wait for anything. The flow starts every time it is collected,
+that is why we see "Flow started" when we call `collect` again.
+
+## Flow cancellation basics
+
+Flow adheres to the general cooperative cancellation of coroutines. As usual, flow collection can be
+cancelled when the flow is suspended in a cancellable suspending function (like [delay]).
+The following example shows how the flow gets cancelled on a timeout when running in a [withTimeoutOrNull] block
+and stops executing its code:
+
+```kotlin
+import kotlinx.coroutines.*
+import kotlinx.coroutines.flow.*
+
+//sampleStart
+fun simple(): Flow<Int> = flow {
+ for (i in 1..3) {
+ delay(100)
+ println("Emitting $i")
+ emit(i)
+ }
+}
+
+fun main() = runBlocking<Unit> {
+ withTimeoutOrNull(250) { // Timeout after 250ms
+ simple().collect { value -> println(value) }
+ }
+ println("Done")
+}
+//sampleEnd
+```
+{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
+
+> You can get the full code from [here](../../kotlinx-coroutines-core/jvm/test/guide/example-flow-06.kt).
+>
+{type="note"}
+
+Notice how only two numbers get emitted by the flow in the `simple` function, producing the following output:
+
+```text
+Emitting 1
+1
+Emitting 2
+2
+Done
+```
+
+<!--- TEST -->
+
+See [Flow cancellation checks](#flow-cancellation-checks) section for more details.
+
+## Flow builders
+
+The `flow { ... }` builder from the previous examples is the most basic one. There are other builders for
+easier declaration of flows:
+
+* [flowOf] builder that defines a flow emitting a fixed set of values.
+* Various collections and sequences can be converted to flows using `.asFlow()` extension functions.
+
+So, the example that prints the numbers from 1 to 3 from a flow can be written as:
+
+```kotlin
+import kotlinx.coroutines.*
+import kotlinx.coroutines.flow.*
+
+fun main() = runBlocking<Unit> {
+//sampleStart
+ // Convert an integer range to a flow
+ (1..3).asFlow().collect { value -> println(value) }
+//sampleEnd
+}
+```
+{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
+
+> You can get the full code from [here](../../kotlinx-coroutines-core/jvm/test/guide/example-flow-07.kt).
+>
+{type="note"}
+
+<!--- TEST
+1
+2
+3
+-->
+
+## Intermediate flow operators
+
+Flows can be transformed with operators, just as you would with collections and sequences.
+Intermediate operators are applied to an upstream flow and return a downstream flow.
+These operators are cold, just like flows are. A call to such an operator is not
+a suspending function itself. It works quickly, returning the definition of a new transformed flow.
+
+The basic operators have familiar names like [map] and [filter].
+The important difference to sequences is that blocks of
+code inside these operators can call suspending functions.
+
+For example, a flow of incoming requests can be
+mapped to the results with the [map] operator, even when performing a request is a long-running
+operation that is implemented by a suspending function:
+
+```kotlin
+import kotlinx.coroutines.*
+import kotlinx.coroutines.flow.*
+
+//sampleStart
+suspend fun performRequest(request: Int): String {
+ delay(1000) // imitate long-running asynchronous work
+ return "response $request"
+}
+
+fun main() = runBlocking<Unit> {
+ (1..3).asFlow() // a flow of requests
+ .map { request -> performRequest(request) }
+ .collect { response -> println(response) }
+}
+//sampleEnd
+```
+{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
+
+> You can get the full code from [here](../../kotlinx-coroutines-core/jvm/test/guide/example-flow-08.kt).
+>
+{type="note"}
+
+It produces the following three lines, each line appearing after each second:
+
+```text
+response 1
+response 2
+response 3
+```
+
+<!--- TEST -->
+
+### Transform operator
+
+Among the flow transformation operators, the most general one is called [transform]. It can be used to imitate
+simple transformations like [map] and [filter], as well as implement more complex transformations.
+Using the `transform` operator, we can [emit][FlowCollector.emit] arbitrary values an arbitrary number of times.
+
+For example, using `transform` we can emit a string before performing a long-running asynchronous request
+and follow it with a response:
+
+```kotlin
+import kotlinx.coroutines.*
+import kotlinx.coroutines.flow.*
+
+suspend fun performRequest(request: Int): String {
+ delay(1000) // imitate long-running asynchronous work
+ return "response $request"
+}
+
+fun main() = runBlocking<Unit> {
+//sampleStart
+ (1..3).asFlow() // a flow of requests
+ .transform { request ->
+ emit("Making request $request")
+ emit(performRequest(request))
+ }
+ .collect { response -> println(response) }
+//sampleEnd
+}
+```
+{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
+
+> You can get the full code from [here](../../kotlinx-coroutines-core/jvm/test/guide/example-flow-09.kt).
+>
+{type="note"}
+
+The output of this code is:
+
+```text
+Making request 1
+response 1
+Making request 2
+response 2
+Making request 3
+response 3
+```
+
+<!--- TEST -->
+
+### Size-limiting operators
+
+Size-limiting intermediate operators like [take] cancel the execution of the flow when the corresponding limit
+is reached. Cancellation in coroutines is always performed by throwing an exception, so that all the resource-management
+functions (like `try { ... } finally { ... }` blocks) operate normally in case of cancellation:
+
+```kotlin
+import kotlinx.coroutines.*
+import kotlinx.coroutines.flow.*
+
+//sampleStart
+fun numbers(): Flow<Int> = flow {
+ try {
+ emit(1)
+ emit(2)
+ println("This line will not execute")
+ emit(3)
+ } finally {
+ println("Finally in numbers")
+ }
+}
+
+fun main() = runBlocking<Unit> {
+ numbers()
+ .take(2) // take only the first two
+ .collect { value -> println(value) }
+}
+//sampleEnd
+```
+{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
+
+> You can get the full code from [here](../../kotlinx-coroutines-core/jvm/test/guide/example-flow-10.kt).
+>
+{type="note"}
+
+The output of this code clearly shows that the execution of the `flow { ... }` body in the `numbers()` function
+stopped after emitting the second number:
+
+```text
+1
+2
+Finally in numbers
+```
+
+<!--- TEST -->
+
+## Terminal flow operators
+
+Terminal operators on flows are _suspending functions_ that start a collection of the flow.
+The [collect] operator is the most basic one, but there are other terminal operators, which can make it easier:
+
+* Conversion to various collections like [toList] and [toSet].
+* Operators to get the [first] value and to ensure that a flow emits a [single] value.
+* Reducing a flow to a value with [reduce] and [fold].
+
+For example:
+
+```kotlin
+import kotlinx.coroutines.*
+import kotlinx.coroutines.flow.*
+
+fun main() = runBlocking<Unit> {
+//sampleStart
+ val sum = (1..5).asFlow()
+ .map { it * it } // squares of numbers from 1 to 5
+ .reduce { a, b -> a + b } // sum them (terminal operator)
+ println(sum)
+//sampleEnd
+}
+```
+{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
+
+> You can get the full code from [here](../../kotlinx-coroutines-core/jvm/test/guide/example-flow-11.kt).
+>
+{type="note"}
+
+Prints a single number:
+
+```text
+55
+```
+
+<!--- TEST -->
+
+## Flows are sequential
+
+Each individual collection of a flow is performed sequentially unless special operators that operate
+on multiple flows are used. The collection works directly in the coroutine that calls a terminal operator.
+No new coroutines are launched by default.
+Each emitted value is processed by all the intermediate operators from
+upstream to downstream and is then delivered to the terminal operator after.
+
+See the following example that filters the even integers and maps them to strings:
+
+```kotlin
+import kotlinx.coroutines.*
+import kotlinx.coroutines.flow.*
+
+fun main() = runBlocking<Unit> {
+//sampleStart
+ (1..5).asFlow()
+ .filter {
+ println("Filter $it")
+ it % 2 == 0
+ }
+ .map {
+ println("Map $it")
+ "string $it"
+ }.collect {
+ println("Collect $it")
+ }
+//sampleEnd
+}
+```
+{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
+
+> You can get the full code from [here](../../kotlinx-coroutines-core/jvm/test/guide/example-flow-12.kt).
+>
+{type="note"}
+
+Producing:
+
+```text
+Filter 1
+Filter 2
+Map 2
+Collect string 2
+Filter 3
+Filter 4
+Map 4
+Collect string 4
+Filter 5
+```
+
+<!--- TEST -->
+
+## Flow context
+
+Collection of a flow always happens in the context of the calling coroutine. For example, if there is
+a `simple` flow, then the following code runs in the context specified
+by the author of this code, regardless of the implementation details of the `simple` flow:
+
+```kotlin
+withContext(context) {
+ simple().collect { value ->
+ println(value) // run in the specified context
+ }
+}
+```
+
+<!--- CLEAR -->
+
+This property of a flow is called _context preservation_.
+
+So, by default, code in the `flow { ... }` builder runs in the context that is provided by a collector
+of the corresponding flow. For example, consider the implementation of a `simple` function that prints the thread
+it is called on and emits three numbers:
+
+```kotlin
+import kotlinx.coroutines.*
+import kotlinx.coroutines.flow.*
+
+fun log(msg: String) = println("[${Thread.currentThread().name}] $msg")
+
+//sampleStart
+fun simple(): Flow<Int> = flow {
+ log("Started simple flow")
+ for (i in 1..3) {
+ emit(i)
+ }
+}
+
+fun main() = runBlocking<Unit> {
+ simple().collect { value -> log("Collected $value") }
+}
+//sampleEnd
+```
+{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
+
+> You can get the full code from [here](../../kotlinx-coroutines-core/jvm/test/guide/example-flow-13.kt).
+>
+{type="note"}
+
+Running this code produces:
+
+```text
+[main @coroutine#1] Started simple flow
+[main @coroutine#1] Collected 1
+[main @coroutine#1] Collected 2
+[main @coroutine#1] Collected 3
+```
+
+<!--- TEST FLEXIBLE_THREAD -->
+
+Since `simple().collect` is called from the main thread, the body of `simple`'s flow is also called in the main thread.
+This is the perfect default for fast-running or asynchronous code that does not care about the execution context and
+does not block the caller.
+
+### Wrong emission withContext
+
+However, the long-running CPU-consuming code might need to be executed in the context of [Dispatchers.Default] and UI-updating
+code might need to be executed in the context of [Dispatchers.Main]. Usually, [withContext] is used
+to change the context in the code using Kotlin coroutines, but code in the `flow { ... }` builder has to honor the context
+preservation property and is not allowed to [emit][FlowCollector.emit] from a different context.
+
+Try running the following code:
+
+```kotlin
+import kotlinx.coroutines.*
+import kotlinx.coroutines.flow.*
+
+//sampleStart
+fun simple(): Flow<Int> = flow {
+ // The WRONG way to change context for CPU-consuming code in flow builder
+ kotlinx.coroutines.withContext(Dispatchers.Default) {
+ for (i in 1..3) {
+ Thread.sleep(100) // pretend we are computing it in CPU-consuming way
+ emit(i) // emit next value
+ }
+ }
+}
+
+fun main() = runBlocking<Unit> {
+ simple().collect { value -> println(value) }
+}
+//sampleEnd
+```
+{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
+
+> You can get the full code from [here](../../kotlinx-coroutines-core/jvm/test/guide/example-flow-14.kt).
+>
+{type="note"}
+
+This code produces the following exception:
+
+```text
+Exception in thread "main" java.lang.IllegalStateException: Flow invariant is violated:
+ Flow was collected in [CoroutineId(1), "coroutine#1":BlockingCoroutine{Active}@5511c7f8, BlockingEventLoop@2eac3323],
+ but emission happened in [CoroutineId(1), "coroutine#1":DispatchedCoroutine{Active}@2dae0000, Dispatchers.Default].
+ Please refer to 'flow' documentation or use 'flowOn' instead
+ at ...
+```
+
+<!--- TEST EXCEPTION -->
+
+### flowOn operator
+
+The exception refers to the [flowOn] function that shall be used to change the context of the flow emission.
+The correct way to change the context of a flow is shown in the example below, which also prints the
+names of the corresponding threads to show how it all works:
+
+```kotlin
+import kotlinx.coroutines.*
+import kotlinx.coroutines.flow.*
+
+fun log(msg: String) = println("[${Thread.currentThread().name}] $msg")
+
+//sampleStart
+fun simple(): Flow<Int> = flow {
+ for (i in 1..3) {
+ Thread.sleep(100) // pretend we are computing it in CPU-consuming way
+ log("Emitting $i")
+ emit(i) // emit next value
+ }
+}.flowOn(Dispatchers.Default) // RIGHT way to change context for CPU-consuming code in flow builder
+
+fun main() = runBlocking<Unit> {
+ simple().collect { value ->
+ log("Collected $value")
+ }
+}
+//sampleEnd
+```
+{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
+
+> You can get the full code from [here](../../kotlinx-coroutines-core/jvm/test/guide/example-flow-15.kt).
+>
+{type="note"}
+
+Notice how `flow { ... }` works in the background thread, while collection happens in the main thread:
+
+<!--- TEST FLEXIBLE_THREAD
+[DefaultDispatcher-worker-1 @coroutine#2] Emitting 1
+[main @coroutine#1] Collected 1
+[DefaultDispatcher-worker-1 @coroutine#2] Emitting 2
+[main @coroutine#1] Collected 2
+[DefaultDispatcher-worker-1 @coroutine#2] Emitting 3
+[main @coroutine#1] Collected 3
+-->
+
+Another thing to observe here is that the [flowOn] operator has changed the default sequential nature of the flow.
+Now collection happens in one coroutine ("coroutine#1") and emission happens in another coroutine
+("coroutine#2") that is running in another thread concurrently with the collecting coroutine. The [flowOn] operator
+creates another coroutine for an upstream flow when it has to change the [CoroutineDispatcher] in its context.
+
+## Buffering
+
+Running different parts of a flow in different coroutines can be helpful from the standpoint of the overall time it takes
+to collect the flow, especially when long-running asynchronous operations are involved. For example, consider a case when
+the emission by a `simple` flow is slow, taking 100 ms to produce an element; and collector is also slow,
+taking 300 ms to process an element. Let's see how long it takes to collect such a flow with three numbers:
+
+```kotlin
+import kotlinx.coroutines.*
+import kotlinx.coroutines.flow.*
+import kotlin.system.*
+
+//sampleStart
+fun simple(): Flow<Int> = flow {
+ for (i in 1..3) {
+ delay(100) // pretend we are asynchronously waiting 100 ms
+ emit(i) // emit next value
+ }
+}
+
+fun main() = runBlocking<Unit> {
+ val time = measureTimeMillis {
+ simple().collect { value ->
+ delay(300) // pretend we are processing it for 300 ms
+ println(value)
+ }
+ }
+ println("Collected in $time ms")
+}
+//sampleEnd
+```
+{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
+
+> You can get the full code from [here](../../kotlinx-coroutines-core/jvm/test/guide/example-flow-16.kt).
+>
+{type="note"}
+
+It produces something like this, with the whole collection taking around 1200 ms (three numbers, 400 ms for each):
+
+```text
+1
+2
+3
+Collected in 1220 ms
+```
+
+<!--- TEST ARBITRARY_TIME -->
+
+We can use a [buffer] operator on a flow to run emitting code of the `simple` flow concurrently with collecting code,
+as opposed to running them sequentially:
+
+```kotlin
+import kotlinx.coroutines.*
+import kotlinx.coroutines.flow.*
+import kotlin.system.*
+
+fun simple(): Flow<Int> = flow {
+ for (i in 1..3) {
+ delay(100) // pretend we are asynchronously waiting 100 ms
+ emit(i) // emit next value
+ }
+}
+
+fun main() = runBlocking<Unit> {
+//sampleStart
+ val time = measureTimeMillis {
+ simple()
+ .buffer() // buffer emissions, don't wait
+ .collect { value ->
+ delay(300) // pretend we are processing it for 300 ms
+ println(value)
+ }
+ }
+ println("Collected in $time ms")
+//sampleEnd
+}
+```
+{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
+
+> You can get the full code from [here](../../kotlinx-coroutines-core/jvm/test/guide/example-flow-17.kt).
+>
+{type="note"}
+
+It produces the same numbers just faster, as we have effectively created a processing pipeline,
+having to only wait 100 ms for the first number and then spending only 300 ms to process
+each number. This way it takes around 1000 ms to run:
+
+```text
+1
+2
+3
+Collected in 1071 ms
+```
+
+<!--- TEST ARBITRARY_TIME -->
+
+> Note that the [flowOn] operator uses the same buffering mechanism when it has to change a [CoroutineDispatcher],
+> but here we explicitly request buffering without changing the execution context.
+>
+{type="note"}
+
+### Conflation
+
+When a flow represents partial results of the operation or operation status updates, it may not be necessary
+to process each value, but instead, only most recent ones. In this case, the [conflate] operator can be used to skip
+intermediate values when a collector is too slow to process them. Building on the previous example:
+
+```kotlin
+import kotlinx.coroutines.*
+import kotlinx.coroutines.flow.*
+import kotlin.system.*
+
+fun simple(): Flow<Int> = flow {
+ for (i in 1..3) {
+ delay(100) // pretend we are asynchronously waiting 100 ms
+ emit(i) // emit next value
+ }
+}
+
+fun main() = runBlocking<Unit> {
+//sampleStart
+ val time = measureTimeMillis {
+ simple()
+ .conflate() // conflate emissions, don't process each one
+ .collect { value ->
+ delay(300) // pretend we are processing it for 300 ms
+ println(value)
+ }
+ }
+ println("Collected in $time ms")
+//sampleEnd
+}
+```
+{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
+
+> You can get the full code from [here](../../kotlinx-coroutines-core/jvm/test/guide/example-flow-18.kt).
+>
+{type="note"}
+
+We see that while the first number was still being processed the second, and third were already produced, so
+the second one was _conflated_ and only the most recent (the third one) was delivered to the collector:
+
+```text
+1
+3
+Collected in 758 ms
+```
+
+<!--- TEST ARBITRARY_TIME -->
+
+### Processing the latest value
+
+Conflation is one way to speed up processing when both the emitter and collector are slow. It does it by dropping emitted values.
+The other way is to cancel a slow collector and restart it every time a new value is emitted. There is
+a family of `xxxLatest` operators that perform the same essential logic of a `xxx` operator, but cancel the
+code in their block on a new value. Let's try changing [conflate] to [collectLatest] in the previous example:
+
+```kotlin
+import kotlinx.coroutines.*
+import kotlinx.coroutines.flow.*
+import kotlin.system.*
+
+fun simple(): Flow<Int> = flow {
+ for (i in 1..3) {
+ delay(100) // pretend we are asynchronously waiting 100 ms
+ emit(i) // emit next value
+ }
+}
+
+fun main() = runBlocking<Unit> {
+//sampleStart
+ val time = measureTimeMillis {
+ simple()
+ .collectLatest { value -> // cancel & restart on the latest value
+ println("Collecting $value")
+ delay(300) // pretend we are processing it for 300 ms
+ println("Done $value")
+ }
+ }
+ println("Collected in $time ms")
+//sampleEnd
+}
+```
+{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
+
+> You can get the full code from [here](../../kotlinx-coroutines-core/jvm/test/guide/example-flow-19.kt).
+>
+{type="note"}
+
+Since the body of [collectLatest] takes 300 ms, but new values are emitted every 100 ms, we see that the block
+is run on every value, but completes only for the last value:
+
+```text
+Collecting 1
+Collecting 2
+Collecting 3
+Done 3
+Collected in 741 ms
+```
+
+<!--- TEST ARBITRARY_TIME -->
+
+## Composing multiple flows
+
+There are lots of ways to compose multiple flows.
+
+### Zip
+
+Just like the [Sequence.zip] extension function in the Kotlin standard library,
+flows have a [zip] operator that combines the corresponding values of two flows:
+
+```kotlin
+import kotlinx.coroutines.*
+import kotlinx.coroutines.flow.*
+
+fun main() = runBlocking<Unit> {
+//sampleStart
+ val nums = (1..3).asFlow() // numbers 1..3
+ val strs = flowOf("one", "two", "three") // strings
+ nums.zip(strs) { a, b -> "$a -> $b" } // compose a single string
+ .collect { println(it) } // collect and print
+//sampleEnd
+}
+```
+{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
+
+> You can get the full code from [here](../../kotlinx-coroutines-core/jvm/test/guide/example-flow-20.kt).
+>
+{type="note"}
+
+This example prints:
+
+```text
+1 -> one
+2 -> two
+3 -> three
+```
+
+<!--- TEST -->
+
+### Combine
+
+When flow represents the most recent value of a variable or operation (see also the related
+section on [conflation](#conflation)), it might be needed to perform a computation that depends on
+the most recent values of the corresponding flows and to recompute it whenever any of the upstream
+flows emit a value. The corresponding family of operators is called [combine].
+
+For example, if the numbers in the previous example update every 300ms, but strings update every 400 ms,
+then zipping them using the [zip] operator will still produce the same result,
+albeit results that are printed every 400 ms:
+
+> We use a [onEach] intermediate operator in this example to delay each element and make the code
+> that emits sample flows more declarative and shorter.
+>
+{type="note"}
+
+```kotlin
+import kotlinx.coroutines.*
+import kotlinx.coroutines.flow.*
+
+fun main() = runBlocking<Unit> {
+//sampleStart
+ val nums = (1..3).asFlow().onEach { delay(300) } // numbers 1..3 every 300 ms
+ val strs = flowOf("one", "two", "three").onEach { delay(400) } // strings every 400 ms
+ val startTime = System.currentTimeMillis() // remember the start time
+ nums.zip(strs) { a, b -> "$a -> $b" } // compose a single string with "zip"
+ .collect { value -> // collect and print
+ println("$value at ${System.currentTimeMillis() - startTime} ms from start")
+ }
+//sampleEnd
+}
+```
+{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
+
+> You can get the full code from [here](../../kotlinx-coroutines-core/jvm/test/guide/example-flow-21.kt).
+>
+{type="note"}
+
+<!--- TEST ARBITRARY_TIME
+1 -> one at 437 ms from start
+2 -> two at 837 ms from start
+3 -> three at 1243 ms from start
+-->
+
+However, when using a [combine] operator here instead of a [zip]:
+
+```kotlin
+import kotlinx.coroutines.*
+import kotlinx.coroutines.flow.*
+
+fun main() = runBlocking<Unit> {
+//sampleStart
+ val nums = (1..3).asFlow().onEach { delay(300) } // numbers 1..3 every 300 ms
+ val strs = flowOf("one", "two", "three").onEach { delay(400) } // strings every 400 ms
+ val startTime = System.currentTimeMillis() // remember the start time
+ nums.combine(strs) { a, b -> "$a -> $b" } // compose a single string with "combine"
+ .collect { value -> // collect and print
+ println("$value at ${System.currentTimeMillis() - startTime} ms from start")
+ }
+//sampleEnd
+}
+```
+{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
+
+> You can get the full code from [here](../../kotlinx-coroutines-core/jvm/test/guide/example-flow-22.kt).
+>
+{type="note"}
+
+We get quite a different output, where a line is printed at each emission from either `nums` or `strs` flows:
+
+```text
+1 -> one at 452 ms from start
+2 -> one at 651 ms from start
+2 -> two at 854 ms from start
+3 -> two at 952 ms from start
+3 -> three at 1256 ms from start
+```
+
+<!--- TEST ARBITRARY_TIME -->
+
+## Flattening flows
+
+Flows represent asynchronously received sequences of values, so it is quite easy to get in a situation where
+each value triggers a request for another sequence of values. For example, we can have the following
+function that returns a flow of two strings 500 ms apart:
+
+```kotlin
+fun requestFlow(i: Int): Flow<String> = flow {
+ emit("$i: First")
+ delay(500) // wait 500 ms
+ emit("$i: Second")
+}
+```
+
+<!--- CLEAR -->
+
+Now if we have a flow of three integers and call `requestFlow` for each of them like this:
+
+```kotlin
+(1..3).asFlow().map { requestFlow(it) }
+```
+
+<!--- CLEAR -->
+
+Then we end up with a flow of flows (`Flow<Flow<String>>`) that needs to be _flattened_ into a single flow for
+further processing. Collections and sequences have [flatten][Sequence.flatten] and [flatMap][Sequence.flatMap]
+operators for this. However, due to the asynchronous nature of flows they call for different _modes_ of flattening,
+as such, there is a family of flattening operators on flows.
+
+### flatMapConcat
+
+Concatenating mode is implemented by [flatMapConcat] and [flattenConcat] operators. They are the most direct
+analogues of the corresponding sequence operators. They wait for the inner flow to complete before
+starting to collect the next one as the following example shows:
+
+```kotlin
+import kotlinx.coroutines.*
+import kotlinx.coroutines.flow.*
+
+fun requestFlow(i: Int): Flow<String> = flow {
+ emit("$i: First")
+ delay(500) // wait 500 ms
+ emit("$i: Second")
+}
+
+fun main() = runBlocking<Unit> {
+//sampleStart
+ val startTime = System.currentTimeMillis() // remember the start time
+ (1..3).asFlow().onEach { delay(100) } // a number every 100 ms
+ .flatMapConcat { requestFlow(it) }
+ .collect { value -> // collect and print
+ println("$value at ${System.currentTimeMillis() - startTime} ms from start")
+ }
+//sampleEnd
+}
+```
+{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
+
+> You can get the full code from [here](../../kotlinx-coroutines-core/jvm/test/guide/example-flow-23.kt).
+>
+{type="note"}
+
+The sequential nature of [flatMapConcat] is clearly seen in the output:
+
+```text
+1: First at 121 ms from start
+1: Second at 622 ms from start
+2: First at 727 ms from start
+2: Second at 1227 ms from start
+3: First at 1328 ms from start
+3: Second at 1829 ms from start
+```
+
+<!--- TEST ARBITRARY_TIME -->
+
+### flatMapMerge
+
+Another flattening mode is to concurrently collect all the incoming flows and merge their values into
+a single flow so that values are emitted as soon as possible.
+It is implemented by [flatMapMerge] and [flattenMerge] operators. They both accept an optional
+`concurrency` parameter that limits the number of concurrent flows that are collected at the same time
+(it is equal to [DEFAULT_CONCURRENCY] by default).
+
+```kotlin
+import kotlinx.coroutines.*
+import kotlinx.coroutines.flow.*
+
+fun requestFlow(i: Int): Flow<String> = flow {
+ emit("$i: First")
+ delay(500) // wait 500 ms
+ emit("$i: Second")
+}
+
+fun main() = runBlocking<Unit> {
+//sampleStart
+ val startTime = System.currentTimeMillis() // remember the start time
+ (1..3).asFlow().onEach { delay(100) } // a number every 100 ms
+ .flatMapMerge { requestFlow(it) }
+ .collect { value -> // collect and print
+ println("$value at ${System.currentTimeMillis() - startTime} ms from start")
+ }
+//sampleEnd
+}
+```
+{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
+
+> You can get the full code from [here](../../kotlinx-coroutines-core/jvm/test/guide/example-flow-24.kt).
+>
+{type="note"}
+
+The concurrent nature of [flatMapMerge] is obvious:
+
+```text
+1: First at 136 ms from start
+2: First at 231 ms from start
+3: First at 333 ms from start
+1: Second at 639 ms from start
+2: Second at 732 ms from start
+3: Second at 833 ms from start
+```
+
+<!--- TEST ARBITRARY_TIME -->
+
+> Note that the [flatMapMerge] calls its block of code (`{ requestFlow(it) }` in this example) sequentially, but
+> collects the resulting flows concurrently, it is the equivalent of performing a sequential
+> `map { requestFlow(it) }` first and then calling [flattenMerge] on the result.
+>
+{type="note"}
+
+### flatMapLatest
+
+In a similar way to the [collectLatest] operator, that was shown in
+["Processing the latest value"](#processing-the-latest-value) section, there is the corresponding "Latest"
+flattening mode where a collection of the previous flow is cancelled as soon as new flow is emitted.
+It is implemented by the [flatMapLatest] operator.
+
+```kotlin
+import kotlinx.coroutines.*
+import kotlinx.coroutines.flow.*
+
+fun requestFlow(i: Int): Flow<String> = flow {
+ emit("$i: First")
+ delay(500) // wait 500 ms
+ emit("$i: Second")
+}
+
+fun main() = runBlocking<Unit> {
+//sampleStart
+ val startTime = System.currentTimeMillis() // remember the start time
+ (1..3).asFlow().onEach { delay(100) } // a number every 100 ms
+ .flatMapLatest { requestFlow(it) }
+ .collect { value -> // collect and print
+ println("$value at ${System.currentTimeMillis() - startTime} ms from start")
+ }
+//sampleEnd
+}
+```
+{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
+
+> You can get the full code from [here](../../kotlinx-coroutines-core/jvm/test/guide/example-flow-25.kt).
+>
+{type="note"}
+
+The output here in this example is a good demonstration of how [flatMapLatest] works:
+
+```text
+1: First at 142 ms from start
+2: First at 322 ms from start
+3: First at 425 ms from start
+3: Second at 931 ms from start
+```
+
+<!--- TEST ARBITRARY_TIME -->
+
+> Note that [flatMapLatest] cancels all the code in its block (`{ requestFlow(it) }` in this example) on a new value.
+> It makes no difference in this particular example, because the call to `requestFlow` itself is fast, not-suspending,
+> and cannot be cancelled. However, it would show up if we were to use suspending functions like `delay` in there.
+>
+{type="note"}
+
+## Flow exceptions
+
+Flow collection can complete with an exception when an emitter or code inside the operators throw an exception.
+There are several ways to handle these exceptions.
+
+### Collector try and catch
+
+A collector can use Kotlin's [`try/catch`][exceptions] block to handle exceptions:
+
+```kotlin
+import kotlinx.coroutines.*
+import kotlinx.coroutines.flow.*
+
+//sampleStart
+fun simple(): Flow<Int> = flow {
+ for (i in 1..3) {
+ println("Emitting $i")
+ emit(i) // emit next value
+ }
+}
+
+fun main() = runBlocking<Unit> {
+ try {
+ simple().collect { value ->
+ println(value)
+ check(value <= 1) { "Collected $value" }
+ }
+ } catch (e: Throwable) {
+ println("Caught $e")
+ }
+}
+//sampleEnd
+```
+{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
+
+> You can get the full code from [here](../../kotlinx-coroutines-core/jvm/test/guide/example-flow-26.kt).
+>
+{type="note"}
+
+This code successfully catches an exception in [collect] terminal operator and,
+as we see, no more values are emitted after that:
+
+```text
+Emitting 1
+1
+Emitting 2
+2
+Caught java.lang.IllegalStateException: Collected 2
+```
+
+<!--- TEST -->
+
+### Everything is caught
+
+The previous example actually catches any exception happening in the emitter or in any intermediate or terminal operators.
+For example, let's change the code so that emitted values are [mapped][map] to strings,
+but the corresponding code produces an exception:
+
+```kotlin
+import kotlinx.coroutines.*
+import kotlinx.coroutines.flow.*
+
+//sampleStart
+fun simple(): Flow<String> =
+ flow {
+ for (i in 1..3) {
+ println("Emitting $i")
+ emit(i) // emit next value
+ }
+ }
+ .map { value ->
+ check(value <= 1) { "Crashed on $value" }
+ "string $value"
+ }
+
+fun main() = runBlocking<Unit> {
+ try {
+ simple().collect { value -> println(value) }
+ } catch (e: Throwable) {
+ println("Caught $e")
+ }
+}
+//sampleEnd
+```
+{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
+
+> You can get the full code from [here](../../kotlinx-coroutines-core/jvm/test/guide/example-flow-27.kt).
+>
+{type="note"}
+
+This exception is still caught and collection is stopped:
+
+```text
+Emitting 1
+string 1
+Emitting 2
+Caught java.lang.IllegalStateException: Crashed on 2
+```
+
+<!--- TEST -->
+
+## Exception transparency
+
+But how can code of the emitter encapsulate its exception handling behavior?
+
+Flows must be _transparent to exceptions_ and it is a violation of the exception transparency to [emit][FlowCollector.emit] values in the
+`flow { ... }` builder from inside of a `try/catch` block. This guarantees that a collector throwing an exception
+can always catch it using `try/catch` as in the previous example.
+
+The emitter can use a [catch] operator that preserves this exception transparency and allows encapsulation
+of its exception handling. The body of the `catch` operator can analyze an exception
+and react to it in different ways depending on which exception was caught:
+
+* Exceptions can be rethrown using `throw`.
+* Exceptions can be turned into emission of values using [emit][FlowCollector.emit] from the body of [catch].
+* Exceptions can be ignored, logged, or processed by some other code.
+
+For example, let us emit the text on catching an exception:
+
+```kotlin
+import kotlinx.coroutines.*
+import kotlinx.coroutines.flow.*
+
+fun simple(): Flow<String> =
+ flow {
+ for (i in 1..3) {
+ println("Emitting $i")
+ emit(i) // emit next value
+ }
+ }
+ .map { value ->
+ check(value <= 1) { "Crashed on $value" }
+ "string $value"
+ }
+
+fun main() = runBlocking<Unit> {
+//sampleStart
+ simple()
+ .catch { e -> emit("Caught $e") } // emit on exception
+ .collect { value -> println(value) }
+//sampleEnd
+}
+```
+{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
+
+> You can get the full code from [here](../../kotlinx-coroutines-core/jvm/test/guide/example-flow-28.kt).
+>
+{type="note"}
+
+The output of the example is the same, even though we do not have `try/catch` around the code anymore.
+
+<!--- TEST
+Emitting 1
+string 1
+Emitting 2
+Caught java.lang.IllegalStateException: Crashed on 2
+-->
+
+### Transparent catch
+
+The [catch] intermediate operator, honoring exception transparency, catches only upstream exceptions
+(that is an exception from all the operators above `catch`, but not below it).
+If the block in `collect { ... }` (placed below `catch`) throws an exception then it escapes:
+
+```kotlin
+import kotlinx.coroutines.*
+import kotlinx.coroutines.flow.*
+
+//sampleStart
+fun simple(): Flow<Int> = flow {
+ for (i in 1..3) {
+ println("Emitting $i")
+ emit(i)
+ }
+}
+
+fun main() = runBlocking<Unit> {
+ simple()
+ .catch { e -> println("Caught $e") } // does not catch downstream exceptions
+ .collect { value ->
+ check(value <= 1) { "Collected $value" }
+ println(value)
+ }
+}
+//sampleEnd
+```
+{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
+
+> You can get the full code from [here](../../kotlinx-coroutines-core/jvm/test/guide/example-flow-29.kt).
+>
+{type="note"}
+
+A "Caught ..." message is not printed despite there being a `catch` operator:
+
+```text
+Emitting 1
+1
+Emitting 2
+Exception in thread "main" java.lang.IllegalStateException: Collected 2
+ at ...
+```
+
+<!--- TEST EXCEPTION -->
+
+### Catching declaratively
+
+We can combine the declarative nature of the [catch] operator with a desire to handle all the exceptions, by moving the body
+of the [collect] operator into [onEach] and putting it before the `catch` operator. Collection of this flow must
+be triggered by a call to `collect()` without parameters:
+
+```kotlin
+import kotlinx.coroutines.*
+import kotlinx.coroutines.flow.*
+
+fun simple(): Flow<Int> = flow {
+ for (i in 1..3) {
+ println("Emitting $i")
+ emit(i)
+ }
+}
+
+fun main() = runBlocking<Unit> {
+//sampleStart
+ simple()
+ .onEach { value ->
+ check(value <= 1) { "Collected $value" }
+ println(value)
+ }
+ .catch { e -> println("Caught $e") }
+ .collect()
+//sampleEnd
+}
+```
+{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
+
+> You can get the full code from [here](../../kotlinx-coroutines-core/jvm/test/guide/example-flow-30.kt).
+>
+{type="note"}
+
+Now we can see that a "Caught ..." message is printed and so we can catch all the exceptions without explicitly
+using a `try/catch` block:
+
+```text
+Emitting 1
+1
+Emitting 2
+Caught java.lang.IllegalStateException: Collected 2
+```
+
+<!--- TEST EXCEPTION -->
+
+## Flow completion
+
+When flow collection completes (normally or exceptionally) it may need to execute an action.
+As you may have already noticed, it can be done in two ways: imperative or declarative.
+
+### Imperative finally block
+
+In addition to `try`/`catch`, a collector can also use a `finally` block to execute an action
+upon `collect` completion.
+
+```kotlin
+import kotlinx.coroutines.*
+import kotlinx.coroutines.flow.*
+
+//sampleStart
+fun simple(): Flow<Int> = (1..3).asFlow()
+
+fun main() = runBlocking<Unit> {
+ try {
+ simple().collect { value -> println(value) }
+ } finally {
+ println("Done")
+ }
+}
+//sampleEnd
+```
+{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
+
+> You can get the full code from [here](../../kotlinx-coroutines-core/jvm/test/guide/example-flow-31.kt).
+>
+{type="note"}
+
+This code prints three numbers produced by the `simple` flow followed by a "Done" string:
+
+```text
+1
+2
+3
+Done
+```
+
+<!--- TEST -->
+
+### Declarative handling
+
+For the declarative approach, flow has [onCompletion] intermediate operator that is invoked
+when the flow has completely collected.
+
+The previous example can be rewritten using an [onCompletion] operator and produces the same output:
+
+```kotlin
+import kotlinx.coroutines.*
+import kotlinx.coroutines.flow.*
+
+fun simple(): Flow<Int> = (1..3).asFlow()
+
+fun main() = runBlocking<Unit> {
+//sampleStart
+ simple()
+ .onCompletion { println("Done") }
+ .collect { value -> println(value) }
+//sampleEnd
+}
+```
+{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
+
+> You can get the full code from [here](../../kotlinx-coroutines-core/jvm/test/guide/example-flow-32.kt).
+>
+{type="note"}
+
+<!--- TEST
+1
+2
+3
+Done
+-->
+
+The key advantage of [onCompletion] is a nullable `Throwable` parameter of the lambda that can be used
+to determine whether the flow collection was completed normally or exceptionally. In the following
+example the `simple` flow throws an exception after emitting the number 1:
+
+```kotlin
+import kotlinx.coroutines.*
+import kotlinx.coroutines.flow.*
+
+//sampleStart
+fun simple(): Flow<Int> = flow {
+ emit(1)
+ throw RuntimeException()
+}
+
+fun main() = runBlocking<Unit> {
+ simple()
+ .onCompletion { cause -> if (cause != null) println("Flow completed exceptionally") }
+ .catch { cause -> println("Caught exception") }
+ .collect { value -> println(value) }
+}
+//sampleEnd
+```
+{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
+
+> You can get the full code from [here](../../kotlinx-coroutines-core/jvm/test/guide/example-flow-33.kt).
+>
+{type="note"}
+
+As you may expect, it prints:
+
+```text
+1
+Flow completed exceptionally
+Caught exception
+```
+
+<!--- TEST -->
+
+The [onCompletion] operator, unlike [catch], does not handle the exception. As we can see from the above
+example code, the exception still flows downstream. It will be delivered to further `onCompletion` operators
+and can be handled with a `catch` operator.
+
+### Successful completion
+
+Another difference with [catch] operator is that [onCompletion] sees all exceptions and receives
+a `null` exception only on successful completion of the upstream flow (without cancellation or failure).
+
+```kotlin
+import kotlinx.coroutines.*
+import kotlinx.coroutines.flow.*
+
+//sampleStart
+fun simple(): Flow<Int> = (1..3).asFlow()
+
+fun main() = runBlocking<Unit> {
+ simple()
+ .onCompletion { cause -> println("Flow completed with $cause") }
+ .collect { value ->
+ check(value <= 1) { "Collected $value" }
+ println(value)
+ }
+}
+//sampleEnd
+```
+{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
+
+> You can get the full code from [here](../../kotlinx-coroutines-core/jvm/test/guide/example-flow-34.kt).
+>
+{type="note"}
+
+We can see the completion cause is not null, because the flow was aborted due to downstream exception:
+
+```text
+1
+Flow completed with java.lang.IllegalStateException: Collected 2
+Exception in thread "main" java.lang.IllegalStateException: Collected 2
+```
+
+<!--- TEST EXCEPTION -->
+
+## Imperative versus declarative
+
+Now we know how to collect flow, and handle its completion and exceptions in both imperative and declarative ways.
+The natural question here is, which approach is preferred and why?
+As a library, we do not advocate for any particular approach and believe that both options
+are valid and should be selected according to your own preferences and code style.
+
+## Launching flow
+
+It is easy to use flows to represent asynchronous events that are coming from some source.
+In this case, we need an analogue of the `addEventListener` function that registers a piece of code with a reaction
+for incoming events and continues further work. The [onEach] operator can serve this role.
+However, `onEach` is an intermediate operator. We also need a terminal operator to collect the flow.
+Otherwise, just calling `onEach` has no effect.
+
+If we use the [collect] terminal operator after `onEach`, then the code after it will wait until the flow is collected:
+
+```kotlin
+import kotlinx.coroutines.*
+import kotlinx.coroutines.flow.*
+
+//sampleStart
+// Imitate a flow of events
+fun events(): Flow<Int> = (1..3).asFlow().onEach { delay(100) }
+
+fun main() = runBlocking<Unit> {
+ events()
+ .onEach { event -> println("Event: $event") }
+ .collect() // <--- Collecting the flow waits
+ println("Done")
+}
+//sampleEnd
+```
+{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
+
+> You can get the full code from [here](../../kotlinx-coroutines-core/jvm/test/guide/example-flow-35.kt).
+>
+{type="note"}
+
+As you can see, it prints:
+
+```text
+Event: 1
+Event: 2
+Event: 3
+Done
+```
+
+<!--- TEST -->
+
+The [launchIn] terminal operator comes in handy here. By replacing `collect` with `launchIn` we can
+launch a collection of the flow in a separate coroutine, so that execution of further code
+immediately continues:
+
+```kotlin
+import kotlinx.coroutines.*
+import kotlinx.coroutines.flow.*
+
+// Imitate a flow of events
+fun events(): Flow<Int> = (1..3).asFlow().onEach { delay(100) }
+
+//sampleStart
+fun main() = runBlocking<Unit> {
+ events()
+ .onEach { event -> println("Event: $event") }
+ .launchIn(this) // <--- Launching the flow in a separate coroutine
+ println("Done")
+}
+//sampleEnd
+```
+{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
+
+> You can get the full code from [here](../../kotlinx-coroutines-core/jvm/test/guide/example-flow-36.kt).
+>
+{type="note"}
+
+It prints:
+
+```text
+Done
+Event: 1
+Event: 2
+Event: 3
+```
+
+<!--- TEST -->
+
+The required parameter to `launchIn` must specify a [CoroutineScope] in which the coroutine to collect the flow is
+launched. In the above example this scope comes from the [runBlocking]
+coroutine builder, so while the flow is running, this [runBlocking] scope waits for completion of its child coroutine
+and keeps the main function from returning and terminating this example.
+
+In actual applications a scope will come from an entity with a limited
+lifetime. As soon as the lifetime of this entity is terminated the corresponding scope is cancelled, cancelling
+the collection of the corresponding flow. This way the pair of `onEach { ... }.launchIn(scope)` works
+like the `addEventListener`. However, there is no need for the corresponding `removeEventListener` function,
+as cancellation and structured concurrency serve this purpose.
+
+Note that [launchIn] also returns a [Job], which can be used to [cancel][Job.cancel] the corresponding flow collection
+coroutine only without cancelling the whole scope or to [join][Job.join] it.
+
+### Flow cancellation checks
+
+For convenience, the [flow][_flow] builder performs additional [ensureActive] checks for cancellation on each emitted value.
+It means that a busy loop emitting from a `flow { ... }` is cancellable:
+
+```kotlin
+import kotlinx.coroutines.*
+import kotlinx.coroutines.flow.*
+
+//sampleStart
+fun foo(): Flow<Int> = flow {
+ for (i in 1..5) {
+ println("Emitting $i")
+ emit(i)
+ }
+}
+
+fun main() = runBlocking<Unit> {
+ foo().collect { value ->
+ if (value == 3) cancel()
+ println(value)
+ }
+}
+//sampleEnd
+```
+{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
+
+> You can get the full code from [here](../../kotlinx-coroutines-core/jvm/test/guide/example-flow-37.kt).
+>
+{type="note"}
+
+We get only numbers up to 3 and a [CancellationException] after trying to emit number 4:
+
+```text
+Emitting 1
+1
+Emitting 2
+2
+Emitting 3
+3
+Emitting 4
+Exception in thread "main" kotlinx.coroutines.JobCancellationException: BlockingCoroutine was cancelled; job="coroutine#1":BlockingCoroutine{Cancelled}@6d7b4f4c
+```
+
+<!--- TEST EXCEPTION -->
+
+However, most other flow operators do not do additional cancellation checks on their own for performance reasons.
+For example, if you use [IntRange.asFlow] extension to write the same busy loop and don't suspend anywhere,
+then there are no checks for cancellation:
+
+```kotlin
+import kotlinx.coroutines.*
+import kotlinx.coroutines.flow.*
+
+//sampleStart
+fun main() = runBlocking<Unit> {
+ (1..5).asFlow().collect { value ->
+ if (value == 3) cancel()
+ println(value)
+ }
+}
+//sampleEnd
+```
+{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
+
+> You can get the full code from [here](../../kotlinx-coroutines-core/jvm/test/guide/example-flow-38.kt).
+>
+{type="note"}
+
+All numbers from 1 to 5 are collected and cancellation gets detected only before return from `runBlocking`:
+
+```text
+1
+2
+3
+4
+5
+Exception in thread "main" kotlinx.coroutines.JobCancellationException: BlockingCoroutine was cancelled; job="coroutine#1":BlockingCoroutine{Cancelled}@3327bd23
+```
+
+<!--- TEST EXCEPTION -->
+
+#### Making busy flow cancellable
+
+In the case where you have a busy loop with coroutines you must explicitly check for cancellation.
+You can add `.onEach { currentCoroutineContext().ensureActive() }`, but there is a ready-to-use
+[cancellable] operator provided to do that:
+
+```kotlin
+import kotlinx.coroutines.*
+import kotlinx.coroutines.flow.*
+
+//sampleStart
+fun main() = runBlocking<Unit> {
+ (1..5).asFlow().cancellable().collect { value ->
+ if (value == 3) cancel()
+ println(value)
+ }
+}
+//sampleEnd
+```
+{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
+
+> You can get the full code from [here](../../kotlinx-coroutines-core/jvm/test/guide/example-flow-39.kt).
+>
+{type="note"}
+
+With the `cancellable` operator only the numbers from 1 to 3 are collected:
+
+```text
+1
+2
+3
+Exception in thread "main" kotlinx.coroutines.JobCancellationException: BlockingCoroutine was cancelled; job="coroutine#1":BlockingCoroutine{Cancelled}@5ec0a365
+```
+
+<!--- TEST EXCEPTION -->
+
+## Flow and Reactive Streams
+
+For those who are familiar with [Reactive Streams](https://www.reactive-streams.org/) or reactive frameworks such as RxJava and project Reactor,
+design of the Flow may look very familiar.
+
+Indeed, its design was inspired by Reactive Streams and its various implementations. But Flow main goal is to have as simple design as possible,
+be Kotlin and suspension friendly and respect structured concurrency. Achieving this goal would be impossible without reactive pioneers and their tremendous work. You can read the complete story in [Reactive Streams and Kotlin Flows](https://medium.com/@elizarov/reactive-streams-and-kotlin-flows-bfd12772cda4) article.
+
+While being different, conceptually, Flow *is* a reactive stream and it is possible to convert it to the reactive (spec and TCK compliant) Publisher and vice versa.
+Such converters are provided by `kotlinx.coroutines` out-of-the-box and can be found in corresponding reactive modules (`kotlinx-coroutines-reactive` for Reactive Streams, `kotlinx-coroutines-reactor` for Project Reactor and `kotlinx-coroutines-rx2`/`kotlinx-coroutines-rx3` for RxJava2/RxJava3).
+Integration modules include conversions from and to `Flow`, integration with Reactor's `Context` and suspension-friendly ways to work with various reactive entities.
+
+<!-- stdlib references -->
+
+[collections]: https://kotlinlang.org/docs/reference/collections-overview.html
+[List]: https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/-list/index.html
+[forEach]: https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/for-each.html
+[Sequence]: https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.sequences/index.html
+[Sequence.zip]: https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.sequences/zip.html
+[Sequence.flatten]: https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.sequences/flatten.html
+[Sequence.flatMap]: https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.sequences/flat-map.html
+[exceptions]: https://kotlinlang.org/docs/reference/exceptions.html
+
+<!--- MODULE kotlinx-coroutines-core -->
+<!--- INDEX kotlinx.coroutines -->
+
+[delay]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/delay.html
+[withTimeoutOrNull]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/with-timeout-or-null.html
+[Dispatchers.Default]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-dispatchers/-default.html
+[Dispatchers.Main]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-dispatchers/-main.html
+[withContext]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/with-context.html
+[CoroutineDispatcher]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-dispatcher/index.html
+[CoroutineScope]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-scope/index.html
+[runBlocking]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/run-blocking.html
+[Job]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job/index.html
+[Job.cancel]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/cancel.html
+[Job.join]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job/join.html
+[ensureActive]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/ensure-active.html
+[CancellationException]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-cancellation-exception/index.html
+
+<!--- INDEX kotlinx.coroutines.flow -->
+
+[Flow]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/-flow/index.html
+[_flow]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/flow.html
+[FlowCollector.emit]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/-flow-collector/emit.html
+[collect]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/collect.html
+[flowOf]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/flow-of.html
+[map]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/map.html
+[filter]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/filter.html
+[transform]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/transform.html
+[take]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/take.html
+[toList]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/to-list.html
+[toSet]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/to-set.html
+[first]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/first.html
+[single]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/single.html
+[reduce]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/reduce.html
+[fold]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/fold.html
+[flowOn]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/flow-on.html
+[buffer]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/buffer.html
+[conflate]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/conflate.html
+[collectLatest]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/collect-latest.html
+[zip]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/zip.html
+[combine]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/combine.html
+[onEach]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/on-each.html
+[flatMapConcat]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/flat-map-concat.html
+[flattenConcat]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/flatten-concat.html
+[flatMapMerge]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/flat-map-merge.html
+[flattenMerge]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/flatten-merge.html
+[DEFAULT_CONCURRENCY]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/-d-e-f-a-u-l-t_-c-o-n-c-u-r-r-e-n-c-y.html
+[flatMapLatest]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/flat-map-latest.html
+[catch]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/catch.html
+[onCompletion]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/on-completion.html
+[launchIn]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/launch-in.html
+[IntRange.asFlow]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/as-flow.html
+[cancellable]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/cancellable.html
+
+<!--- END -->
diff --git a/docs/topics/knit.properties b/docs/topics/knit.properties
new file mode 100644
index 00000000..562b4121
--- /dev/null
+++ b/docs/topics/knit.properties
@@ -0,0 +1,9 @@
+#
+# Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+#
+
+knit.package=kotlinx.coroutines.guide
+knit.dir=../../kotlinx-coroutines-core/jvm/test/guide/
+
+test.package=kotlinx.coroutines.guide.test
+test.dir=../../kotlinx-coroutines-core/jvm/test/guide/test/
diff --git a/docs/topics/select-expression.md b/docs/topics/select-expression.md
new file mode 100644
index 00000000..3d20ff39
--- /dev/null
+++ b/docs/topics/select-expression.md
@@ -0,0 +1,507 @@
+<!--- TEST_NAME SelectGuideTest -->
+
+[//]: # (title: Select expression \(experimental\))
+
+Select expression makes it possible to await multiple suspending functions simultaneously and _select_
+the first one that becomes available.
+
+> Select expressions are an experimental feature of `kotlinx.coroutines`. Their API is expected to
+> evolve in the upcoming updates of the `kotlinx.coroutines` library with potentially
+> breaking changes.
+>
+{type="note"}
+
+## Selecting from channels
+
+Let us have two producers of strings: `fizz` and `buzz`. The `fizz` produces "Fizz" string every 300 ms:
+
+```kotlin
+fun CoroutineScope.fizz() = produce<String> {
+ while (true) { // sends "Fizz" every 300 ms
+ delay(300)
+ send("Fizz")
+ }
+}
+```
+
+And the `buzz` produces "Buzz!" string every 500 ms:
+
+```kotlin
+fun CoroutineScope.buzz() = produce<String> {
+ while (true) { // sends "Buzz!" every 500 ms
+ delay(500)
+ send("Buzz!")
+ }
+}
+```
+
+Using [receive][ReceiveChannel.receive] suspending function we can receive _either_ from one channel or the
+other. But [select] expression allows us to receive from _both_ simultaneously using its
+[onReceive][ReceiveChannel.onReceive] clauses:
+
+```kotlin
+suspend fun selectFizzBuzz(fizz: ReceiveChannel<String>, buzz: ReceiveChannel<String>) {
+ select<Unit> { // <Unit> means that this select expression does not produce any result
+ fizz.onReceive { value -> // this is the first select clause
+ println("fizz -> '$value'")
+ }
+ buzz.onReceive { value -> // this is the second select clause
+ println("buzz -> '$value'")
+ }
+ }
+}
+```
+
+Let us run it all seven times:
+
+<!--- CLEAR -->
+
+```kotlin
+import kotlinx.coroutines.*
+import kotlinx.coroutines.channels.*
+import kotlinx.coroutines.selects.*
+
+fun CoroutineScope.fizz() = produce<String> {
+ while (true) { // sends "Fizz" every 300 ms
+ delay(300)
+ send("Fizz")
+ }
+}
+
+fun CoroutineScope.buzz() = produce<String> {
+ while (true) { // sends "Buzz!" every 500 ms
+ delay(500)
+ send("Buzz!")
+ }
+}
+
+suspend fun selectFizzBuzz(fizz: ReceiveChannel<String>, buzz: ReceiveChannel<String>) {
+ select<Unit> { // <Unit> means that this select expression does not produce any result
+ fizz.onReceive { value -> // this is the first select clause
+ println("fizz -> '$value'")
+ }
+ buzz.onReceive { value -> // this is the second select clause
+ println("buzz -> '$value'")
+ }
+ }
+}
+
+fun main() = runBlocking<Unit> {
+//sampleStart
+ val fizz = fizz()
+ val buzz = buzz()
+ repeat(7) {
+ selectFizzBuzz(fizz, buzz)
+ }
+ coroutineContext.cancelChildren() // cancel fizz & buzz coroutines
+//sampleEnd
+}
+```
+{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
+
+> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-select-01.kt).
+>
+{type="note"}
+
+The result of this code is:
+
+```text
+fizz -> 'Fizz'
+buzz -> 'Buzz!'
+fizz -> 'Fizz'
+fizz -> 'Fizz'
+buzz -> 'Buzz!'
+fizz -> 'Fizz'
+buzz -> 'Buzz!'
+```
+
+<!--- TEST -->
+
+## Selecting on close
+
+The [onReceive][ReceiveChannel.onReceive] clause in `select` fails when the channel is closed causing the corresponding
+`select` to throw an exception. We can use [onReceiveCatching][ReceiveChannel.onReceiveCatching] clause to perform a
+specific action when the channel is closed. The following example also shows that `select` is an expression that returns
+the result of its selected clause:
+
+```kotlin
+suspend fun selectAorB(a: ReceiveChannel<String>, b: ReceiveChannel<String>): String =
+ select<String> {
+ a.onReceiveCatching { it ->
+ val value = it.getOrNull()
+ if (value != null) {
+ "a -> '$value'"
+ } else {
+ "Channel 'a' is closed"
+ }
+ }
+ b.onReceiveCatching { it ->
+ val value = it.getOrNull()
+ if (value != null) {
+ "b -> '$value'"
+ } else {
+ "Channel 'b' is closed"
+ }
+ }
+ }
+```
+
+
+Let's use it with channel `a` that produces "Hello" string four times and
+channel `b` that produces "World" four times:
+
+<!--- CLEAR -->
+
+```kotlin
+import kotlinx.coroutines.*
+import kotlinx.coroutines.channels.*
+import kotlinx.coroutines.selects.*
+
+suspend fun selectAorB(a: ReceiveChannel<String>, b: ReceiveChannel<String>): String =
+ select<String> {
+ a.onReceiveCatching { it ->
+ val value = it.getOrNull()
+ if (value != null) {
+ "a -> '$value'"
+ } else {
+ "Channel 'a' is closed"
+ }
+ }
+ b.onReceiveCatching { it ->
+ val value = it.getOrNull()
+ if (value != null) {
+ "b -> '$value'"
+ } else {
+ "Channel 'b' is closed"
+ }
+ }
+ }
+
+fun main() = runBlocking<Unit> {
+//sampleStart
+ val a = produce<String> {
+ repeat(4) { send("Hello $it") }
+ }
+ val b = produce<String> {
+ repeat(4) { send("World $it") }
+ }
+ repeat(8) { // print first eight results
+ println(selectAorB(a, b))
+ }
+ coroutineContext.cancelChildren()
+//sampleEnd
+}
+```
+{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
+
+> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-select-02.kt).
+>
+{type="note"}
+
+The result of this code is quite interesting, so we'll analyze it in more detail:
+
+```text
+a -> 'Hello 0'
+a -> 'Hello 1'
+b -> 'World 0'
+a -> 'Hello 2'
+a -> 'Hello 3'
+b -> 'World 1'
+Channel 'a' is closed
+Channel 'a' is closed
+```
+
+<!--- TEST -->
+
+There are couple of observations to make out of it.
+
+First of all, `select` is _biased_ to the first clause. When several clauses are selectable at the same time,
+the first one among them gets selected. Here, both channels are constantly producing strings, so `a` channel,
+being the first clause in select, wins. However, because we are using unbuffered channel, the `a` gets suspended from
+time to time on its [send][SendChannel.send] invocation and gives a chance for `b` to send, too.
+
+The second observation, is that [onReceiveCatching][ReceiveChannel.onReceiveCatching] gets immediately selected when the
+channel is already closed.
+
+## Selecting to send
+
+Select expression has [onSend][SendChannel.onSend] clause that can be used for a great good in combination
+with a biased nature of selection.
+
+Let us write an example of producer of integers that sends its values to a `side` channel when
+the consumers on its primary channel cannot keep up with it:
+
+```kotlin
+fun CoroutineScope.produceNumbers(side: SendChannel<Int>) = produce<Int> {
+ for (num in 1..10) { // produce 10 numbers from 1 to 10
+ delay(100) // every 100 ms
+ select<Unit> {
+ onSend(num) {} // Send to the primary channel
+ side.onSend(num) {} // or to the side channel
+ }
+ }
+}
+```
+
+Consumer is going to be quite slow, taking 250 ms to process each number:
+
+<!--- CLEAR -->
+
+```kotlin
+import kotlinx.coroutines.*
+import kotlinx.coroutines.channels.*
+import kotlinx.coroutines.selects.*
+
+fun CoroutineScope.produceNumbers(side: SendChannel<Int>) = produce<Int> {
+ for (num in 1..10) { // produce 10 numbers from 1 to 10
+ delay(100) // every 100 ms
+ select<Unit> {
+ onSend(num) {} // Send to the primary channel
+ side.onSend(num) {} // or to the side channel
+ }
+ }
+}
+
+fun main() = runBlocking<Unit> {
+//sampleStart
+ val side = Channel<Int>() // allocate side channel
+ launch { // this is a very fast consumer for the side channel
+ side.consumeEach { println("Side channel has $it") }
+ }
+ produceNumbers(side).consumeEach {
+ println("Consuming $it")
+ delay(250) // let us digest the consumed number properly, do not hurry
+ }
+ println("Done consuming")
+ coroutineContext.cancelChildren()
+//sampleEnd
+}
+```
+{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
+
+> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-select-03.kt).
+>
+{type="note"}
+
+So let us see what happens:
+
+```text
+Consuming 1
+Side channel has 2
+Side channel has 3
+Consuming 4
+Side channel has 5
+Side channel has 6
+Consuming 7
+Side channel has 8
+Side channel has 9
+Consuming 10
+Done consuming
+```
+
+<!--- TEST -->
+
+## Selecting deferred values
+
+Deferred values can be selected using [onAwait][Deferred.onAwait] clause.
+Let us start with an async function that returns a deferred string value after
+a random delay:
+
+```kotlin
+fun CoroutineScope.asyncString(time: Int) = async {
+ delay(time.toLong())
+ "Waited for $time ms"
+}
+```
+
+Let us start a dozen of them with a random delay.
+
+```kotlin
+fun CoroutineScope.asyncStringsList(): List<Deferred<String>> {
+ val random = Random(3)
+ return List(12) { asyncString(random.nextInt(1000)) }
+}
+```
+
+Now the main function awaits for the first of them to complete and counts the number of deferred values
+that are still active. Note that we've used here the fact that `select` expression is a Kotlin DSL,
+so we can provide clauses for it using an arbitrary code. In this case we iterate over a list
+of deferred values to provide `onAwait` clause for each deferred value.
+
+<!--- CLEAR -->
+
+```kotlin
+import kotlinx.coroutines.*
+import kotlinx.coroutines.selects.*
+import java.util.*
+
+fun CoroutineScope.asyncString(time: Int) = async {
+ delay(time.toLong())
+ "Waited for $time ms"
+}
+
+fun CoroutineScope.asyncStringsList(): List<Deferred<String>> {
+ val random = Random(3)
+ return List(12) { asyncString(random.nextInt(1000)) }
+}
+
+fun main() = runBlocking<Unit> {
+//sampleStart
+ val list = asyncStringsList()
+ val result = select<String> {
+ list.withIndex().forEach { (index, deferred) ->
+ deferred.onAwait { answer ->
+ "Deferred $index produced answer '$answer'"
+ }
+ }
+ }
+ println(result)
+ val countActive = list.count { it.isActive }
+ println("$countActive coroutines are still active")
+//sampleEnd
+}
+```
+{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
+
+> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-select-04.kt).
+>
+{type="note"}
+
+The output is:
+
+```text
+Deferred 4 produced answer 'Waited for 128 ms'
+11 coroutines are still active
+```
+
+<!--- TEST -->
+
+## Switch over a channel of deferred values
+
+Let us write a channel producer function that consumes a channel of deferred string values, waits for each received
+deferred value, but only until the next deferred value comes over or the channel is closed. This example puts together
+[onReceiveCatching][ReceiveChannel.onReceiveCatching] and [onAwait][Deferred.onAwait] clauses in the same `select`:
+
+```kotlin
+fun CoroutineScope.switchMapDeferreds(input: ReceiveChannel<Deferred<String>>) = produce<String> {
+ var current = input.receive() // start with first received deferred value
+ while (isActive) { // loop while not cancelled/closed
+ val next = select<Deferred<String>?> { // return next deferred value from this select or null
+ input.onReceiveCatching { update ->
+ update.getOrNull()
+ }
+ current.onAwait { value ->
+ send(value) // send value that current deferred has produced
+ input.receiveCatching().getOrNull() // and use the next deferred from the input channel
+ }
+ }
+ if (next == null) {
+ println("Channel was closed")
+ break // out of loop
+ } else {
+ current = next
+ }
+ }
+}
+```
+
+To test it, we'll use a simple async function that resolves to a specified string after a specified time:
+
+```kotlin
+fun CoroutineScope.asyncString(str: String, time: Long) = async {
+ delay(time)
+ str
+}
+```
+
+The main function just launches a coroutine to print results of `switchMapDeferreds` and sends some test
+data to it:
+
+<!--- CLEAR -->
+
+```kotlin
+import kotlinx.coroutines.*
+import kotlinx.coroutines.channels.*
+import kotlinx.coroutines.selects.*
+
+fun CoroutineScope.switchMapDeferreds(input: ReceiveChannel<Deferred<String>>) = produce<String> {
+ var current = input.receive() // start with first received deferred value
+ while (isActive) { // loop while not cancelled/closed
+ val next = select<Deferred<String>?> { // return next deferred value from this select or null
+ input.onReceiveCatching { update ->
+ update.getOrNull()
+ }
+ current.onAwait { value ->
+ send(value) // send value that current deferred has produced
+ input.receiveCatching().getOrNull() // and use the next deferred from the input channel
+ }
+ }
+ if (next == null) {
+ println("Channel was closed")
+ break // out of loop
+ } else {
+ current = next
+ }
+ }
+}
+
+fun CoroutineScope.asyncString(str: String, time: Long) = async {
+ delay(time)
+ str
+}
+
+fun main() = runBlocking<Unit> {
+//sampleStart
+ val chan = Channel<Deferred<String>>() // the channel for test
+ launch { // launch printing coroutine
+ for (s in switchMapDeferreds(chan))
+ println(s) // print each received string
+ }
+ chan.send(asyncString("BEGIN", 100))
+ delay(200) // enough time for "BEGIN" to be produced
+ chan.send(asyncString("Slow", 500))
+ delay(100) // not enough time to produce slow
+ chan.send(asyncString("Replace", 100))
+ delay(500) // give it time before the last one
+ chan.send(asyncString("END", 500))
+ delay(1000) // give it time to process
+ chan.close() // close the channel ...
+ delay(500) // and wait some time to let it finish
+//sampleEnd
+}
+```
+{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
+
+> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-select-05.kt).
+>
+{type="note"}
+
+The result of this code:
+
+```text
+BEGIN
+Replace
+END
+Channel was closed
+```
+
+<!--- TEST -->
+
+<!--- MODULE kotlinx-coroutines-core -->
+<!--- INDEX kotlinx.coroutines -->
+
+[Deferred.onAwait]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-deferred/on-await.html
+
+<!--- INDEX kotlinx.coroutines.channels -->
+
+[ReceiveChannel.receive]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-receive-channel/receive.html
+[ReceiveChannel.onReceive]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-receive-channel/on-receive.html
+[ReceiveChannel.onReceiveCatching]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-receive-channel/on-receive-catching.html
+[SendChannel.send]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-send-channel/send.html
+[SendChannel.onSend]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-send-channel/on-send.html
+
+<!--- INDEX kotlinx.coroutines.selects -->
+
+[select]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.selects/select.html
+
+<!--- END -->
diff --git a/docs/topics/shared-mutable-state-and-concurrency.md b/docs/topics/shared-mutable-state-and-concurrency.md
new file mode 100644
index 00000000..40b0134a
--- /dev/null
+++ b/docs/topics/shared-mutable-state-and-concurrency.md
@@ -0,0 +1,513 @@
+<!--- TEST_NAME SharedStateGuideTest -->
+
+[//]: # (title: Shared mutable state and concurrency)
+
+Coroutines can be executed parallelly using a multi-threaded dispatcher like the [Dispatchers.Default]. It presents
+all the usual parallelism problems. The main problem being synchronization of access to **shared mutable state**.
+Some solutions to this problem in the land of coroutines are similar to the solutions in the multi-threaded world,
+but others are unique.
+
+## The problem
+
+Let us launch a hundred coroutines all doing the same action thousand times.
+We'll also measure their completion time for further comparisons:
+
+```kotlin
+suspend fun massiveRun(action: suspend () -> Unit) {
+ val n = 100 // number of coroutines to launch
+ val k = 1000 // times an action is repeated by each coroutine
+ val time = measureTimeMillis {
+ coroutineScope { // scope for coroutines
+ repeat(n) {
+ launch {
+ repeat(k) { action() }
+ }
+ }
+ }
+ }
+ println("Completed ${n * k} actions in $time ms")
+}
+```
+
+We start with a very simple action that increments a shared mutable variable using
+multi-threaded [Dispatchers.Default].
+
+<!--- CLEAR -->
+
+```kotlin
+import kotlinx.coroutines.*
+import kotlin.system.*
+
+suspend fun massiveRun(action: suspend () -> Unit) {
+ val n = 100 // number of coroutines to launch
+ val k = 1000 // times an action is repeated by each coroutine
+ val time = measureTimeMillis {
+ coroutineScope { // scope for coroutines
+ repeat(n) {
+ launch {
+ repeat(k) { action() }
+ }
+ }
+ }
+ }
+ println("Completed ${n * k} actions in $time ms")
+}
+
+//sampleStart
+var counter = 0
+
+fun main() = runBlocking {
+ withContext(Dispatchers.Default) {
+ massiveRun {
+ counter++
+ }
+ }
+ println("Counter = $counter")
+}
+//sampleEnd
+```
+{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
+
+> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-sync-01.kt).
+>
+{type="note"}
+
+<!--- TEST LINES_START
+Completed 100000 actions in
+Counter =
+-->
+
+What does it print at the end? It is highly unlikely to ever print "Counter = 100000", because a hundred coroutines
+increment the `counter` concurrently from multiple threads without any synchronization.
+
+## Volatiles are of no help
+
+There is a common misconception that making a variable `volatile` solves concurrency problem. Let us try it:
+
+<!--- CLEAR -->
+
+```kotlin
+import kotlinx.coroutines.*
+import kotlin.system.*
+
+suspend fun massiveRun(action: suspend () -> Unit) {
+ val n = 100 // number of coroutines to launch
+ val k = 1000 // times an action is repeated by each coroutine
+ val time = measureTimeMillis {
+ coroutineScope { // scope for coroutines
+ repeat(n) {
+ launch {
+ repeat(k) { action() }
+ }
+ }
+ }
+ }
+ println("Completed ${n * k} actions in $time ms")
+}
+
+//sampleStart
+@Volatile // in Kotlin `volatile` is an annotation
+var counter = 0
+
+fun main() = runBlocking {
+ withContext(Dispatchers.Default) {
+ massiveRun {
+ counter++
+ }
+ }
+ println("Counter = $counter")
+}
+//sampleEnd
+```
+{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
+
+> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-sync-02.kt).
+>
+{type="note"}
+
+<!--- TEST LINES_START
+Completed 100000 actions in
+Counter =
+-->
+
+This code works slower, but we still don't get "Counter = 100000" at the end, because volatile variables guarantee
+linearizable (this is a technical term for "atomic") reads and writes to the corresponding variable, but
+do not provide atomicity of larger actions (increment in our case).
+
+## Thread-safe data structures
+
+The general solution that works both for threads and for coroutines is to use a thread-safe (aka synchronized,
+linearizable, or atomic) data structure that provides all the necessary synchronization for the corresponding
+operations that needs to be performed on a shared state.
+In the case of a simple counter we can use `AtomicInteger` class which has atomic `incrementAndGet` operations:
+
+<!--- CLEAR -->
+
+```kotlin
+import kotlinx.coroutines.*
+import java.util.concurrent.atomic.*
+import kotlin.system.*
+
+suspend fun massiveRun(action: suspend () -> Unit) {
+ val n = 100 // number of coroutines to launch
+ val k = 1000 // times an action is repeated by each coroutine
+ val time = measureTimeMillis {
+ coroutineScope { // scope for coroutines
+ repeat(n) {
+ launch {
+ repeat(k) { action() }
+ }
+ }
+ }
+ }
+ println("Completed ${n * k} actions in $time ms")
+}
+
+//sampleStart
+val counter = AtomicInteger()
+
+fun main() = runBlocking {
+ withContext(Dispatchers.Default) {
+ massiveRun {
+ counter.incrementAndGet()
+ }
+ }
+ println("Counter = $counter")
+}
+//sampleEnd
+```
+{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
+
+> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-sync-03.kt).
+>
+{type="note"}
+
+<!--- TEST ARBITRARY_TIME
+Completed 100000 actions in xxx ms
+Counter = 100000
+-->
+
+This is the fastest solution for this particular problem. It works for plain counters, collections, queues and other
+standard data structures and basic operations on them. However, it does not easily scale to complex
+state or to complex operations that do not have ready-to-use thread-safe implementations.
+
+## Thread confinement fine-grained
+
+_Thread confinement_ is an approach to the problem of shared mutable state where all access to the particular shared
+state is confined to a single thread. It is typically used in UI applications, where all UI state is confined to
+the single event-dispatch/application thread. It is easy to apply with coroutines by using a
+single-threaded context.
+
+<!--- CLEAR -->
+
+```kotlin
+import kotlinx.coroutines.*
+import kotlin.system.*
+
+suspend fun massiveRun(action: suspend () -> Unit) {
+ val n = 100 // number of coroutines to launch
+ val k = 1000 // times an action is repeated by each coroutine
+ val time = measureTimeMillis {
+ coroutineScope { // scope for coroutines
+ repeat(n) {
+ launch {
+ repeat(k) { action() }
+ }
+ }
+ }
+ }
+ println("Completed ${n * k} actions in $time ms")
+}
+
+//sampleStart
+val counterContext = newSingleThreadContext("CounterContext")
+var counter = 0
+
+fun main() = runBlocking {
+ withContext(Dispatchers.Default) {
+ massiveRun {
+ // confine each increment to a single-threaded context
+ withContext(counterContext) {
+ counter++
+ }
+ }
+ }
+ println("Counter = $counter")
+}
+//sampleEnd
+```
+{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
+
+> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-sync-04.kt).
+>
+{type="note"}
+
+<!--- TEST ARBITRARY_TIME
+Completed 100000 actions in xxx ms
+Counter = 100000
+-->
+
+This code works very slowly, because it does _fine-grained_ thread-confinement. Each individual increment switches
+from multi-threaded [Dispatchers.Default] context to the single-threaded context using
+[withContext(counterContext)][withContext] block.
+
+## Thread confinement coarse-grained
+
+In practice, thread confinement is performed in large chunks, e.g. big pieces of state-updating business logic
+are confined to the single thread. The following example does it like that, running each coroutine in
+the single-threaded context to start with.
+
+<!--- CLEAR -->
+
+```kotlin
+import kotlinx.coroutines.*
+import kotlin.system.*
+
+suspend fun massiveRun(action: suspend () -> Unit) {
+ val n = 100 // number of coroutines to launch
+ val k = 1000 // times an action is repeated by each coroutine
+ val time = measureTimeMillis {
+ coroutineScope { // scope for coroutines
+ repeat(n) {
+ launch {
+ repeat(k) { action() }
+ }
+ }
+ }
+ }
+ println("Completed ${n * k} actions in $time ms")
+}
+
+//sampleStart
+val counterContext = newSingleThreadContext("CounterContext")
+var counter = 0
+
+fun main() = runBlocking {
+ // confine everything to a single-threaded context
+ withContext(counterContext) {
+ massiveRun {
+ counter++
+ }
+ }
+ println("Counter = $counter")
+}
+//sampleEnd
+```
+{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
+
+> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-sync-05.kt).
+>
+{type="note"}
+
+<!--- TEST ARBITRARY_TIME
+Completed 100000 actions in xxx ms
+Counter = 100000
+-->
+
+This now works much faster and produces correct result.
+
+## Mutual exclusion
+
+Mutual exclusion solution to the problem is to protect all modifications of the shared state with a _critical section_
+that is never executed concurrently. In a blocking world you'd typically use `synchronized` or `ReentrantLock` for that.
+Coroutine's alternative is called [Mutex]. It has [lock][Mutex.lock] and [unlock][Mutex.unlock] functions to
+delimit a critical section. The key difference is that `Mutex.lock()` is a suspending function. It does not block a thread.
+
+There is also [withLock] extension function that conveniently represents
+`mutex.lock(); try { ... } finally { mutex.unlock() }` pattern:
+
+<!--- CLEAR -->
+
+```kotlin
+import kotlinx.coroutines.*
+import kotlinx.coroutines.sync.*
+import kotlin.system.*
+
+suspend fun massiveRun(action: suspend () -> Unit) {
+ val n = 100 // number of coroutines to launch
+ val k = 1000 // times an action is repeated by each coroutine
+ val time = measureTimeMillis {
+ coroutineScope { // scope for coroutines
+ repeat(n) {
+ launch {
+ repeat(k) { action() }
+ }
+ }
+ }
+ }
+ println("Completed ${n * k} actions in $time ms")
+}
+
+//sampleStart
+val mutex = Mutex()
+var counter = 0
+
+fun main() = runBlocking {
+ withContext(Dispatchers.Default) {
+ massiveRun {
+ // protect each increment with lock
+ mutex.withLock {
+ counter++
+ }
+ }
+ }
+ println("Counter = $counter")
+}
+//sampleEnd
+```
+{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
+
+> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-sync-06.kt).
+>
+{type="note"}
+
+<!--- TEST ARBITRARY_TIME
+Completed 100000 actions in xxx ms
+Counter = 100000
+-->
+
+The locking in this example is fine-grained, so it pays the price. However, it is a good choice for some situations
+where you absolutely must modify some shared state periodically, but there is no natural thread that this state
+is confined to.
+
+## Actors
+
+An [actor](https://en.wikipedia.org/wiki/Actor_model) is an entity made up of a combination of a coroutine,
+the state that is confined and encapsulated into this coroutine,
+and a channel to communicate with other coroutines. A simple actor can be written as a function,
+but an actor with a complex state is better suited for a class.
+
+There is an [actor] coroutine builder that conveniently combines actor's mailbox channel into its
+scope to receive messages from and combines the send channel into the resulting job object, so that a
+single reference to the actor can be carried around as its handle.
+
+The first step of using an actor is to define a class of messages that an actor is going to process.
+Kotlin's [sealed classes](https://kotlinlang.org/docs/reference/sealed-classes.html) are well suited for that purpose.
+We define `CounterMsg` sealed class with `IncCounter` message to increment a counter and `GetCounter` message
+to get its value. The later needs to send a response. A [CompletableDeferred] communication
+primitive, that represents a single value that will be known (communicated) in the future,
+is used here for that purpose.
+
+```kotlin
+// Message types for counterActor
+sealed class CounterMsg
+object IncCounter : CounterMsg() // one-way message to increment counter
+class GetCounter(val response: CompletableDeferred<Int>) : CounterMsg() // a request with reply
+```
+
+Then we define a function that launches an actor using an [actor] coroutine builder:
+
+```kotlin
+// This function launches a new counter actor
+fun CoroutineScope.counterActor() = actor<CounterMsg> {
+ var counter = 0 // actor state
+ for (msg in channel) { // iterate over incoming messages
+ when (msg) {
+ is IncCounter -> counter++
+ is GetCounter -> msg.response.complete(counter)
+ }
+ }
+}
+```
+
+The main code is straightforward:
+
+<!--- CLEAR -->
+
+```kotlin
+import kotlinx.coroutines.*
+import kotlinx.coroutines.channels.*
+import kotlin.system.*
+
+suspend fun massiveRun(action: suspend () -> Unit) {
+ val n = 100 // number of coroutines to launch
+ val k = 1000 // times an action is repeated by each coroutine
+ val time = measureTimeMillis {
+ coroutineScope { // scope for coroutines
+ repeat(n) {
+ launch {
+ repeat(k) { action() }
+ }
+ }
+ }
+ }
+ println("Completed ${n * k} actions in $time ms")
+}
+
+// Message types for counterActor
+sealed class CounterMsg
+object IncCounter : CounterMsg() // one-way message to increment counter
+class GetCounter(val response: CompletableDeferred<Int>) : CounterMsg() // a request with reply
+
+// This function launches a new counter actor
+fun CoroutineScope.counterActor() = actor<CounterMsg> {
+ var counter = 0 // actor state
+ for (msg in channel) { // iterate over incoming messages
+ when (msg) {
+ is IncCounter -> counter++
+ is GetCounter -> msg.response.complete(counter)
+ }
+ }
+}
+
+//sampleStart
+fun main() = runBlocking<Unit> {
+ val counter = counterActor() // create the actor
+ withContext(Dispatchers.Default) {
+ massiveRun {
+ counter.send(IncCounter)
+ }
+ }
+ // send a message to get a counter value from an actor
+ val response = CompletableDeferred<Int>()
+ counter.send(GetCounter(response))
+ println("Counter = ${response.await()}")
+ counter.close() // shutdown the actor
+}
+//sampleEnd
+```
+{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
+
+> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-sync-07.kt).
+>
+{type="note"}
+
+<!--- TEST ARBITRARY_TIME
+Completed 100000 actions in xxx ms
+Counter = 100000
+-->
+
+It does not matter (for correctness) what context the actor itself is executed in. An actor is
+a coroutine and a coroutine is executed sequentially, so confinement of the state to the specific coroutine
+works as a solution to the problem of shared mutable state. Indeed, actors may modify their own private state,
+but can only affect each other through messages (avoiding the need for any locks).
+
+Actor is more efficient than locking under load, because in this case it always has work to do and it does not
+have to switch to a different context at all.
+
+> Note that an [actor] coroutine builder is a dual of [produce] coroutine builder. An actor is associated
+> with the channel that it receives messages from, while a producer is associated with the channel that it
+> sends elements to.
+>
+{type="note"}
+
+<!--- MODULE kotlinx-coroutines-core -->
+<!--- INDEX kotlinx.coroutines -->
+
+[Dispatchers.Default]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-dispatchers/-default.html
+[withContext]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/with-context.html
+[CompletableDeferred]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-completable-deferred/index.html
+
+<!--- INDEX kotlinx.coroutines.sync -->
+
+[Mutex]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.sync/-mutex/index.html
+[Mutex.lock]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.sync/-mutex/lock.html
+[Mutex.unlock]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.sync/-mutex/unlock.html
+[withLock]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.sync/with-lock.html
+
+<!--- INDEX kotlinx.coroutines.channels -->
+
+[actor]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/actor.html
+[produce]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/produce.html
+
+<!--- END -->
diff --git a/gradle.properties b/gradle.properties
index 1ffa02d1..26e5147c 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -1,27 +1,28 @@
#
-# Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+# Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
#
# Kotlin
-version=1.4.1-SNAPSHOT
+version=1.5.2-SNAPSHOT
group=org.jetbrains.kotlinx
-kotlin_version=1.4.0
+kotlin_version=1.5.30
# Dependencies
junit_version=4.12
-atomicfu_version=0.14.4
-knit_version=0.2.2
-html_version=0.6.8
-lincheck_version=2.7.1
-dokka_version=0.9.16-rdev-2-mpp-hacks
+junit5_version=5.7.0
+atomicfu_version=0.16.3
+knit_version=0.3.0
+html_version=0.7.2
+lincheck_version=2.14
+dokka_version=1.5.0
byte_buddy_version=1.10.9
-reactor_version=3.2.5.RELEASE
-reactive_streams_version=1.0.2
+reactor_version=3.4.1
+reactive_streams_version=1.0.3
rxjava2_version=2.2.8
rxjava3_version=3.0.2
javafx_version=11.0.2
javafx_plugin_version=0.0.8
-binary_compatibility_validator_version=0.2.2
+binary_compatibility_validator_version=0.7.0
blockhound_version=1.0.2.RELEASE
jna_version=5.5.0
@@ -52,12 +53,7 @@ jekyll_version=4.0
# JS IR backend sometimes crashes with out-of-memory
# TODO: Remove once KT-37187 is fixed
-org.gradle.jvmargs=-Xmx2g
+org.gradle.jvmargs=-Xmx4g
-# Workaround for Bintray treating .sha512 files as artifacts
-# https://github.com/gradle/gradle/issues/11412
-systemProp.org.gradle.internal.publish.checksums.insecure=true
-
-# todo:KLUDGE: This is commented out, and the property is set conditionally in build.gradle, because IDEA doesn't work with it.
-#kotlin.mpp.enableGranularSourceSetsMetadata=true
kotlin.mpp.enableCompatibilityMetadataVariant=true
+kotlin.mpp.stability.nowarn=true
diff --git a/gradle/compile-common.gradle b/gradle/compile-common.gradle
index 0dc1b5c0..6c3777b1 100644
--- a/gradle/compile-common.gradle
+++ b/gradle/compile-common.gradle
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
kotlin.sourceSets {
diff --git a/gradle/compile-js-multiplatform.gradle b/gradle/compile-js-multiplatform.gradle
index b52cfc52..d6df7e40 100644
--- a/gradle/compile-js-multiplatform.gradle
+++ b/gradle/compile-js-multiplatform.gradle
@@ -1,23 +1,18 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
apply from: rootProject.file('gradle/node-js.gradle')
kotlin {
js {
- // In 1.3.7x js() has not member `moduleName`
- // In 1.4.x it has and allow to safety set compiler output file name and does not break test integration
- if (it.hasProperty("moduleName")) {
- moduleName = project.name
- }
+ moduleName = project.name
- // In 1.3.7x js() has not member `irTarget`
// In 1.4.x it has in `both` and `legacy` mode and js() is of type `KotlinJsTarget`
// `irTarget` is non-null in `both` mode
// and contains appropriate `irTarget` with type `KotlinJsIrTarget`
// `irTarget` is null in `legacy` mode
- if (it.hasProperty("irTarget") && it.irTarget != null) {
+ if (it.irTarget != null) {
irTarget.nodejs()
irTarget.compilations['main']?.dependencies {
api "org.jetbrains.kotlinx:atomicfu-js:$atomicfu_version"
@@ -51,7 +46,7 @@ compileJsLegacy.configure {
kotlinOptions {
// drop -js suffix from outputFile
def baseName = project.name - "-js"
- outputFile = new File(outputFile.parent, baseName + ".js")
+ outputFile = new File(outputFileProperty.get().parent, baseName + ".js")
}
}
diff --git a/gradle/compile-js.gradle b/gradle/compile-js.gradle
deleted file mode 100644
index 55c81fe5..00000000
--- a/gradle/compile-js.gradle
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
- */
-
-// Platform-specific configuration to compile JS modules
-
-apply plugin: 'org.jetbrains.kotlin.js'
-
-dependencies {
- testImplementation "org.jetbrains.kotlin:kotlin-test-js:$kotlin_version"
-}
-
-kotlin {
- js(LEGACY) {
- moduleName = project.name - "-js"
- }
-
- sourceSets {
- main.kotlin.srcDirs = ['src']
- test.kotlin.srcDirs = ['test']
- main.resources.srcDirs = ['resources']
- test.resources.srcDirs = ['test-resources']
- }
-}
-
-tasks.withType(compileKotlinJs.getClass()) {
- kotlinOptions {
- moduleKind = "umd"
- sourceMap = true
- metaInfo = true
- }
-}
diff --git a/gradle/compile-jvm-multiplatform.gradle b/gradle/compile-jvm-multiplatform.gradle
index e72d3051..5e650427 100644
--- a/gradle/compile-jvm-multiplatform.gradle
+++ b/gradle/compile-jvm-multiplatform.gradle
@@ -1,14 +1,12 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
sourceCompatibility = 1.6
targetCompatibility = 1.6
kotlin {
- targets {
- fromPreset(presets.jvm, 'jvm')
- }
+ jvm {}
sourceSets {
jvmTest.dependencies {
api "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
diff --git a/gradle/compile-jvm.gradle b/gradle/compile-jvm.gradle
deleted file mode 100644
index caa5c45f..00000000
--- a/gradle/compile-jvm.gradle
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
- */
-
-// Platform-specific configuration to compile JVM modules
-
-apply plugin: 'org.jetbrains.kotlin.jvm'
-
-sourceCompatibility = 1.6
-targetCompatibility = 1.6
-
-dependencies {
- testCompile "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
- // Workaround to make addSuppressed work in tests
- testCompile "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
- testCompile "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
- testCompile "org.jetbrains.kotlin:kotlin-test-junit:$kotlin_version"
- testCompile "junit:junit:$junit_version"
-}
-
-compileKotlin {
- kotlinOptions {
- freeCompilerArgs += ['-Xexplicit-api=strict']
- }
-}
-
-tasks.withType(Test) {
- testLogging {
- showStandardStreams = true
- events "passed", "failed"
- }
- def stressTest = project.properties['stressTest']
- if (stressTest != null) systemProperties['stressTest'] = stressTest
-}
diff --git a/gradle/compile-native-multiplatform.gradle b/gradle/compile-native-multiplatform.gradle
index 44874467..0a247ede 100644
--- a/gradle/compile-native-multiplatform.gradle
+++ b/gradle/compile-native-multiplatform.gradle
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
project.ext.nativeMainSets = []
@@ -24,6 +24,11 @@ kotlin {
addTarget(presets.watchosArm32)
addTarget(presets.watchosArm64)
addTarget(presets.watchosX86)
+ addTarget(presets.watchosX64)
+ addTarget(presets.iosSimulatorArm64)
+ addTarget(presets.watchosSimulatorArm64)
+ addTarget(presets.tvosSimulatorArm64)
+ addTarget(presets.macosArm64)
}
sourceSets {
diff --git a/gradle/dokka.gradle b/gradle/dokka.gradle
deleted file mode 100644
index 559ec8b6..00000000
--- a/gradle/dokka.gradle
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
- */
-
-// Configures generation of JavaDoc & Dokka artifacts
-
-def makeLinkMapping(dokka, projectDir) {
- dokka.linkMapping {
- def relPath = rootProject.projectDir.toPath().relativize(projectDir.toPath())
- dir = "$projectDir/src"
- url = "https://github.com/kotlin/kotlinx.coroutines/tree/master/$relPath/src"
- suffix = "#L"
- }
-}
-
-configurations {
- dokkaStubs.extendsFrom compileOnly
- configureKotlinJvmPlatform(dokkaStubs)
-}
-
-apply plugin: 'org.jetbrains.dokka'
-
-tasks.withType(dokka.getClass()) {
- jdkVersion = 8
- includes = ['README.md']
-}
-
-dependencies {
- dokkaStubs project(":stdlib-stubs")
-}
-
-
-dokka {
- kotlinTasks { [] }
- outputFormat = 'kotlin-website'
- dependsOn(project.configurations.dokkaStubs)
-
- noStdlibLink = true
-
- externalDocumentationLink {
- packageListUrl = rootProject.projectDir.toPath().resolve("site/stdlib.package.list").toUri().toURL()
- url = new URL("https://kotlinlang.org/api/latest/jvm/stdlib/")
- }
-
- if (project.name != "kotlinx-coroutines-core") {
- dependsOn(project.configurations.compileClasspath)
- dependsOn(project.sourceSets.main.output)
- doFirst {
- // resolve classpath only during execution
- classpath = project.configurations.dokkaStubs.files + project.configurations.compileClasspath.files + project.sourceSets.main.output.files
- }
- }
-}
-
-if (project.name == "kotlinx-coroutines-core") {
- // Custom configuration for MPP modules
- dependencies {
- dokkaStubs project(":js-stub") // so that JS library reference can resolve properly
- dokkaStubs project(":kotlinx-coroutines-core")
- }
-
- dokka {
- kotlinTasks { [] }
- suppressedModifiers = ['actual']
- makeLinkMapping(it, projectDir)
- makeLinkMapping(it, project.file("js"))
- makeLinkMapping(it, project.file("jvm"))
- makeLinkMapping(it, project.file("native"))
- makeLinkMapping(it, project.file("common"))
- // source roots
- impliedPlatforms = ['JVM', 'JS', 'Native']
- sourceRoot {
- path = rootProject.file("$project.name/common/src")
- }
- sourceRoot {
- path = rootProject.file("$project.name/jvm/src")
- platforms = ['JVM']
- }
- sourceRoot {
- path = rootProject.file("$project.name/js/src")
- platforms = ['JS']
- }
- sourceRoot {
- path = rootProject.file("$project.name/native/src")
- platforms = ['Native']
- }
- doFirst {
- classpath = project.configurations.dokkaStubs.files +
- project.configurations.jvmCompileClasspath.files +
- project.kotlin.targets.jvm.compilations.main.output.allOutputs
- }
- }
-}
diff --git a/gradle/dokka.gradle.kts b/gradle/dokka.gradle.kts
new file mode 100644
index 00000000..659890a3
--- /dev/null
+++ b/gradle/dokka.gradle.kts
@@ -0,0 +1,75 @@
+import org.jetbrains.dokka.gradle.*
+import java.net.*
+
+/*
+ * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+// Configures generation of JavaDoc & Dokka artifacts
+apply<DokkaPlugin>()
+//apply<JavaPlugin>()
+
+fun GradleDokkaSourceSetBuilder.makeLinkMapping(projectDir: File) {
+ sourceLink {
+ val relPath = rootProject.projectDir.toPath().relativize(projectDir.toPath())
+ localDirectory.set(projectDir.resolve("src"))
+ remoteUrl.set(URL("https://github.com/kotlin/kotlinx.coroutines/tree/master/$relPath/src"))
+ remoteLineSuffix.set("#L")
+ }
+}
+
+val knit_version: String by project
+tasks.withType(DokkaTaskPartial::class).configureEach {
+ dependencies {
+ plugins("org.jetbrains.kotlinx:dokka-pathsaver-plugin:$knit_version")
+ }
+}
+
+tasks.withType(DokkaTaskPartial::class).configureEach {
+ suppressInheritedMembers.set(true)
+ dokkaSourceSets.configureEach {
+ jdkVersion.set(11)
+ includes.from("README.md")
+ noStdlibLink.set(true)
+
+ externalDocumentationLink {
+ url.set(URL("https://kotlinlang.org/api/latest/jvm/stdlib/"))
+ packageListUrl.set(rootProject.projectDir.toPath().resolve("site/stdlib.package.list").toUri().toURL())
+ }
+
+ if (project.name != "kotlinx-coroutines-core") {
+ dependsOn(project.configurations["compileClasspath"])
+ doFirst {
+ // resolve classpath only during execution
+ classpath.from(project.configurations["compileClasspath"].files)// + project.sourceSets.main.output.files)
+ }
+ }
+ }
+}
+
+if (project.name == "kotlinx-coroutines-core") {
+ // Custom configuration for MPP modules
+ tasks.withType(DokkaTaskPartial::class).configureEach {
+ dokkaSourceSets {
+ val commonMain by getting {
+ makeLinkMapping(project.file("common"))
+ }
+
+ val nativeMain by getting {
+ makeLinkMapping(project.file("native"))
+ }
+
+ val jsMain by getting {
+ makeLinkMapping(project.file("js"))
+ }
+
+ val jvmMain by getting {
+ makeLinkMapping(project.file("jvm"))
+ }
+
+ configureEach {
+ classpath.from(project.configurations["jvmCompileClasspath"].files)
+ }
+ }
+ }
+}
diff --git a/gradle/node-js.gradle b/gradle/node-js.gradle
index d4bd86ca..42f101c5 100644
--- a/gradle/node-js.gradle
+++ b/gradle/node-js.gradle
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
apply plugin: 'com.moowork.node'
diff --git a/gradle/experimental.gradle b/gradle/opt-in.gradle
index b045a1f6..22f022db 100644
--- a/gradle/experimental.gradle
+++ b/gradle/opt-in.gradle
@@ -1,12 +1,12 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
-// For new mpp
-ext.experimentalAnnotations = [
- "kotlin.Experimental",
+ext.optInAnnotations = [
+ "kotlin.RequiresOptIn",
"kotlin.experimental.ExperimentalTypeInference",
"kotlin.ExperimentalMultiplatform",
+ "kotlinx.coroutines.DelicateCoroutinesApi",
"kotlinx.coroutines.ExperimentalCoroutinesApi",
"kotlinx.coroutines.ObsoleteCoroutinesApi",
"kotlinx.coroutines.InternalCoroutinesApi",
diff --git a/gradle/publish-mpp-root-module-in-platform.gradle b/gradle/publish-mpp-root-module-in-platform.gradle
index 8bc0b502..8036bea0 100644
--- a/gradle/publish-mpp-root-module-in-platform.gradle
+++ b/gradle/publish-mpp-root-module-in-platform.gradle
@@ -1,39 +1,42 @@
/*
- * Copyright 2014-2020 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
-/*
- * Publish the platform JAR and POM so that consumers who depend on this module and can't read Gradle module metadata
- * can still get the platform artifact and transitive dependencies from the POM.
- *
- * See the full rationale here https://youtrack.jetbrains.com/issue/KMM-237#focus=streamItem-27-4115233.0-0
- */
-project.ext.publishPlatformArtifactsInRootModule = { platformPublication ->
- def platformPomBuilder = null
+/** Publish the platform JAR and POM so that consumers who depend on this module and can't read Gradle module
+ metadata can still get the platform artifact and transitive dependencies from the POM: */
+project.ext.publishPlatformArtifactsInRootModule = { MavenPublication platformPublication ->
- platformPublication.pom.withXml { platformPomBuilder = asString() }
+ XmlProvider platformXml = null
- publishing.publications.kotlinMultiplatform {
- platformPublication.artifacts.forEach {
- artifact(it)
- }
+ platformPublication.pom.withXml { platformXml = it }
+ publishing.publications.kotlinMultiplatform {
pom.withXml {
- def pomStringBuilder = asString()
- pomStringBuilder.setLength(0)
- // The platform POM needs its artifact ID replaced with the artifact ID of the root module:
- def platformPomString = platformPomBuilder.toString()
- platformPomString.eachLine { line ->
- if (!line.contains("<!--")) { // Remove the Gradle module metadata marker as it will be added anew
- pomStringBuilder.append(line.replace(platformPublication.artifactId, artifactId))
- pomStringBuilder.append("\n")
- }
- }
+ Node root = asNode()
+ // Remove the original content and add the content from the platform POM:
+ root.children().toList().each { root.remove(it as Node) }
+ platformXml.asNode().children().each { root.append(it as Node) }
+
+ // Adjust the self artifact ID, as it should match the root module's coordinates:
+ ((root.get("artifactId") as NodeList).get(0) as Node).setValue(artifactId)
+
+ // Set packaging to POM to indicate that there's no artifact:
+ root.appendNode("packaging", "pom")
+
+ // Remove the original platform dependencies and add a single dependency on the platform module:
+ Node dependencies = (root.get("dependencies") as NodeList).get(0) as Node
+ dependencies.children().toList().each { dependencies.remove(it as Node) }
+ Node singleDependency = dependencies.appendNode("dependency")
+ singleDependency.appendNode("groupId", platformPublication.groupId)
+ singleDependency.appendNode("artifactId", platformPublication.artifactId)
+ singleDependency.appendNode("version", platformPublication.version)
+ singleDependency.appendNode("scope", "compile")
}
}
- tasks.matching { it.name == "generatePomFileForKotlinMultiplatformPublication"}.configureEach {
+ tasks.matching { it.name == "generatePomFileForKotlinMultiplatformPublication" }.configureEach {
dependsOn(tasks["generatePomFileFor${platformPublication.name.capitalize()}Publication"])
}
-} \ No newline at end of file
+
+}
diff --git a/gradle/publish-npm-js.gradle b/gradle/publish-npm-js.gradle
index 8e1a7bb5..382c6749 100644
--- a/gradle/publish-npm-js.gradle
+++ b/gradle/publish-npm-js.gradle
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
def prop(name, defVal) {
diff --git a/gradle/publish-bintray.gradle b/gradle/publish.gradle
index b36c7976..3a0a4224 100644
--- a/gradle/publish-bintray.gradle
+++ b/gradle/publish.gradle
@@ -1,19 +1,19 @@
-import org.gradle.util.VersionNumber
-
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
-// Configures publishing of Maven artifacts to Bintray
+import org.gradle.util.VersionNumber
+
+// Configures publishing of Maven artifacts to Maven Central
apply plugin: 'maven'
apply plugin: 'maven-publish'
+apply plugin: 'signing'
// ------------- tasks
def isMultiplatform = project.name == "kotlinx-coroutines-core"
def isBom = project.name == "kotlinx-coroutines-bom"
-def isKotlin137x = VersionNumber.parse(kotlin_version) <= VersionNumber.parse("1.3.79")
if (!isBom) {
apply plugin: "com.github.johnrengelman.shadow"
@@ -37,17 +37,7 @@ if (!isMultiplatform && !isBom) {
publishing {
repositories {
- maven {
- def user = 'kotlin'
- def repo = 'kotlinx'
- def name = 'kotlinx.coroutines'
- url = "https://api.bintray.com/maven/$user/$repo/$name/;publish=0"
-
- credentials {
- username = project.hasProperty('bintrayUser') ? project.property('bintrayUser') : System.getenv('BINTRAY_USER')
- password = project.hasProperty('bintrayApiKey') ? project.property('bintrayApiKey') : System.getenv('BINTRAY_API_KEY')
- }
- }
+ PublishingKt.configureMavenPublication(delegate, project)
}
if (!isMultiplatform && !isBom) {
@@ -65,46 +55,29 @@ publishing {
}
publications.all {
- MavenCentralKt.configureMavenCentralMetadata(pom, project)
-
+ PublishingKt.configureMavenCentralMetadata(pom, project)
+ PublishingKt.signPublicationIfKeyPresent(project, it)
// add empty javadocs
if (!isBom && it.name != "kotlinMultiplatform") {
it.artifact(javadocJar)
}
- // Rename MPP artifacts for backward compatibility
def type = it.name
switch (type) {
case 'kotlinMultiplatform':
// With Kotlin 1.4 & HMPP, the root module should have no suffix in the ID, but for compatibility with
// the consumers who can't read Gradle module metadata, we publish the JVM artifacts in it, too
- it.artifactId = isKotlin137x ? "$project.name-native" : project.name
- if (!isKotlin137x) {
- apply from: "$rootDir/gradle/publish-mpp-root-module-in-platform.gradle"
- publishPlatformArtifactsInRootModule(publications["jvm"])
- }
+ it.artifactId = project.name
+ apply from: "$rootDir/gradle/publish-mpp-root-module-in-platform.gradle"
+ publishPlatformArtifactsInRootModule(publications["jvm"])
break
case 'metadata':
- // As the old -common dependencies will fail to resolve with Gradle module metadata, rename the module
- // to '*-metadata' so that the resolution failure are more clear
- it.artifactId = isKotlin137x ? "$project.name-common" : "$project.name-metadata"
- break
case 'jvm':
- it.artifactId = isKotlin137x ? project.name : "$project.name-jvm"
- break
case 'js':
case 'native':
it.artifactId = "$project.name-$type"
break
}
- // Hierarchical project structures are not fully supported in 1.3.7x MPP
- if (isKotlin137x) {
- // disable metadata everywhere, but in native and js modules
- if (type == 'maven' || type == 'metadata' || type == 'jvm') {
- moduleDescriptorGenerator = null
- }
- }
-
}
}
@@ -112,12 +85,5 @@ tasks.matching { it.name == "generatePomFileForKotlinMultiplatformPublication"}.
dependsOn(tasks["generatePomFileForJvmPublication"])
}
-task publishDevelopSnapshot() {
- def branch = System.getenv('currentBranch')
- if (branch == "develop") {
- dependsOn(":publish")
- }
-}
-
// Compatibility with old TeamCity configurations that perform :kotlinx-coroutines-core:bintrayUpload
task bintrayUpload(dependsOn: publish)
diff --git a/gradle/test-mocha-js.gradle b/gradle/test-mocha-js.gradle
index 7de79b99..d011eeaa 100644
--- a/gradle/test-mocha-js.gradle
+++ b/gradle/test-mocha-js.gradle
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
// -- Testing with Mocha under Node
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
index 28861d27..e708b1c0 100644
--- a/gradle/wrapper/gradle-wrapper.jar
+++ b/gradle/wrapper/gradle-wrapper.jar
Binary files differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 23082f9a..d7ae858e 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,9 +1,9 @@
#
-# Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+# Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
#
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-6.8.3-all.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-6.3-all.zip
diff --git a/gradlew b/gradlew
index cccdd3d5..ea79e0af 100755
--- a/gradlew
+++ b/gradlew
@@ -1,5 +1,9 @@
#!/usr/bin/env sh
+#
+# Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+#
+
##############################################################################
##
## Gradle start up script for UN*X
@@ -28,7 +32,7 @@ APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
-DEFAULT_JVM_OPTS=""
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
@@ -66,6 +70,7 @@ esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
@@ -109,10 +114,11 @@ if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
-# For Cygwin, switch paths to Windows format before running java
-if $cygwin ; then
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
@@ -138,19 +144,19 @@ if $cygwin ; then
else
eval `echo args$i`="\"$arg\""
fi
- i=$((i+1))
+ i=`expr $i + 1`
done
case $i in
- (0) set -- ;;
- (1) set -- "$args0" ;;
- (2) set -- "$args0" "$args1" ;;
- (3) set -- "$args0" "$args1" "$args2" ;;
- (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
- (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
- (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
- (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
- (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
- (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ 0) set -- ;;
+ 1) set -- "$args0" ;;
+ 2) set -- "$args0" "$args1" ;;
+ 3) set -- "$args0" "$args1" "$args2" ;;
+ 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
@@ -159,14 +165,9 @@ save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
-APP_ARGS=$(save "$@")
+APP_ARGS=`save "$@"`
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
-# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
-if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
- cd "$(dirname "$0")"
-fi
-
exec "$JAVACMD" "$@"
diff --git a/gradlew.bat b/gradlew.bat
index f9553162..107acd32 100644
--- a/gradlew.bat
+++ b/gradlew.bat
@@ -1,3 +1,19 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@@ -13,15 +29,18 @@ if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
-set DEFAULT_JVM_OPTS=
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
-if "%ERRORLEVEL%" == "0" goto init
+if "%ERRORLEVEL%" == "0" goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
@@ -35,7 +54,7 @@ goto fail
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
-if exist "%JAVA_EXE%" goto init
+if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
@@ -45,28 +64,14 @@ echo location of your Java installation.
goto fail
-:init
-@rem Get command-line arguments, handling Windows variants
-
-if not "%OS%" == "Windows_NT" goto win9xME_args
-
-:win9xME_args
-@rem Slurp the command line arguments.
-set CMD_LINE_ARGS=
-set _SKIP=2
-
-:win9xME_args_slurp
-if "x%~1" == "x" goto execute
-
-set CMD_LINE_ARGS=%*
-
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
@rem Execute Gradle
-"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
diff --git a/integration-testing/build.gradle b/integration-testing/build.gradle
index b1cbc08f..6efa3a14 100644
--- a/integration-testing/build.gradle
+++ b/integration-testing/build.gradle
@@ -1,10 +1,12 @@
-import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType
-
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
-apply from: rootProject.file("gradle/compile-jvm.gradle")
+import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType
+
+plugins {
+ id("kotlin-jvm-conventions")
+}
repositories {
mavenLocal()
@@ -90,10 +92,10 @@ task coreAgentTest(type: Test) {
}
dependencies {
- testCompile "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
- testCompile 'junit:junit:4.12'
- npmTestCompile 'org.apache.commons:commons-compress:1.18'
- npmTestCompile 'com.google.code.gson:gson:2.8.5'
+ testImplementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
+ testImplementation 'junit:junit:4.12'
+ npmTestImplementation 'org.apache.commons:commons-compress:1.18'
+ npmTestImplementation 'com.google.code.gson:gson:2.8.5'
debugAgentTestCompile project(':kotlinx-coroutines-core')
debugAgentTestCompile project(':kotlinx-coroutines-debug')
coreAgentTestCompile project(':kotlinx-coroutines-core')
diff --git a/integration-testing/src/coreAgentTest/kotlin/CoreAgentTest.kt b/integration-testing/src/coreAgentTest/kotlin/CoreAgentTest.kt
index 6d473233..359338b8 100644
--- a/integration-testing/src/coreAgentTest/kotlin/CoreAgentTest.kt
+++ b/integration-testing/src/coreAgentTest/kotlin/CoreAgentTest.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
import org.junit.*
import kotlinx.coroutines.*
diff --git a/integration-testing/src/debugAgentTest/kotlin/DebugAgentTest.kt b/integration-testing/src/debugAgentTest/kotlin/DebugAgentTest.kt
index d6c4aa2f..779cd5b7 100644
--- a/integration-testing/src/debugAgentTest/kotlin/DebugAgentTest.kt
+++ b/integration-testing/src/debugAgentTest/kotlin/DebugAgentTest.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
import org.junit.*
import kotlinx.coroutines.*
diff --git a/integration-testing/src/debugAgentTest/kotlin/DebugProbes.kt b/integration-testing/src/debugAgentTest/kotlin/DebugProbes.kt
index 46a7634a..81741569 100644
--- a/integration-testing/src/debugAgentTest/kotlin/DebugProbes.kt
+++ b/integration-testing/src/debugAgentTest/kotlin/DebugProbes.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
@file:Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
package kotlin.coroutines.jvm.internal
@@ -11,4 +11,4 @@ internal fun <T> probeCoroutineCreated(completion: Continuation<T>): Continuatio
internal fun probeCoroutineResumed(frame: Continuation<*>) = DebugProbesImpl.probeCoroutineResumed(frame)
-internal fun probeCoroutineSuspended(frame: Continuation<*>) = DebugProbesImpl.probeCoroutineSuspended(frame) \ No newline at end of file
+internal fun probeCoroutineSuspended(frame: Continuation<*>) = DebugProbesImpl.probeCoroutineSuspended(frame)
diff --git a/integration-testing/src/debugAgentTest/kotlin/PrecompiledDebugProbesTest.kt b/integration-testing/src/debugAgentTest/kotlin/PrecompiledDebugProbesTest.kt
index 5d799ee0..ce82e577 100644
--- a/integration-testing/src/debugAgentTest/kotlin/PrecompiledDebugProbesTest.kt
+++ b/integration-testing/src/debugAgentTest/kotlin/PrecompiledDebugProbesTest.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
import org.junit.Test
import java.io.*
@@ -36,4 +36,4 @@ class PrecompiledDebugProbesTest {
"ensure that classfile has major version equal to 50 (Java 6 compliance)")
}
}
-} \ No newline at end of file
+}
diff --git a/integration-testing/src/mavenTest/kotlin/MavenPublicationValidator.kt b/integration-testing/src/mavenTest/kotlin/MavenPublicationValidator.kt
index 5089c535..39d6598b 100644
--- a/integration-testing/src/mavenTest/kotlin/MavenPublicationValidator.kt
+++ b/integration-testing/src/mavenTest/kotlin/MavenPublicationValidator.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.validator
diff --git a/integration-testing/src/npmTest/kotlin/NpmPublicationValidator.kt b/integration-testing/src/npmTest/kotlin/NpmPublicationValidator.kt
index 52479d56..8e1b9f99 100644
--- a/integration-testing/src/npmTest/kotlin/NpmPublicationValidator.kt
+++ b/integration-testing/src/npmTest/kotlin/NpmPublicationValidator.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.validator
diff --git a/integration/kotlinx-coroutines-guava/README.md b/integration/kotlinx-coroutines-guava/README.md
index 4c43317a..34b8e581 100644
--- a/integration/kotlinx-coroutines-guava/README.md
+++ b/integration/kotlinx-coroutines-guava/README.md
@@ -50,11 +50,18 @@ Integration with Guava [ListenableFuture](https://github.com/google/guava/wiki/L
<!--- MODULE kotlinx-coroutines-core -->
<!--- INDEX kotlinx.coroutines -->
+
[CoroutineScope]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-scope/index.html
+
<!--- MODULE kotlinx-coroutines-guava -->
<!--- INDEX kotlinx.coroutines.guava -->
-[future]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-guava/kotlinx.coroutines.guava/kotlinx.coroutines.-coroutine-scope/future.html
-[com.google.common.util.concurrent.ListenableFuture]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-guava/kotlinx.coroutines.guava/com.google.common.util.concurrent.-listenable-future/index.html
-[com.google.common.util.concurrent.ListenableFuture.await]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-guava/kotlinx.coroutines.guava/com.google.common.util.concurrent.-listenable-future/await.html
-[kotlinx.coroutines.Deferred.asListenableFuture]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-guava/kotlinx.coroutines.guava/kotlinx.coroutines.-deferred/as-listenable-future.html
+
+[future]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-guava/kotlinx.coroutines.guava/future.html
+[com.google.common.util.concurrent.ListenableFuture.await]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-guava/kotlinx.coroutines.guava/await.html
+[kotlinx.coroutines.Deferred.asListenableFuture]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-guava/kotlinx.coroutines.guava/as-listenable-future.html
+
+<!--- INDEX com.google.common.util.concurrent -->
+
+[com.google.common.util.concurrent.ListenableFuture]: https://kotlin.github.io/kotlinx.coroutines/https://google.github.io/guava/releases/28.0-jre/api/docs/com/google/common/util/concurrent/ListenableFuture.html
+
<!--- END -->
diff --git a/integration/kotlinx-coroutines-guava/build.gradle.kts b/integration/kotlinx-coroutines-guava/build.gradle.kts
index 53e91add..12a6ca70 100644
--- a/integration/kotlinx-coroutines-guava/build.gradle.kts
+++ b/integration/kotlinx-coroutines-guava/build.gradle.kts
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
val guavaVersion = "28.0-jre"
diff --git a/integration/kotlinx-coroutines-guava/src/ListenableFuture.kt b/integration/kotlinx-coroutines-guava/src/ListenableFuture.kt
index 974e2462..8f11e0a9 100644
--- a/integration/kotlinx-coroutines-guava/src/ListenableFuture.kt
+++ b/integration/kotlinx-coroutines-guava/src/ListenableFuture.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.guava
@@ -17,8 +17,11 @@ import kotlin.coroutines.*
* The coroutine is immediately started. Passing [CoroutineStart.LAZY] to [start] throws
* [IllegalArgumentException], because Futures don't have a way to start lazily.
*
- * The created coroutine is cancelled when the resulting future completes successfully, fails, or
- * is cancelled.
+ * When the created coroutine [isCompleted][Job.isCompleted], it will try to
+ * *synchronously* complete the returned Future with the same outcome. This will
+ * succeed, barring a race with external cancellation of returned [ListenableFuture].
+ *
+ * Cancellation is propagated bidirectionally.
*
* `CoroutineContext` is inherited from this [CoroutineScope]. Additional context elements can be
* added/overlaid by passing [context].
@@ -32,8 +35,10 @@ import kotlin.coroutines.*
* See [newCoroutineContext][CoroutineScope.newCoroutineContext] for a description of debugging
* facilities.
*
- * Note that the error and cancellation semantics of [future] are _subtly different_ than
- * [asListenableFuture]'s. See [ListenableFutureCoroutine] for details.
+ * Note that the error and cancellation semantics of [future] are _subtly different_ than [asListenableFuture]'s.
+ * In particular, any exception that happens in the coroutine after returned future is
+ * successfully cancelled will be passed to the [CoroutineExceptionHandler] from the [context].
+ * See [ListenableFutureCoroutine] for details.
*
* @param context added overlaying [CoroutineScope.coroutineContext] to form the new context.
* @param start coroutine start option. The default value is [CoroutineStart.DEFAULT].
@@ -46,14 +51,9 @@ public fun <T> CoroutineScope.future(
): ListenableFuture<T> {
require(!start.isLazy) { "$start start is not supported" }
val newContext = newCoroutineContext(context)
- val future = SettableFuture.create<T>()
- val coroutine = ListenableFutureCoroutine(newContext, future)
- future.addListener(
- coroutine,
- MoreExecutors.directExecutor())
+ val coroutine = ListenableFutureCoroutine<T>(newContext)
coroutine.start(start, coroutine, block)
- // Return hides the SettableFuture. This should prevent casting.
- return object: ListenableFuture<T> by future {}
+ return coroutine.future
}
/**
@@ -70,7 +70,7 @@ public fun <T> CoroutineScope.future(
* When `this` `ListenableFuture` is [successfully cancelled][java.util.concurrent.Future.cancel],
* it will cancel the returned `Deferred`.
*
- * When the returned `Deferred` is [cancelled][Deferred.cancel()], it will try to propagate the
+ * When the returned `Deferred` is [cancelled][Deferred.cancel], it will try to propagate the
* cancellation to `this` `ListenableFuture`. Propagation will succeed, barring a race with the
* `ListenableFuture` completing normally. This is the only case in which the returned `Deferred`
* will complete with a different outcome than `this` `ListenableFuture`.
@@ -136,11 +136,13 @@ public fun <T> ListenableFuture<T>.asDeferred(): Deferred<T> {
override fun onSuccess(result: T?) {
// Here we work with flexible types, so we unchecked cast to trick the type system
@Suppress("UNCHECKED_CAST")
- deferred.complete(result as T)
+ runCatching { deferred.complete(result as T) }
+ .onFailure { handleCoroutineException(EmptyCoroutineContext, it) }
}
override fun onFailure(t: Throwable) {
- deferred.completeExceptionally(t)
+ runCatching { deferred.completeExceptionally(t) }
+ .onFailure { handleCoroutineException(EmptyCoroutineContext, it) }
}
}, MoreExecutors.directExecutor())
@@ -152,7 +154,8 @@ public fun <T> ListenableFuture<T>.asDeferred(): Deferred<T> {
deferred.invokeOnCompletion {
cancel(false)
}
- return deferred
+ // Return hides the CompletableDeferred. This should prevent casting.
+ return object : Deferred<T> by deferred {}
}
/**
@@ -166,7 +169,7 @@ public fun <T> ListenableFuture<T>.asDeferred(): Deferred<T> {
* state - a serious fundamental bug.
*/
private fun ExecutionException.nonNullCause(): Throwable {
- return this.cause!!
+ return this.cause!!
}
/**
@@ -195,13 +198,21 @@ private fun ExecutionException.nonNullCause(): Throwable {
*
* This is inherently a race. See [Future.cancel] for a description of `Future` cancellation
* semantics. See [Job] for a description of coroutine cancellation semantics. See
- * [DeferredListenableFuture.cancel] for greater detail on the overlapped cancellation semantics and
+ * [JobListenableFuture.cancel] for greater detail on the overlapped cancellation semantics and
* corner cases of this method.
*/
public fun <T> Deferred<T>.asListenableFuture(): ListenableFuture<T> {
- val outerFuture = OuterFuture<T>(this)
- outerFuture.afterInit()
- return outerFuture
+ val listenableFuture = JobListenableFuture<T>(this)
+ // This invokeOnCompletion completes the JobListenableFuture with the same result as `this` Deferred.
+ // The JobListenableFuture may have completed earlier if it got cancelled! See JobListenableFuture.cancel().
+ invokeOnCompletion { throwable ->
+ if (throwable == null) {
+ listenableFuture.complete(getCompleted())
+ } else {
+ listenableFuture.completeExceptionallyOrCancel(throwable)
+ }
+ }
+ return listenableFuture
}
/**
@@ -215,7 +226,6 @@ public fun <T> Deferred<T>.asListenableFuture(): ListenableFuture<T> {
* This method is intended to be used with one-shot Futures, so on coroutine cancellation, the Future is cancelled as well.
* If cancelling the given future is undesired, use [Futures.nonCancellationPropagating] or
* [kotlinx.coroutines.NonCancellable].
- *
*/
public suspend fun <T> ListenableFuture<T>.await(): T {
try {
@@ -255,8 +265,7 @@ private class ToContinuation<T>(
continuation.cancel()
} else {
try {
- continuation.resumeWith(
- Result.success(Uninterruptibles.getUninterruptibly(futureToObserve)))
+ continuation.resume(Uninterruptibles.getUninterruptibly(futureToObserve))
} catch (e: ExecutionException) {
// ExecutionException is the only kind of exception that can be thrown from a gotten
// Future. Anything else showing up here indicates a very fundamental bug in a
@@ -271,57 +280,47 @@ private class ToContinuation<T>(
* An [AbstractCoroutine] intended for use directly creating a [ListenableFuture] handle to
* completion.
*
- * The code in the [Runnable] portion of the class is registered as a [ListenableFuture] callback.
- * See [run] for details. Both types are implemented by this object to save an allocation.
+ * If [future] is successfully cancelled, cancellation is propagated to `this` `Coroutine`.
+ * By documented contract, a [Future] has been cancelled if
+ * and only if its `isCancelled()` method returns true.
+ *
+ * Any error that occurs after successfully cancelling a [ListenableFuture] will be passed
+ * to the [CoroutineExceptionHandler] from the context. The contract of [Future] does not permit
+ * it to return an error after it is successfully cancelled.
+ *
+ * By calling [asListenableFuture] on a [Deferred], any error that occurs after successfully
+ * cancelling the [ListenableFuture] representation of the [Deferred] will _not_ be passed to
+ * the [CoroutineExceptionHandler]. Cancelling a [Deferred] places that [Deferred] in the
+ * cancelling/cancelled states defined by [Job], which _can_ show the error. It's assumed that
+ * the [Deferred] pointing to the task will be used to observe any error outcome occurring after
+ * cancellation.
+ *
+ * This may be counterintuitive, but it maintains the error and cancellation contracts of both
+ * the [Deferred] and [ListenableFuture] types, while permitting both kinds of promise to point
+ * to the same running task.
*/
private class ListenableFutureCoroutine<T>(
- context: CoroutineContext,
- private val future: SettableFuture<T>
-) : AbstractCoroutine<T>(context), Runnable {
+ context: CoroutineContext
+) : AbstractCoroutine<T>(context, initParentJob = true, active = true) {
- /**
- * When registered as a [ListenableFuture] listener, cancels the returned [Coroutine] if
- * [future] is successfully cancelled. By documented contract, a [Future] has been cancelled if
- * and only if its `isCancelled()` method returns true.
- *
- * Any error that occurs after successfully cancelling a [ListenableFuture]
- * created by submitting the returned object as a [Runnable] to an `Executor` will be passed
- * to the [CoroutineExceptionHandler] from the context. The contract of [Future] does not permit
- * it to return an error after it is successfully cancelled.
- *
- * By calling [asListenableFuture] on a [Deferred], any error that occurs after successfully
- * cancelling the [ListenableFuture] representation of the [Deferred] will _not_ be passed to
- * the [CoroutineExceptionHandler]. Cancelling a [Deferred] places that [Deferred] in the
- * cancelling/cancelled states defined by [Job], which _can_ show the error. It's assumed that
- * the [Deferred] pointing to the task will be used to observe any error outcome occurring after
- * cancellation.
- *
- * This may be counterintuitive, but it maintains the error and cancellation contracts of both
- * the [Deferred] and [ListenableFuture] types, while permitting both kinds of promise to point
- * to the same running task.
- */
- override fun run() {
- if (future.isCancelled) {
- cancel()
- }
- }
+ // JobListenableFuture propagates external cancellation to `this` coroutine. See JobListenableFuture.
+ @JvmField
+ val future = JobListenableFuture<T>(this)
override fun onCompleted(value: T) {
- future.set(value)
+ future.complete(value)
}
- // TODO: This doesn't actually cancel the Future. There doesn't seem to be bidi cancellation?
override fun onCancelled(cause: Throwable, handled: Boolean) {
- if (!future.setException(cause) && !handled) {
- // prevents loss of exception that was not handled by parent & could not be set to SettableFuture
+ if (!future.completeExceptionallyOrCancel(cause) && !handled) {
+ // prevents loss of exception that was not handled by parent & could not be set to JobListenableFuture
handleCoroutineException(context, cause)
}
}
}
/**
- * A [ListenableFuture] that delegates to an internal [DeferredListenableFuture], collaborating with
- * it.
+ * A [ListenableFuture] that delegates to an internal [SettableFuture], collaborating with it.
*
* This setup allows the returned [ListenableFuture] to maintain the following properties:
*
@@ -333,130 +332,176 @@ private class ListenableFutureCoroutine<T>(
* - Fully correct cancellation and listener happens-after obeying [Future] and
* [ListenableFuture]'s documented and implicit contracts is surprisingly difficult to achieve.
* The best way to be correct, especially given the fun corner cases from
- * [AsyncFuture.setAsync], is to just use an [AsyncFuture].
- * - To maintain sanity, this class implements [ListenableFuture] and uses an inner [AsyncFuture]
- * around its input [deferred] as a state engine to establish happens-after-completion. This
- * could probably be compressed into one subclass of [AsyncFuture] to save an allocation, at the
+ * [AbstractFuture.setFuture], is to just use an [AbstractFuture].
+ * - To maintain sanity, this class implements [ListenableFuture] and uses an auxiliary [SettableFuture]
+ * around coroutine's result as a state engine to establish happens-after-completion. This
+ * could probably be compressed into one subclass of [AbstractFuture] to save an allocation, at the
* cost of the implementation's readability.
*/
-private class OuterFuture<T>(private val deferred: Deferred<T>): ListenableFuture<T> {
- val innerFuture = DeferredListenableFuture(deferred)
+private class JobListenableFuture<T>(private val jobToCancel: Job): ListenableFuture<T> {
+ /**
+ * Serves as a state machine for [Future] cancellation.
+ *
+ * [AbstractFuture] has a highly-correct atomic implementation of `Future`'s completion and
+ * cancellation semantics. By using that type, the [JobListenableFuture] can delegate its semantics to
+ * `auxFuture.get()` the result in such a way that the `Deferred` is always complete when returned.
+ *
+ * To preserve Coroutine's [CancellationException], this future points to either `T` or [Cancelled].
+ */
+ private val auxFuture = SettableFuture.create<Any>()
- // Adding the listener after initialization resolves partial construction hairpin problem.
- //
- // This invokeOnCompletion completes the innerFuture as `deferred` does. The innerFuture may
- // have completed earlier if it got cancelled! See DeferredListenableFuture.
- fun afterInit() {
- deferred.invokeOnCompletion {
- innerFuture.complete()
- }
- }
+ /**
+ * `true` if [auxFuture.get][ListenableFuture.get] throws [ExecutionException].
+ *
+ * Note: this is eventually consistent with the state of [auxFuture].
+ *
+ * Unfortunately, there's no API to figure out if [ListenableFuture] throws [ExecutionException]
+ * apart from calling [ListenableFuture.get] on it. To avoid unnecessary [ExecutionException] allocation
+ * we use this field as an optimization.
+ */
+ private var auxFutureIsFailed: Boolean = false
+
+ /**
+ * When the attached coroutine [isCompleted][Job.isCompleted] successfully
+ * its outcome should be passed to this method.
+ *
+ * This should succeed barring a race with external cancellation.
+ */
+ fun complete(result: T): Boolean = auxFuture.set(result)
+
+ /**
+ * When the attached coroutine [isCompleted][Job.isCompleted] [exceptionally][Job.isCancelled]
+ * its outcome should be passed to this method.
+ *
+ * This method will map coroutine's exception into corresponding Future's exception.
+ *
+ * This should succeed barring a race with external cancellation.
+ */
+ // CancellationException is wrapped into `Cancelled` to preserve original cause and message.
+ // All the other exceptions are delegated to SettableFuture.setException.
+ fun completeExceptionallyOrCancel(t: Throwable): Boolean =
+ if (t is CancellationException) auxFuture.set(Cancelled(t))
+ else auxFuture.setException(t).also { if (it) auxFutureIsFailed = true }
/**
* Returns cancellation _in the sense of [Future]_. This is _not_ equivalent to
* [Job.isCancelled].
*
- * When done, this Future is cancelled if its innerFuture is cancelled, or if its delegate
- * [deferred] is cancelled. Cancellation of [innerFuture] collaborates with this class.
+ * When done, this Future is cancelled if its [auxFuture] is cancelled, or if [auxFuture]
+ * contains [CancellationException].
*
- * See [DeferredListenableFuture.cancel].
+ * See [cancel].
*/
override fun isCancelled(): Boolean {
// This expression ensures that isCancelled() will *never* return true when isDone() returns false.
// In the case that the deferred has completed with cancellation, completing `this`, its
// reaching the "cancelled" state with a cause of CancellationException is treated as the
- // same thing as innerFuture getting cancelled. If the Job is in the "cancelling" state and
+ // same thing as auxFuture getting cancelled. If the Job is in the "cancelling" state and
// this Future hasn't itself been successfully cancelled, the Future will return
// isCancelled() == false. This is the only discovered way to reconcile the two different
// cancellation contracts.
- return isDone
- && (innerFuture.isCancelled
- || deferred.getCompletionExceptionOrNull() is kotlinx.coroutines.CancellationException)
+ return auxFuture.isCancelled || isDone && !auxFutureIsFailed && try {
+ Uninterruptibles.getUninterruptibly(auxFuture) is Cancelled
+ } catch (e: CancellationException) {
+ // `auxFuture` got cancelled right after `auxFuture.isCancelled` returned false.
+ true
+ } catch (e: ExecutionException) {
+ // `auxFutureIsFailed` hasn't been updated yet.
+ auxFutureIsFailed = true
+ false
+ }
}
/**
- * Waits for [innerFuture] to complete by blocking, then uses the [deferred] returned by that
- * Future to get the `T` value `this` [ListenableFuture] is pointing to. This establishes
- * happens-after ordering for completion of the [Deferred] input to [OuterFuture].
+ * Waits for [auxFuture] to complete by blocking, then uses its `result`
+ * to get the `T` value `this` [ListenableFuture] is pointing to or throw a [CancellationException].
+ * This establishes happens-after ordering for completion of the entangled coroutine.
*
- * `innerFuture` _must be complete_ in order for the [isDone] and [isCancelled] happens-after
- * contract of [Future] to be correctly followed. If this method were to directly use
- * _`this.deferred`_ instead of blocking on its `innerFuture`, the [Deferred] that this
- * [ListenableFuture] is created from might be in an incomplete state when used by `get()`.
+ * [SettableFuture.get] can only throw [CancellationException] if it was cancelled externally.
+ * Otherwise it returns [Cancelled] that encapsulates outcome of the entangled coroutine.
+ *
+ * [auxFuture] _must be complete_ in order for the [isDone] and [isCancelled] happens-after
+ * contract of [Future] to be correctly followed.
*/
override fun get(): T {
- return getInternal(innerFuture.get())
+ return getInternal(auxFuture.get())
}
/** See [get()]. */
override fun get(timeout: Long, unit: TimeUnit): T {
- return getInternal(innerFuture.get(timeout, unit))
+ return getInternal(auxFuture.get(timeout, unit))
}
/** See [get()]. */
- private fun getInternal(deferred: Deferred<T>): T {
- if (deferred.isCancelled) {
- val exception = deferred.getCompletionExceptionOrNull()
- if (exception is kotlinx.coroutines.CancellationException) {
- throw exception
- } else {
- throw ExecutionException(exception)
- }
- } else {
- return deferred.getCompleted()
- }
+ private fun getInternal(result: Any): T = if (result is Cancelled) {
+ throw CancellationException().initCause(result.exception)
+ } else {
+ // We know that `auxFuture` can contain either `T` or `Cancelled`.
+ @Suppress("UNCHECKED_CAST")
+ result as T
}
override fun addListener(listener: Runnable, executor: Executor) {
- innerFuture.addListener(listener, executor)
+ auxFuture.addListener(listener, executor)
}
override fun isDone(): Boolean {
- return innerFuture.isDone
- }
-
- override fun cancel(mayInterruptIfRunning: Boolean): Boolean {
- return innerFuture.cancel(mayInterruptIfRunning)
- }
-}
-
-/**
- * Holds a delegate deferred, and serves as a state machine for [Future] cancellation.
- *
- * [AbstractFuture] has a highly-correct atomic implementation of `Future`'s completion and
- * cancellation semantics. By using that type, the [OuterFuture] can delegate its semantics to
- * _this_ `Future` `get()` the result in such a way that the `Deferred` is always complete when
- * returned.
- */
-private class DeferredListenableFuture<T>(
- private val deferred: Deferred<T>
-) : AbstractFuture<Deferred<T>>() {
-
- fun complete() {
- set(deferred)
+ return auxFuture.isDone
}
/**
- * Tries to cancel the task. This is fundamentally racy.
+ * Tries to cancel [jobToCancel] if `this` future was cancelled. This is fundamentally racy.
*
- * For any given call to `cancel()`, if [deferred] is already completed, the call will complete
- * this Future with it, and fail to cancel. Otherwise, the
- * call to `cancel()` will try to cancel this Future: if and only if cancellation of this
- * succeeds, [deferred] will have its [Deferred.cancel] called.
+ * The call to `cancel()` will try to cancel [auxFuture]: if and only if cancellation of [auxFuture]
+ * succeeds, [jobToCancel] will have its [Job.cancel] called.
*
- * This arrangement means that [deferred] _might not successfully cancel_, if the race resolves
- * in a particular way. [deferred] may also be in its "cancelling" state while this
+ * This arrangement means that [jobToCancel] _might not successfully cancel_, if the race resolves
+ * in a particular way. [jobToCancel] may also be in its "cancelling" state while this
* ListenableFuture is complete and cancelled.
- *
- * [OuterFuture] collaborates with this class to present a more cohesive picture and ensure
- * that certain combinations of cancelled/cancelling states can't be observed.
*/
override fun cancel(mayInterruptIfRunning: Boolean): Boolean {
- return if (super.cancel(mayInterruptIfRunning)) {
- deferred.cancel()
+ // TODO: call jobToCancel.cancel() _before_ running the listeners.
+ // `auxFuture.cancel()` will execute auxFuture's listeners. This delays cancellation of
+ // `jobToCancel` until after auxFuture's listeners have already run.
+ // Consider moving `jobToCancel.cancel()` into [AbstractFuture.afterDone] when the API is finalized.
+ return if (auxFuture.cancel(mayInterruptIfRunning)) {
+ jobToCancel.cancel()
true
} else {
false
}
}
+
+ override fun toString(): String = buildString {
+ append(super.toString())
+ append("[status=")
+ if (isDone) {
+ try {
+ when (val result = Uninterruptibles.getUninterruptibly(auxFuture)) {
+ is Cancelled -> append("CANCELLED, cause=[${result.exception}]")
+ else -> append("SUCCESS, result=[$result]")
+ }
+ } catch (e: CancellationException) {
+ // `this` future was cancelled by `Future.cancel`. In this case there's no cause or message.
+ append("CANCELLED")
+ } catch (e: ExecutionException) {
+ append("FAILURE, cause=[${e.cause}]")
+ } catch (t: Throwable) {
+ // Violation of Future's contract, should never happen.
+ append("UNKNOWN, cause=[${t.javaClass} thrown from get()]")
+ }
+ } else {
+ append("PENDING, delegate=[$auxFuture]")
+ }
+ append(']')
+ }
}
+
+/**
+ * A wrapper for `Coroutine`'s [CancellationException].
+ *
+ * If the coroutine is _cancelled normally_, we want to show the reason of cancellation to the user. Unfortunately,
+ * [SettableFuture] can't store the reason of cancellation. To mitigate this, we wrap cancellation exception into this
+ * class and pass it into [SettableFuture.complete]. See implementation of [JobListenableFuture].
+ */
+private class Cancelled(@JvmField val exception: CancellationException)
diff --git a/integration/kotlinx-coroutines-guava/test/FutureAsDeferredUnhandledCompletionExceptionTest.kt b/integration/kotlinx-coroutines-guava/test/FutureAsDeferredUnhandledCompletionExceptionTest.kt
new file mode 100644
index 00000000..d6469a94
--- /dev/null
+++ b/integration/kotlinx-coroutines-guava/test/FutureAsDeferredUnhandledCompletionExceptionTest.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.guava
+
+import com.google.common.util.concurrent.*
+import kotlinx.coroutines.*
+import org.junit.*
+import org.junit.Test
+import kotlin.test.*
+
+class FutureAsDeferredUnhandledCompletionExceptionTest : TestBase() {
+
+ // This is a separate test in order to avoid interference with uncaught exception handlers in other tests
+ private val exceptionHandler = Thread.getDefaultUncaughtExceptionHandler()
+ private lateinit var caughtException: Throwable
+
+ @Before
+ fun setUp() {
+ Thread.setDefaultUncaughtExceptionHandler { _, e -> caughtException = e }
+ }
+
+ @After
+ fun tearDown() {
+ Thread.setDefaultUncaughtExceptionHandler(exceptionHandler)
+ }
+
+ @Test
+ fun testLostExceptionOnSuccess() = runTest {
+ val future = SettableFuture.create<Int>()
+ val deferred = future.asDeferred()
+ deferred.invokeOnCompletion { throw TestException() }
+ future.set(1)
+ assertTrue { caughtException is CompletionHandlerException && caughtException.cause is TestException }
+ }
+
+ @Test
+ fun testLostExceptionOnFailure() = runTest {
+ val future = SettableFuture.create<Int>()
+ val deferred = future.asDeferred()
+ deferred.invokeOnCompletion { throw TestException() }
+ future.setException(TestException2())
+ assertTrue { caughtException is CompletionHandlerException && caughtException.cause is TestException }
+ }
+}
diff --git a/integration/kotlinx-coroutines-guava/test/ListenableFutureTest.kt b/integration/kotlinx-coroutines-guava/test/ListenableFutureTest.kt
index a9a7f7ba..69ba1930 100644
--- a/integration/kotlinx-coroutines-guava/test/ListenableFutureTest.kt
+++ b/integration/kotlinx-coroutines-guava/test/ListenableFutureTest.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.guava
@@ -7,9 +7,11 @@ package kotlinx.coroutines.guava
import com.google.common.util.concurrent.*
import kotlinx.coroutines.*
import org.junit.*
+import org.junit.Ignore
import org.junit.Test
import java.util.concurrent.*
import java.util.concurrent.CancellationException
+import java.util.concurrent.atomic.*
import kotlin.test.*
class ListenableFutureTest : TestBase() {
@@ -316,6 +318,28 @@ class ListenableFutureTest : TestBase() {
}
@Test
+ @Ignore // TODO: propagate cancellation before running listeners.
+ fun testAsListenableFuturePropagatesCancellationBeforeRunningListeners() = runTest {
+ expect(1)
+ val deferred = async(context = Dispatchers.Unconfined) {
+ try {
+ delay(Long.MAX_VALUE)
+ } finally {
+ expect(3) // Cancelled.
+ }
+ }
+ val asFuture = deferred.asListenableFuture()
+ asFuture.addListener(Runnable { expect(4) }, MoreExecutors.directExecutor())
+ assertFalse(asFuture.isDone)
+ expect(2)
+ asFuture.cancel(false)
+ assertTrue(asFuture.isDone)
+ assertTrue(asFuture.isCancelled)
+ assertFailsWith<CancellationException> { deferred.await() }
+ finish(5)
+ }
+
+ @Test
fun testFutureCancellation() = runTest {
val future = awaitFutureWithCancel(true)
assertTrue(future.isCancelled)
@@ -333,15 +357,18 @@ class ListenableFutureTest : TestBase() {
val outputCancellationException =
assertFailsWith<CancellationException> { asFuture.get() }
- assertEquals(outputCancellationException.message, "Foobar")
- assertTrue(outputCancellationException.cause is OutOfMemoryError)
- assertEquals(outputCancellationException.cause?.message, "Foobaz")
+ val cause = outputCancellationException.cause
+ assertNotNull(cause)
+ assertEquals(cause.message, "Foobar")
+ assertTrue(cause.cause is OutOfMemoryError)
+ assertEquals(cause.cause?.message, "Foobaz")
}
@Test
fun testNoFutureCancellation() = runTest {
val future = awaitFutureWithCancel(false)
assertFalse(future.isCancelled)
+ @Suppress("BlockingMethodInNonBlockingContext")
assertEquals(42, future.get())
finish(4)
}
@@ -354,7 +381,7 @@ class ListenableFutureTest : TestBase() {
assertTrue(asDeferredAsFuture.isCancelled)
assertFailsWith<CancellationException> {
- val value: Int = asDeferredAsFuture.await()
+ asDeferredAsFuture.await()
}
}
@@ -379,7 +406,7 @@ class ListenableFutureTest : TestBase() {
assertTrue(asDeferred.isCancelled)
assertFailsWith<CancellationException> {
- val value: Int = asDeferred.await()
+ asDeferred.await()
}
}
@@ -433,7 +460,10 @@ class ListenableFutureTest : TestBase() {
@Test
fun testFutureCompletedWithNullFastPathAsDeferred() = runTest {
val executor = MoreExecutors.listeningDecorator(ForkJoinPool.commonPool())
- val future = executor.submit(Callable<Int> { null }).also { it.get() }
+ val future = executor.submit(Callable<Int> { null }).also {
+ @Suppress("BlockingMethodInNonBlockingContext")
+ it.get()
+ }
assertNull(future.asDeferred().await())
}
@@ -494,8 +524,10 @@ class ListenableFutureTest : TestBase() {
val future = future(Dispatchers.Unconfined) {
try {
delay(Long.MAX_VALUE)
- } finally {
+ expectUnreached()
+ } catch (e: CancellationException) {
expect(2)
+ throw e
}
}
@@ -507,17 +539,19 @@ class ListenableFutureTest : TestBase() {
@Test
fun testExceptionOnExternalCancellation() = runTest(expected = {it is TestException}) {
- expect(1)
val result = future(Dispatchers.Unconfined) {
try {
+ expect(1)
delay(Long.MAX_VALUE)
- } finally {
- expect(2)
+ expectUnreached()
+ } catch (e: CancellationException) {
+ expect(3)
throw TestException()
}
}
+ expect(2)
result.cancel(true)
- finish(3)
+ finish(4)
}
@Test
@@ -540,12 +574,164 @@ class ListenableFutureTest : TestBase() {
finish(3)
}
+ /** This test ensures that we never pass [CancellationException] to [CoroutineExceptionHandler]. */
+ @Test
+ fun testCancellationExceptionOnExternalCancellation() = runTest {
+ expect(1)
+ // No parent here (NonCancellable), so nowhere to propagate exception
+ val result = future(NonCancellable + Dispatchers.Unconfined) {
+ try {
+ delay(Long.MAX_VALUE)
+ } finally {
+ expect(2)
+ throw TestCancellationException() // this exception cannot be handled
+ }
+ }
+ assertTrue(result.cancel(true))
+ finish(3)
+ }
+
+ @Test
+ fun testCancellingFutureContextJobCancelsFuture() = runTest {
+ expect(1)
+ val supervisorJob = SupervisorJob()
+ val future = future(context = supervisorJob) {
+ expect(2)
+ try {
+ delay(Long.MAX_VALUE)
+ expectUnreached()
+ } catch (e: CancellationException) {
+ expect(4)
+ throw e
+ }
+ }
+ yield()
+ expect(3)
+ supervisorJob.cancel(CancellationException("Parent cancelled", TestException()))
+ supervisorJob.join()
+ assertTrue(future.isDone)
+ assertTrue(future.isCancelled)
+ val thrown = assertFailsWith<CancellationException> { future.get() }
+ val cause = thrown.cause
+ assertNotNull(cause)
+ assertTrue(cause is CancellationException)
+ assertEquals("Parent cancelled", cause.message)
+ assertTrue(cause.cause is TestException)
+ finish(5)
+ }
+
+ @Test
+ fun testFutureChildException() = runTest {
+ val future = future(context = NonCancellable + Dispatchers.Unconfined) {
+ val foo = async { delay(Long.MAX_VALUE); 42 }
+ val bar = async<Int> { throw TestException() }
+ foo.await() + bar.await()
+ }
+ future.checkFutureException<TestException>()
+ }
+
+ @Test
+ fun testFutureIsDoneAfterChildrenCompleted() = runTest {
+ expect(1)
+ val testException = TestException()
+ // Don't propagate exception to the test and use different dispatchers as we are going to block test thread.
+ val future = future(context = NonCancellable + Dispatchers.Default) {
+ val foo = async {
+ try {
+ delay(Long.MAX_VALUE)
+ 42
+ } finally {
+ withContext(NonCancellable) {
+ delay(200)
+ }
+ }
+ }
+ foo.invokeOnCompletion {
+ expect(3)
+ }
+ val bar = async<Int> { throw testException }
+ foo.await() + bar.await()
+ }
+ yield()
+ expect(2)
+ // Blocking get should succeed after internal coroutine completes.
+ val thrown = assertFailsWith<ExecutionException> { future.get() }
+ expect(4)
+ assertEquals(testException, thrown.cause)
+ finish(5)
+ }
+
+ @Test
+ @Ignore // TODO: propagate cancellation before running listeners.
+ fun testFuturePropagatesCancellationBeforeRunningListeners() = runTest {
+ expect(1)
+ val future = future(context = Dispatchers.Unconfined) {
+ try {
+ delay(Long.MAX_VALUE)
+ } finally {
+ expect(3) // Cancelled.
+ }
+ }
+ future.addListener(Runnable { expect(4) }, MoreExecutors.directExecutor())
+ assertFalse(future.isDone)
+ expect(2)
+ future.cancel(false)
+ assertTrue(future.isDone)
+ assertTrue(future.isCancelled)
+ finish(5)
+ }
+
+ @Test
+ fun testFutureCompletedExceptionally() = runTest {
+ val testException = TestException()
+ // NonCancellable to not propagate error to this scope.
+ val future = future(context = NonCancellable) {
+ throw testException
+ }
+ yield()
+ assertTrue(future.isDone)
+ assertFalse(future.isCancelled)
+ val thrown = assertFailsWith<ExecutionException> { future.get() }
+ assertEquals(testException, thrown.cause)
+ }
+
+ @Test
+ fun testAsListenableFutureCompletedExceptionally() = runTest {
+ val testException = TestException()
+ val deferred = CompletableDeferred<String>().apply {
+ completeExceptionally(testException)
+ }
+ val asListenableFuture = deferred.asListenableFuture()
+ assertTrue(asListenableFuture.isDone)
+ assertFalse(asListenableFuture.isCancelled)
+ val thrown = assertFailsWith<ExecutionException> { asListenableFuture.get() }
+ assertEquals(testException, thrown.cause)
+ }
+
+ @Test
+ fun stressTestJobListenableFutureIsCancelledDoesNotThrow() = runTest {
+ repeat(1000) {
+ val deferred = CompletableDeferred<String>()
+ val asListenableFuture = deferred.asListenableFuture()
+ // We heed two threads to test a race condition.
+ withContext(Dispatchers.Default) {
+ val cancellationJob = launch {
+ asListenableFuture.cancel(false)
+ }
+ while (!cancellationJob.isCompleted) {
+ asListenableFuture.isCancelled // Shouldn't throw.
+ }
+ }
+ }
+ }
+
private inline fun <reified T: Throwable> ListenableFuture<*>.checkFutureException() {
val e = assertFailsWith<ExecutionException> { get() }
val cause = e.cause!!
assertTrue(cause is T)
}
+ @Suppress("SuspendFunctionOnCoroutineScope")
private suspend fun CoroutineScope.awaitFutureWithCancel(cancellable: Boolean): ListenableFuture<Int> {
val latch = CountDownLatch(1)
val executor = MoreExecutors.listeningDecorator(ForkJoinPool.commonPool())
@@ -562,4 +748,31 @@ class ListenableFutureTest : TestBase() {
latch.countDown()
return future
}
+
+ @Test
+ fun testCancelledParent() = runTest({ it is CancellationException }) {
+ cancel()
+ future { expectUnreached() }
+ future(start = CoroutineStart.ATOMIC) { }
+ future(start = CoroutineStart.UNDISPATCHED) { }
+ }
+
+ @Test
+ fun testStackOverflow() = runTest {
+ val future = SettableFuture.create<Int>()
+ val completed = AtomicLong()
+ val count = 10000L
+ val children = ArrayList<Job>()
+ for (i in 0 until count) {
+ children += launch(Dispatchers.Default) {
+ future.asDeferred().await()
+ completed.incrementAndGet()
+ }
+ }
+ future.set(1)
+ withTimeout(60_000) {
+ children.forEach { it.join() }
+ assertEquals(count, completed.get())
+ }
+ }
}
diff --git a/integration/kotlinx-coroutines-guava/test/ListenableFutureToStringTest.kt b/integration/kotlinx-coroutines-guava/test/ListenableFutureToStringTest.kt
new file mode 100644
index 00000000..13ac2990
--- /dev/null
+++ b/integration/kotlinx-coroutines-guava/test/ListenableFutureToStringTest.kt
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.guava
+
+import kotlinx.coroutines.*
+import org.junit.Test
+import kotlin.test.*
+
+class ListenableFutureToStringTest : TestBase() {
+ @Test
+ fun testSuccessfulFuture() = runTest {
+ val deferred = CompletableDeferred("OK")
+ val succeededFuture = deferred.asListenableFuture()
+ val toString = succeededFuture.toString()
+ assertTrue(message = "Unexpected format: $toString") {
+ toString.matches(Regex("""kotlinx\.coroutines\.guava\.JobListenableFuture@[^\[]*\[status=SUCCESS, result=\[OK]]"""))
+ }
+ }
+
+ @Test
+ fun testFailedFuture() = runTest {
+ val exception = TestRuntimeException("test")
+ val deferred = CompletableDeferred<String>().apply {
+ completeExceptionally(exception)
+ }
+ val failedFuture = deferred.asListenableFuture()
+ val toString = failedFuture.toString()
+ assertTrue(message = "Unexpected format: $toString") {
+ toString.matches(Regex("""kotlinx\.coroutines\.guava\.JobListenableFuture@[^\[]*\[status=FAILURE, cause=\[$exception]]"""))
+ }
+ }
+
+ @Test
+ fun testPendingFuture() = runTest {
+ val deferred = CompletableDeferred<String>()
+ val pendingFuture = deferred.asListenableFuture()
+ val toString = pendingFuture.toString()
+ assertTrue(message = "Unexpected format: $toString") {
+ toString.matches(Regex("""kotlinx\.coroutines\.guava\.JobListenableFuture@[^\[]*\[status=PENDING, delegate=\[.*]]"""))
+ }
+ }
+
+ @Test
+ fun testCancelledCoroutineAsListenableFuture() = runTest {
+ val exception = CancellationException("test")
+ val deferred = CompletableDeferred<String>().apply {
+ cancel(exception)
+ }
+ val cancelledFuture = deferred.asListenableFuture()
+ val toString = cancelledFuture.toString()
+ assertTrue(message = "Unexpected format: $toString") {
+ toString.matches(Regex("""kotlinx\.coroutines\.guava\.JobListenableFuture@[^\[]*\[status=CANCELLED, cause=\[$exception]]"""))
+ }
+ }
+
+ @Test
+ fun testCancelledFuture() = runTest {
+ val deferred = CompletableDeferred<String>()
+ val cancelledFuture = deferred.asListenableFuture().apply {
+ cancel(false)
+ }
+ val toString = cancelledFuture.toString()
+ assertTrue(message = "Unexpected format: $toString") {
+ toString.matches(Regex("""kotlinx\.coroutines\.guava\.JobListenableFuture@[^\[]*\[status=CANCELLED]"""))
+ }
+ }
+}
diff --git a/integration/kotlinx-coroutines-jdk8/README.md b/integration/kotlinx-coroutines-jdk8/README.md
index 3a204416..35808c6f 100644
--- a/integration/kotlinx-coroutines-jdk8/README.md
+++ b/integration/kotlinx-coroutines-jdk8/README.md
@@ -53,12 +53,16 @@ Integration with JDK8 [CompletableFuture] (Android API level 24).
<!--- MODULE kotlinx-coroutines-core -->
<!--- INDEX kotlinx.coroutines -->
+
[CoroutineScope]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-scope/index.html
[Deferred]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-deferred/index.html
+
<!--- MODULE kotlinx-coroutines-jdk8 -->
<!--- INDEX kotlinx.coroutines.future -->
-[future]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-jdk8/kotlinx.coroutines.future/kotlinx.coroutines.-coroutine-scope/future.html
-[java.util.concurrent.CompletionStage.await]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-jdk8/kotlinx.coroutines.future/java.util.concurrent.-completion-stage/await.html
-[java.util.concurrent.CompletionStage.asDeferred]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-jdk8/kotlinx.coroutines.future/java.util.concurrent.-completion-stage/as-deferred.html
-[kotlinx.coroutines.Deferred.asCompletableFuture]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-jdk8/kotlinx.coroutines.future/kotlinx.coroutines.-deferred/as-completable-future.html
+
+[future]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-jdk8/kotlinx.coroutines.future/future.html
+[java.util.concurrent.CompletionStage.await]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-jdk8/kotlinx.coroutines.future/await.html
+[java.util.concurrent.CompletionStage.asDeferred]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-jdk8/kotlinx.coroutines.future/as-deferred.html
+[kotlinx.coroutines.Deferred.asCompletableFuture]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-jdk8/kotlinx.coroutines.future/as-completable-future.html
+
<!--- END -->
diff --git a/integration/kotlinx-coroutines-jdk8/build.gradle.kts b/integration/kotlinx-coroutines-jdk8/build.gradle.kts
index 09915929..791bd950 100644
--- a/integration/kotlinx-coroutines-jdk8/build.gradle.kts
+++ b/integration/kotlinx-coroutines-jdk8/build.gradle.kts
@@ -1,4 +1,4 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
diff --git a/integration/kotlinx-coroutines-jdk8/src/future/Future.kt b/integration/kotlinx-coroutines-jdk8/src/future/Future.kt
index f7fdba5f..caf2a3c3 100644
--- a/integration/kotlinx-coroutines-jdk8/src/future/Future.kt
+++ b/integration/kotlinx-coroutines-jdk8/src/future/Future.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.future
@@ -48,7 +48,7 @@ public fun <T> CoroutineScope.future(
private class CompletableFutureCoroutine<T>(
context: CoroutineContext,
private val future: CompletableFuture<T>
-) : AbstractCoroutine<T>(context), BiConsumer<T?, Throwable?> {
+) : AbstractCoroutine<T>(context, initParentJob = true, active = true), BiConsumer<T?, Throwable?> {
override fun accept(value: T?, exception: Throwable?) {
cancel()
}
@@ -105,16 +105,19 @@ private fun Job.setupCancellation(future: CompletableFuture<*>) {
}
/**
- * Converts this completion stage to an instance of [Deferred].
- * When this completion stage is an instance of [Future], then it is cancelled when
- * the resulting deferred is cancelled.
+ * Converts this [CompletionStage] to an instance of [Deferred].
+ *
+ * The [CompletableFuture] that corresponds to this [CompletionStage] (see [CompletionStage.toCompletableFuture])
+ * is cancelled when the resulting deferred is cancelled.
*/
+@Suppress("DeferredIsResult")
public fun <T> CompletionStage<T>.asDeferred(): Deferred<T> {
+ val future = toCompletableFuture() // retrieve the future
// Fast path if already completed
- if (this is Future<*> && isDone()){
+ if (future.isDone) {
return try {
@Suppress("UNCHECKED_CAST")
- CompletableDeferred(get() as T)
+ CompletableDeferred(future.get() as T)
} catch (e: Throwable) {
// unwrap original cause from ExecutionException
val original = (e as? ExecutionException)?.cause ?: e
@@ -123,34 +126,42 @@ public fun <T> CompletionStage<T>.asDeferred(): Deferred<T> {
}
val result = CompletableDeferred<T>()
whenComplete { value, exception ->
- if (exception == null) {
- // the future has completed normally
- result.complete(value)
- } else {
- // the future has completed with an exception, unwrap it consistently with fast path
- // Note: In the fast-path the implementation of CompletableFuture.get() does unwrapping
- result.completeExceptionally((exception as? CompletionException)?.cause ?: exception)
+ try {
+ if (exception == null) {
+ // the future has completed normally
+ result.complete(value)
+ } else {
+ // the future has completed with an exception, unwrap it consistently with fast path
+ // Note: In the fast-path the implementation of CompletableFuture.get() does unwrapping
+ result.completeExceptionally((exception as? CompletionException)?.cause ?: exception)
+ }
+ } catch (e: Throwable) {
+ // We come here iff the internals of Deferred threw an exception during its completion
+ handleCoroutineException(EmptyCoroutineContext, e)
}
}
- if (this is Future<*>) result.cancelFutureOnCompletion(this)
+ result.cancelFutureOnCompletion(future)
return result
}
/**
- * Awaits for completion of the completion stage without blocking a thread.
+ * Awaits for completion of [CompletionStage] without blocking a thread.
*
* This suspending function is cancellable.
* If the [Job] of the current coroutine is cancelled or completed while this suspending function is waiting, this function
* stops waiting for the completion stage and immediately resumes with [CancellationException][kotlinx.coroutines.CancellationException].
- * This method is intended to be used with one-shot futures, so on coroutine cancellation completion stage is cancelled as well if it is instance of [CompletableFuture].
- * If cancelling given stage is undesired, `stage.asDeferred().await()` should be used instead.
+ *
+ * This method is intended to be used with one-shot futures, so on coroutine cancellation the [CompletableFuture] that
+ * corresponds to this [CompletionStage] (see [CompletionStage.toCompletableFuture])
+ * is cancelled. If cancelling the given stage is undesired, `stage.asDeferred().await()` should be used instead.
*/
public suspend fun <T> CompletionStage<T>.await(): T {
+ val future = toCompletableFuture() // retrieve the future
// fast path when CompletableFuture is already done (does not suspend)
- if (this is Future<*> && isDone()) {
+ if (future.isDone) {
try {
- @Suppress("UNCHECKED_CAST")
- return get() as T
+ @Suppress("UNCHECKED_CAST", "BlockingMethodInNonBlockingContext")
+ return future.get() as T
} catch (e: ExecutionException) {
throw e.cause ?: e // unwrap original cause from ExecutionException
}
@@ -160,8 +171,7 @@ public suspend fun <T> CompletionStage<T>.await(): T {
val consumer = ContinuationConsumer(cont)
whenComplete(consumer)
cont.invokeOnCancellation {
- // mayInterruptIfRunning is not used
- (this as? CompletableFuture<T>)?.cancel(false)
+ future.cancel(false)
consumer.cont = null // shall clear reference to continuation to aid GC
}
}
diff --git a/integration/kotlinx-coroutines-jdk8/src/stream/Stream.kt b/integration/kotlinx-coroutines-jdk8/src/stream/Stream.kt
index 641a83a6..1d804e59 100644
--- a/integration/kotlinx-coroutines-jdk8/src/stream/Stream.kt
+++ b/integration/kotlinx-coroutines-jdk8/src/stream/Stream.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.stream
diff --git a/integration/kotlinx-coroutines-jdk8/src/time/Time.kt b/integration/kotlinx-coroutines-jdk8/src/time/Time.kt
index acff1d21..78cf6e53 100644
--- a/integration/kotlinx-coroutines-jdk8/src/time/Time.kt
+++ b/integration/kotlinx-coroutines-jdk8/src/time/Time.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
@file:OptIn(ExperimentalContracts::class)
diff --git a/integration/kotlinx-coroutines-jdk8/test/future/FutureAsDeferredUnhandledCompletionExceptionTest.kt b/integration/kotlinx-coroutines-jdk8/test/future/FutureAsDeferredUnhandledCompletionExceptionTest.kt
new file mode 100644
index 00000000..bf810af7
--- /dev/null
+++ b/integration/kotlinx-coroutines-jdk8/test/future/FutureAsDeferredUnhandledCompletionExceptionTest.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package future
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.future.*
+import org.junit.*
+import org.junit.Test
+import java.util.concurrent.*
+import kotlin.test.*
+
+class FutureAsDeferredUnhandledCompletionExceptionTest : TestBase() {
+
+ // This is a separate test in order to avoid interference with uncaught exception handlers in other tests
+ private val exceptionHandler = Thread.getDefaultUncaughtExceptionHandler()
+ private lateinit var caughtException: Throwable
+
+ @Before
+ fun setUp() {
+ Thread.setDefaultUncaughtExceptionHandler { _, e -> caughtException = e }
+ }
+
+ @After
+ fun tearDown() {
+ Thread.setDefaultUncaughtExceptionHandler(exceptionHandler)
+ }
+
+ @Test
+ fun testLostException() = runTest {
+ val future = CompletableFuture<Int>()
+ val deferred = future.asDeferred()
+ deferred.invokeOnCompletion { throw TestException() }
+ future.complete(1)
+ assertTrue { caughtException is CompletionHandlerException && caughtException.cause is TestException }
+ }
+}
diff --git a/integration/kotlinx-coroutines-jdk8/test/future/FutureTest.kt b/integration/kotlinx-coroutines-jdk8/test/future/FutureTest.kt
index f75c9674..372e79ef 100644
--- a/integration/kotlinx-coroutines-jdk8/test/future/FutureTest.kt
+++ b/integration/kotlinx-coroutines-jdk8/test/future/FutureTest.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.future
@@ -490,4 +490,108 @@ class FutureTest : TestBase() {
}
}
}
+
+ /**
+ * https://github.com/Kotlin/kotlinx.coroutines/issues/2456
+ */
+ @Test
+ fun testCompletedStageAwait() = runTest {
+ val stage = CompletableFuture.completedStage("OK")
+ assertEquals("OK", stage.await())
+ }
+
+ /**
+ * https://github.com/Kotlin/kotlinx.coroutines/issues/2456
+ */
+ @Test
+ fun testCompletedStageAsDeferredAwait() = runTest {
+ val stage = CompletableFuture.completedStage("OK")
+ val deferred = stage.asDeferred()
+ assertEquals("OK", deferred.await())
+ }
+
+ @Test
+ fun testCompletedStateThenApplyAwait() = runTest {
+ expect(1)
+ val cf = CompletableFuture<String>()
+ launch {
+ expect(3)
+ cf.complete("O")
+ }
+ expect(2)
+ val stage = cf.thenApply { it + "K" }
+ assertEquals("OK", stage.await())
+ finish(4)
+ }
+
+ @Test
+ fun testCompletedStateThenApplyAwaitCancel() = runTest {
+ expect(1)
+ val cf = CompletableFuture<String>()
+ launch {
+ expect(3)
+ cf.cancel(false)
+ }
+ expect(2)
+ val stage = cf.thenApply { it + "K" }
+ assertFailsWith<CancellationException> { stage.await() }
+ finish(4)
+ }
+
+ @Test
+ fun testCompletedStateThenApplyAsDeferredAwait() = runTest {
+ expect(1)
+ val cf = CompletableFuture<String>()
+ launch {
+ expect(3)
+ cf.complete("O")
+ }
+ expect(2)
+ val stage = cf.thenApply { it + "K" }
+ val deferred = stage.asDeferred()
+ assertEquals("OK", deferred.await())
+ finish(4)
+ }
+
+ @Test
+ fun testCompletedStateThenApplyAsDeferredAwaitCancel() = runTest {
+ expect(1)
+ val cf = CompletableFuture<String>()
+ expect(2)
+ val stage = cf.thenApply { it + "K" }
+ val deferred = stage.asDeferred()
+ launch {
+ expect(3)
+ deferred.cancel() // cancel the deferred!
+ }
+ assertFailsWith<CancellationException> { stage.await() }
+ finish(4)
+ }
+
+ @Test
+ fun testCancelledParent() = runTest({ it is java.util.concurrent.CancellationException }) {
+ cancel()
+ future { expectUnreached() }
+ future(start = CoroutineStart.ATOMIC) { }
+ future(start = CoroutineStart.UNDISPATCHED) { }
+ }
+
+ @Test
+ fun testStackOverflow() = runTest {
+ val future = CompletableFuture<Int>()
+ val completed = AtomicLong()
+ val count = 10000L
+ val children = ArrayList<Job>()
+ for (i in 0 until count) {
+ children += launch(Dispatchers.Default) {
+ future.asDeferred().await()
+ completed.incrementAndGet()
+ }
+ }
+ future.complete(1)
+ withTimeout(60_000) {
+ children.forEach { it.join() }
+ assertEquals(count, completed.get())
+ }
+ }
}
diff --git a/integration/kotlinx-coroutines-jdk8/test/time/FlowSampleTest.kt b/integration/kotlinx-coroutines-jdk8/test/time/FlowSampleTest.kt
index 11ceb1a8..d35ee72d 100644
--- a/integration/kotlinx-coroutines-jdk8/test/time/FlowSampleTest.kt
+++ b/integration/kotlinx-coroutines-jdk8/test/time/FlowSampleTest.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.time
@@ -12,8 +12,7 @@ import org.junit.Test
import java.time.Duration
import kotlin.test.assertEquals
-
-class SampleTest : TestBase() {
+class FlowSampleTest : TestBase() {
@Test
public fun testBasic() = withVirtualTime {
expect(1)
diff --git a/integration/kotlinx-coroutines-play-services/README.md b/integration/kotlinx-coroutines-play-services/README.md
index 4ee6bf42..e5e0e613 100644
--- a/integration/kotlinx-coroutines-play-services/README.md
+++ b/integration/kotlinx-coroutines-play-services/README.md
@@ -6,6 +6,7 @@ Extension functions:
| **Name** | **Description**
| -------- | ---------------
+| [Task.asDeferred][asDeferred] | Converts a Task into a Deferred
| [Task.await][await] | Awaits for completion of the Task (cancellable)
| [Deferred.asTask][asTask] | Converts a deferred value to a Task
@@ -25,5 +26,14 @@ val snapshot = try {
// Do stuff
```
+If the `Task` supports cancellation via passing a `CancellationToken`, pass the corresponding `CancellationTokenSource` to `asDeferred` or `await` to support bi-directional cancellation:
+
+```kotlin
+val cancellationTokenSource = CancellationTokenSource()
+val currentLocationTask = fusedLocationProviderClient.getCurrentLocation(PRIORITY_HIGH_ACCURACY, cancellationTokenSource.token)
+val currentLocation = currentLocationTask.await(cancellationTokenSource) // cancelling `await` also cancels `currentLocationTask`, and vice versa
+```
+
+[asDeferred]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-play-services/kotlinx.coroutines.tasks/com.google.android.gms.tasks.-task/as-deferred.html
[await]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-play-services/kotlinx.coroutines.tasks/com.google.android.gms.tasks.-task/await.html
[asTask]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-play-services/kotlinx.coroutines.tasks/kotlinx.coroutines.-deferred/as-task.html
diff --git a/integration/kotlinx-coroutines-play-services/api/kotlinx-coroutines-play-services.api b/integration/kotlinx-coroutines-play-services/api/kotlinx-coroutines-play-services.api
index 9b2c4dd3..cc23e8db 100644
--- a/integration/kotlinx-coroutines-play-services/api/kotlinx-coroutines-play-services.api
+++ b/integration/kotlinx-coroutines-play-services/api/kotlinx-coroutines-play-services.api
@@ -1,6 +1,8 @@
public final class kotlinx/coroutines/tasks/TasksKt {
public static final fun asDeferred (Lcom/google/android/gms/tasks/Task;)Lkotlinx/coroutines/Deferred;
+ public static final fun asDeferred (Lcom/google/android/gms/tasks/Task;Lcom/google/android/gms/tasks/CancellationTokenSource;)Lkotlinx/coroutines/Deferred;
public static final fun asTask (Lkotlinx/coroutines/Deferred;)Lcom/google/android/gms/tasks/Task;
+ public static final fun await (Lcom/google/android/gms/tasks/Task;Lcom/google/android/gms/tasks/CancellationTokenSource;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static final fun await (Lcom/google/android/gms/tasks/Task;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
}
diff --git a/integration/kotlinx-coroutines-play-services/build.gradle b/integration/kotlinx-coroutines-play-services/build.gradle
deleted file mode 100644
index 29ce3d60..00000000
--- a/integration/kotlinx-coroutines-play-services/build.gradle
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
- */
-
-ext.tasks_version = '16.0.1'
-
-def artifactType = Attribute.of("artifactType", String)
-def unpackedAar = Attribute.of("unpackedAar", Boolean)
-
-configurations.all {
- afterEvaluate {
- if (canBeResolved) {
- attributes.attribute(unpackedAar, true) // request all AARs to be unpacked
- }
- }
-}
-
-dependencies {
- attributesSchema {
- attribute(unpackedAar)
- }
-
- artifactTypes {
- aar {
- attributes.attribute(unpackedAar, false)
- }
- }
-
- registerTransform(UnpackAar) {
- from.attribute(unpackedAar, false).attribute(artifactType, "aar")
- to.attribute(unpackedAar, true).attribute(artifactType, "jar")
- }
-
- api("com.google.android.gms:play-services-tasks:$tasks_version") {
- exclude group: 'com.android.support'
- }
-}
-
-tasks.withType(dokka.getClass()) {
- externalDocumentationLink {
- url = new URL("https://developers.google.com/android/reference/")
- // This is workaround for missing package list in Google API
- packageListUrl = projectDir.toPath().resolve("package.list").toUri().toURL()
- }
-}
diff --git a/integration/kotlinx-coroutines-play-services/build.gradle.kts b/integration/kotlinx-coroutines-play-services/build.gradle.kts
new file mode 100644
index 00000000..59f3b0bd
--- /dev/null
+++ b/integration/kotlinx-coroutines-play-services/build.gradle.kts
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+val tasksVersion = "16.0.1"
+
+val artifactType = Attribute.of("artifactType", String::class.java)
+val unpackedAar = Attribute.of("unpackedAar", Boolean::class.javaObjectType)
+
+configurations.configureEach {
+ afterEvaluate {
+ if (isCanBeResolved) {
+ attributes.attribute(unpackedAar, true) // request all AARs to be unpacked
+ }
+ }
+}
+
+dependencies {
+ attributesSchema {
+ attribute(unpackedAar)
+ }
+
+ artifactTypes {
+ create("aar") {
+ attributes.attribute(unpackedAar, false)
+ }
+ }
+
+ registerTransform(UnpackAar::class.java) {
+ from.attribute(unpackedAar, false).attribute(artifactType, "aar")
+ to.attribute(unpackedAar, true).attribute(artifactType, "jar")
+ }
+
+ api("com.google.android.gms:play-services-tasks:$tasksVersion") {
+ exclude(group="com.android.support")
+ }
+}
+
+externalDocumentationLink(
+ url = "https://developers.google.com/android/reference/"
+)
diff --git a/integration/kotlinx-coroutines-play-services/src/Tasks.kt b/integration/kotlinx-coroutines-play-services/src/Tasks.kt
index f9b9a604..c37ac7a0 100644
--- a/integration/kotlinx-coroutines-play-services/src/Tasks.kt
+++ b/integration/kotlinx-coroutines-play-services/src/Tasks.kt
@@ -1,20 +1,13 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
@file:Suppress("RedundantVisibilityModifier")
package kotlinx.coroutines.tasks
-import com.google.android.gms.tasks.CancellationTokenSource
-import com.google.android.gms.tasks.RuntimeExecutionException
-import com.google.android.gms.tasks.Task
-import com.google.android.gms.tasks.TaskCompletionSource
-import kotlinx.coroutines.CancellationException
-import kotlinx.coroutines.CompletableDeferred
-import kotlinx.coroutines.Deferred
-import kotlinx.coroutines.Job
-import kotlinx.coroutines.suspendCancellableCoroutine
+import com.google.android.gms.tasks.*
+import kotlinx.coroutines.*
import kotlin.coroutines.*
/**
@@ -45,39 +38,85 @@ public fun <T> Deferred<T>.asTask(): Task<T> {
/**
* Converts this task to an instance of [Deferred].
* If task is cancelled then resulting deferred will be cancelled as well.
+ * However, the opposite is not true: if the deferred is cancelled, the [Task] will not be cancelled.
+ * For bi-directional cancellation, an overload that accepts [CancellationTokenSource] can be used.
*/
-public fun <T> Task<T>.asDeferred(): Deferred<T> {
+public fun <T> Task<T>.asDeferred(): Deferred<T> = asDeferredImpl(null)
+
+/**
+ * Converts this task to an instance of [Deferred] with a [CancellationTokenSource] to control cancellation.
+ * The cancellation of this function is bi-directional:
+ * * If the given task is cancelled, the resulting deferred will be cancelled.
+ * * If the resulting deferred is cancelled, the provided [cancellationTokenSource] will be cancelled.
+ *
+ * Providing a [CancellationTokenSource] that is unrelated to the receiving [Task] is not supported and
+ * leads to an unspecified behaviour.
+ */
+@ExperimentalCoroutinesApi // Since 1.5.1, tentatively until 1.6.0
+public fun <T> Task<T>.asDeferred(cancellationTokenSource: CancellationTokenSource): Deferred<T> =
+ asDeferredImpl(cancellationTokenSource)
+
+private fun <T> Task<T>.asDeferredImpl(cancellationTokenSource: CancellationTokenSource?): Deferred<T> {
+ val deferred = CompletableDeferred<T>()
if (isComplete) {
val e = exception
- return if (e == null) {
- @Suppress("UNCHECKED_CAST")
- CompletableDeferred<T>().apply { if (isCanceled) cancel() else complete(result as T) }
+ if (e == null) {
+ if (isCanceled) {
+ deferred.cancel()
+ } else {
+ @Suppress("UNCHECKED_CAST")
+ deferred.complete(result as T)
+ }
} else {
- CompletableDeferred<T>().apply { completeExceptionally(e) }
+ deferred.completeExceptionally(e)
+ }
+ } else {
+ addOnCompleteListener {
+ val e = it.exception
+ if (e == null) {
+ @Suppress("UNCHECKED_CAST")
+ if (it.isCanceled) deferred.cancel() else deferred.complete(it.result as T)
+ } else {
+ deferred.completeExceptionally(e)
+ }
}
}
- val result = CompletableDeferred<T>()
- addOnCompleteListener {
- val e = it.exception
- if (e == null) {
- @Suppress("UNCHECKED_CAST")
- if (isCanceled) result.cancel() else result.complete(it.result as T)
- } else {
- result.completeExceptionally(e)
+ if (cancellationTokenSource != null) {
+ deferred.invokeOnCompletion {
+ cancellationTokenSource.cancel()
}
}
- return result
+ // Prevent casting to CompletableDeferred and manual completion.
+ return object : Deferred<T> by deferred {}
}
/**
- * Awaits for completion of the task without blocking a thread.
+ * Awaits the completion of the task without blocking a thread.
*
* This suspending function is cancellable.
* If the [Job] of the current coroutine is cancelled or completed while this suspending function is waiting, this function
* stops waiting for the completion stage and immediately resumes with [CancellationException].
+ *
+ * For bi-directional cancellation, an overload that accepts [CancellationTokenSource] can be used.
+ */
+public suspend fun <T> Task<T>.await(): T = awaitImpl(null)
+
+/**
+ * Awaits the completion of the task that is linked to the given [CancellationTokenSource] to control cancellation.
+ *
+ * This suspending function is cancellable and cancellation is bi-directional:
+ * * If the [Job] of the current coroutine is cancelled or completed while this suspending function is waiting, this function
+ * cancels the [cancellationTokenSource] and throws a [CancellationException].
+ * * If the task is cancelled, then this function will throw a [CancellationException].
+ *
+ * Providing a [CancellationTokenSource] that is unrelated to the receiving [Task] is not supported and
+ * leads to an unspecified behaviour.
*/
-public suspend fun <T> Task<T>.await(): T {
+@ExperimentalCoroutinesApi // Since 1.5.1, tentatively until 1.6.0
+public suspend fun <T> Task<T>.await(cancellationTokenSource: CancellationTokenSource): T = awaitImpl(cancellationTokenSource)
+
+private suspend fun <T> Task<T>.awaitImpl(cancellationTokenSource: CancellationTokenSource?): T {
// fast path
if (isComplete) {
val e = exception
@@ -95,13 +134,19 @@ public suspend fun <T> Task<T>.await(): T {
return suspendCancellableCoroutine { cont ->
addOnCompleteListener {
- val e = exception
+ val e = it.exception
if (e == null) {
@Suppress("UNCHECKED_CAST")
- if (isCanceled) cont.cancel() else cont.resume(result as T)
+ if (it.isCanceled) cont.cancel() else cont.resume(it.result as T)
} else {
cont.resumeWithException(e)
}
}
+
+ if (cancellationTokenSource != null) {
+ cont.invokeOnCancellation {
+ cancellationTokenSource.cancel()
+ }
+ }
}
}
diff --git a/integration/kotlinx-coroutines-play-services/test/TaskTest.kt b/integration/kotlinx-coroutines-play-services/test/TaskTest.kt
index 0f125ac9..b125192e 100644
--- a/integration/kotlinx-coroutines-play-services/test/TaskTest.kt
+++ b/integration/kotlinx-coroutines-play-services/test/TaskTest.kt
@@ -149,5 +149,270 @@ class TaskTest : TestBase() {
}
}
+ @Test
+ fun testCancellableTaskAsDeferred() = runTest {
+ val cancellationTokenSource = CancellationTokenSource()
+ val deferred = Tasks.forResult(42).asDeferred(cancellationTokenSource)
+ assertEquals(42, deferred.await())
+ assertTrue(cancellationTokenSource.token.isCancellationRequested)
+ }
+
+ @Test
+ fun testNullResultCancellableTaskAsDeferred() = runTest {
+ val cancellationTokenSource = CancellationTokenSource()
+ assertNull(Tasks.forResult(null).asDeferred(cancellationTokenSource).await())
+ assertTrue(cancellationTokenSource.token.isCancellationRequested)
+ }
+
+ @Test
+ fun testCancelledCancellableTaskAsDeferred() = runTest {
+ val cancellationTokenSource = CancellationTokenSource()
+ val deferred = Tasks.forCanceled<Int>().asDeferred(cancellationTokenSource)
+
+ assertTrue(deferred.isCancelled)
+ try {
+ deferred.await()
+ fail("deferred.await() should be cancelled")
+ } catch (e: Exception) {
+ assertTrue(e is CancellationException)
+ }
+ assertTrue(cancellationTokenSource.token.isCancellationRequested)
+ }
+
+ @Test
+ fun testCancellingCancellableTaskAsDeferred() = runTest {
+ val cancellationTokenSource = CancellationTokenSource()
+ val task = TaskCompletionSource<Int>(cancellationTokenSource.token).task
+ val deferred = task.asDeferred(cancellationTokenSource)
+
+ deferred.cancel()
+ try {
+ deferred.await()
+ fail("deferred.await() should be cancelled")
+ } catch (e: Exception) {
+ assertTrue(e is CancellationException)
+ }
+ assertTrue(cancellationTokenSource.token.isCancellationRequested)
+ }
+
+ @Test
+ fun testExternallyCancelledCancellableTaskAsDeferred() = runTest {
+ val cancellationTokenSource = CancellationTokenSource()
+ val task = TaskCompletionSource<Int>(cancellationTokenSource.token).task
+ val deferred = task.asDeferred(cancellationTokenSource)
+
+ cancellationTokenSource.cancel()
+
+ try {
+ deferred.await()
+ fail("deferred.await() should be cancelled")
+ } catch (e: Exception) {
+ assertTrue(e is CancellationException)
+ }
+ assertTrue(cancellationTokenSource.token.isCancellationRequested)
+ }
+
+ @Test
+ fun testSeparatelyCancelledCancellableTaskAsDeferred() = runTest {
+ val cancellationTokenSource = CancellationTokenSource()
+ val task = TaskCompletionSource<Int>().task
+ task.asDeferred(cancellationTokenSource)
+
+ cancellationTokenSource.cancel()
+
+ assertTrue(cancellationTokenSource.token.isCancellationRequested)
+ }
+
+ @Test
+ fun testFailedCancellableTaskAsDeferred() = runTest {
+ val cancellationTokenSource = CancellationTokenSource()
+ val deferred = Tasks.forException<Int>(TestException("something went wrong")).asDeferred(cancellationTokenSource)
+
+ assertTrue(deferred.isCancelled && deferred.isCompleted)
+ val completionException = deferred.getCompletionExceptionOrNull()!!
+ assertTrue(completionException is TestException)
+ assertEquals("something went wrong", completionException.message)
+
+ try {
+ deferred.await()
+ fail("deferred.await() should throw an exception")
+ } catch (e: Exception) {
+ assertTrue(e is TestException)
+ assertEquals("something went wrong", e.message)
+ }
+ assertTrue(cancellationTokenSource.token.isCancellationRequested)
+ }
+
+ @Test
+ fun testFailingCancellableTaskAsDeferred() = runTest {
+ val cancellationTokenSource = CancellationTokenSource()
+ val lock = ReentrantLock().apply { lock() }
+
+ val deferred: Deferred<Int> = Tasks.call {
+ lock.withLock { throw TestException("something went wrong") }
+ }.asDeferred(cancellationTokenSource)
+
+ assertFalse(deferred.isCompleted)
+ lock.unlock()
+
+ try {
+ deferred.await()
+ fail("deferred.await() should throw an exception")
+ } catch (e: Exception) {
+ assertTrue(e is TestException)
+ assertEquals("something went wrong", e.message)
+ assertSame(e.cause, deferred.getCompletionExceptionOrNull()) // debug mode stack augmentation
+ }
+ assertTrue(cancellationTokenSource.token.isCancellationRequested)
+ }
+
+ @Test
+ fun testFastPathCompletedTaskWithCancelledTokenSourceAsDeferred() = runTest {
+ val cancellationTokenSource = CancellationTokenSource()
+ val deferred = Tasks.forResult(42).asDeferred(cancellationTokenSource)
+ cancellationTokenSource.cancel()
+ assertEquals(42, deferred.await())
+ }
+
+ @Test
+ fun testAwaitCancellableTask() = runTest {
+ val cancellationTokenSource = CancellationTokenSource()
+ val taskCompletionSource = TaskCompletionSource<Int>(cancellationTokenSource.token)
+
+ val deferred: Deferred<Int> = async(start = CoroutineStart.UNDISPATCHED) {
+ taskCompletionSource.task.await(cancellationTokenSource)
+ }
+
+ assertFalse(deferred.isCompleted)
+ taskCompletionSource.setResult(42)
+
+ assertEquals(42, deferred.await())
+ assertTrue(deferred.isCompleted)
+ }
+
+ @Test
+ fun testFailedAwaitTask() = runTest(expected = { it is TestException }) {
+ val cancellationTokenSource = CancellationTokenSource()
+ val taskCompletionSource = TaskCompletionSource<Int>(cancellationTokenSource.token)
+
+ val deferred: Deferred<Int> = async(start = CoroutineStart.UNDISPATCHED) {
+ taskCompletionSource.task.await(cancellationTokenSource)
+ }
+
+ assertFalse(deferred.isCompleted)
+ taskCompletionSource.setException(TestException("something went wrong"))
+
+ deferred.await()
+ }
+
+ @Test
+ fun testCancelledAwaitCancellableTask() = runTest {
+ val cancellationTokenSource = CancellationTokenSource()
+ val taskCompletionSource = TaskCompletionSource<Int>(cancellationTokenSource.token)
+
+ val deferred: Deferred<Int> = async(start = CoroutineStart.UNDISPATCHED) {
+ taskCompletionSource.task.await(cancellationTokenSource)
+ }
+
+ assertFalse(deferred.isCompleted)
+ // Cancel the deferred
+ deferred.cancel()
+
+ try {
+ deferred.await()
+ fail("deferred.await() should be cancelled")
+ } catch (e: Exception) {
+ assertTrue(e is CancellationException)
+ }
+
+ assertTrue(cancellationTokenSource.token.isCancellationRequested)
+ }
+
+ @Test
+ fun testExternallyCancelledAwaitCancellableTask() = runTest {
+ val cancellationTokenSource = CancellationTokenSource()
+ val taskCompletionSource = TaskCompletionSource<Int>(cancellationTokenSource.token)
+
+ val deferred: Deferred<Int> = async(start = CoroutineStart.UNDISPATCHED) {
+ taskCompletionSource.task.await(cancellationTokenSource)
+ }
+
+ assertFalse(deferred.isCompleted)
+ // Cancel the cancellation token source
+ cancellationTokenSource.cancel()
+
+ try {
+ deferred.await()
+ fail("deferred.await() should be cancelled")
+ } catch (e: Exception) {
+ assertTrue(e is CancellationException)
+ }
+
+ assertTrue(cancellationTokenSource.token.isCancellationRequested)
+ }
+
+ @Test
+ fun testFastPathCancellationTokenSourceCancelledAwaitCancellableTask() = runTest {
+ val cancellationTokenSource = CancellationTokenSource()
+ // Construct a task without the cancellation token source
+ val taskCompletionSource = TaskCompletionSource<Int>()
+
+ val deferred: Deferred<Int> = async(start = CoroutineStart.LAZY) {
+ taskCompletionSource.task.await(cancellationTokenSource)
+ }
+
+ assertFalse(deferred.isCompleted)
+ cancellationTokenSource.cancel()
+
+ // Cancelling the token doesn't cancel the deferred
+ assertTrue(cancellationTokenSource.token.isCancellationRequested)
+ assertFalse(deferred.isCompleted)
+
+ // Cleanup
+ deferred.cancel()
+ }
+
+ @Test
+ fun testSlowPathCancellationTokenSourceCancelledAwaitCancellableTask() = runTest {
+ val cancellationTokenSource = CancellationTokenSource()
+ // Construct a task without the cancellation token source
+ val taskCompletionSource = TaskCompletionSource<Int>()
+
+ val deferred: Deferred<Int> = async(start = CoroutineStart.UNDISPATCHED) {
+ taskCompletionSource.task.await(cancellationTokenSource)
+ }
+
+ assertFalse(deferred.isCompleted)
+ cancellationTokenSource.cancel()
+
+ // Cancelling the token doesn't cancel the deferred
+ assertTrue(cancellationTokenSource.token.isCancellationRequested)
+ assertFalse(deferred.isCompleted)
+
+ // Cleanup
+ deferred.cancel()
+ }
+
+ @Test
+ fun testFastPathWithCompletedTaskAndCanceledTokenSourceAwaitTask() = runTest {
+ val firstCancellationTokenSource = CancellationTokenSource()
+ val secondCancellationTokenSource = CancellationTokenSource()
+ // Construct a task with a different cancellation token source
+ val taskCompletionSource = TaskCompletionSource<Int>(firstCancellationTokenSource.token)
+
+ val deferred: Deferred<Int> = async(start = CoroutineStart.LAZY) {
+ taskCompletionSource.task.await(secondCancellationTokenSource)
+ }
+
+ assertFalse(deferred.isCompleted)
+ secondCancellationTokenSource.cancel()
+
+ assertFalse(deferred.isCompleted)
+ taskCompletionSource.setResult(42)
+
+ assertEquals(42, deferred.await())
+ assertTrue(deferred.isCompleted)
+ }
+
class TestException(message: String) : Exception(message)
}
diff --git a/integration/kotlinx-coroutines-slf4j/README.md b/integration/kotlinx-coroutines-slf4j/README.md
index ee5fb320..e23d3907 100644
--- a/integration/kotlinx-coroutines-slf4j/README.md
+++ b/integration/kotlinx-coroutines-slf4j/README.md
@@ -20,5 +20,7 @@ Integration with SLF4J [MDC](https://logback.qos.ch/manual/mdc.html).
<!--- MODULE kotlinx-coroutines-slf4j -->
<!--- INDEX kotlinx.coroutines.slf4j -->
+
[MDCContext]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-slf4j/kotlinx.coroutines.slf4j/-m-d-c-context/index.html
+
<!--- END -->
diff --git a/integration/kotlinx-coroutines-slf4j/api/kotlinx-coroutines-slf4j.api b/integration/kotlinx-coroutines-slf4j/api/kotlinx-coroutines-slf4j.api
index a8bf271b..6b565d4c 100644
--- a/integration/kotlinx-coroutines-slf4j/api/kotlinx-coroutines-slf4j.api
+++ b/integration/kotlinx-coroutines-slf4j/api/kotlinx-coroutines-slf4j.api
@@ -3,11 +3,7 @@ public final class kotlinx/coroutines/slf4j/MDCContext : kotlin/coroutines/Abstr
public fun <init> ()V
public fun <init> (Ljava/util/Map;)V
public synthetic fun <init> (Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
- public fun fold (Ljava/lang/Object;Lkotlin/jvm/functions/Function2;)Ljava/lang/Object;
- public fun get (Lkotlin/coroutines/CoroutineContext$Key;)Lkotlin/coroutines/CoroutineContext$Element;
public final fun getContextMap ()Ljava/util/Map;
- public fun minusKey (Lkotlin/coroutines/CoroutineContext$Key;)Lkotlin/coroutines/CoroutineContext;
- public fun plus (Lkotlin/coroutines/CoroutineContext;)Lkotlin/coroutines/CoroutineContext;
public synthetic fun restoreThreadContext (Lkotlin/coroutines/CoroutineContext;Ljava/lang/Object;)V
public fun restoreThreadContext (Lkotlin/coroutines/CoroutineContext;Ljava/util/Map;)V
public synthetic fun updateThreadContext (Lkotlin/coroutines/CoroutineContext;)Ljava/lang/Object;
diff --git a/integration/kotlinx-coroutines-slf4j/build.gradle.kts b/integration/kotlinx-coroutines-slf4j/build.gradle.kts
index c7d0d82d..a341eefe 100644
--- a/integration/kotlinx-coroutines-slf4j/build.gradle.kts
+++ b/integration/kotlinx-coroutines-slf4j/build.gradle.kts
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
dependencies {
diff --git a/integration/kotlinx-coroutines-slf4j/src/MDCContext.kt b/integration/kotlinx-coroutines-slf4j/src/MDCContext.kt
index 078800d0..9528f2b2 100644
--- a/integration/kotlinx-coroutines-slf4j/src/MDCContext.kt
+++ b/integration/kotlinx-coroutines-slf4j/src/MDCContext.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.slf4j
diff --git a/js/example-frontend-js/build.gradle b/js/example-frontend-js/build.gradle
deleted file mode 100644
index 7abde639..00000000
--- a/js/example-frontend-js/build.gradle
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
- */
-
-project.kotlin {
- js(LEGACY) {
- binaries.executable()
- browser {
- distribution {
- directory = new File(directory.parentFile, "dist")
- }
- webpackTask {
- cssSupport.enabled = true
- }
- runTask {
- cssSupport.enabled = true
- }
- testTask {
- useKarma {
- useChromeHeadless()
- webpackConfig.cssSupport.enabled = true
- }
- }
- }
- }
-
- sourceSets {
- main.dependencies {
- implementation "org.jetbrains.kotlinx:kotlinx-html-js:$html_version"
- implementation(npm("html-webpack-plugin", "3.2.0"))
- }
- }
-}
diff --git a/js/example-frontend-js/build.gradle.kts b/js/example-frontend-js/build.gradle.kts
new file mode 100644
index 00000000..a78ac3dc
--- /dev/null
+++ b/js/example-frontend-js/build.gradle.kts
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+kotlin {
+ js(LEGACY) {
+ binaries.executable()
+ browser {
+ distribution {
+ directory = directory.parentFile.resolve("dist")
+ }
+ commonWebpackConfig {
+ cssSupport.enabled = true
+ }
+ testTask {
+ useKarma {
+ useChromeHeadless()
+ }
+ }
+ }
+ }
+}
+
+// For kotlinx-html
+repositories {
+ maven("https://maven.pkg.jetbrains.space/public/p/kotlinx-html/maven")
+}
+
+dependencies {
+ implementation("org.jetbrains.kotlinx:kotlinx-html-js:${version("html")}")
+ implementation(devNpm("html-webpack-plugin", "5.3.1"))
+}
diff --git a/js/example-frontend-js/src/ExampleMain.kt b/js/example-frontend-js/src/ExampleMain.kt
index da6e4196..d4e530b0 100644
--- a/js/example-frontend-js/src/ExampleMain.kt
+++ b/js/example-frontend-js/src/ExampleMain.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
import kotlinx.coroutines.*
diff --git a/js/example-frontend-js/src/main/web/style.css b/js/example-frontend-js/src/main/web/style.css
index 31d0ebc0..e2e777a5 100644
--- a/js/example-frontend-js/src/main/web/style.css
+++ b/js/example-frontend-js/src/main/web/style.css
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
#scene {
diff --git a/js/js-stub/README.md b/js/js-stub/README.md
deleted file mode 100644
index 46d18670..00000000
--- a/js/js-stub/README.md
+++ /dev/null
@@ -1 +0,0 @@
-This is a workaround for Dokka to generate proper references for JS modules. \ No newline at end of file
diff --git a/js/js-stub/build.gradle.kts b/js/js-stub/build.gradle.kts
deleted file mode 100644
index 6b9d6555..00000000
--- a/js/js-stub/build.gradle.kts
+++ /dev/null
@@ -1,11 +0,0 @@
-/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
- */
-
-import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
-
-tasks.named<KotlinCompile>("compileKotlin") {
- kotlinOptions {
- freeCompilerArgs += "-Xallow-kotlin-package"
- }
-}
diff --git a/js/js-stub/src/Performance.kt b/js/js-stub/src/Performance.kt
deleted file mode 100644
index 0b85c93d..00000000
--- a/js/js-stub/src/Performance.kt
+++ /dev/null
@@ -1,9 +0,0 @@
-/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
- */
-
-package org.w3c.performance
-
-public abstract class Performance {
- public abstract fun now(): Double
-}
diff --git a/js/js-stub/src/Promise.kt b/js/js-stub/src/Promise.kt
deleted file mode 100644
index 7413a872..00000000
--- a/js/js-stub/src/Promise.kt
+++ /dev/null
@@ -1,7 +0,0 @@
-/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
- */
-
-package kotlin.js
-
-public open class Promise<out T>
diff --git a/js/js-stub/src/Window.kt b/js/js-stub/src/Window.kt
deleted file mode 100644
index f54ed0d7..00000000
--- a/js/js-stub/src/Window.kt
+++ /dev/null
@@ -1,7 +0,0 @@
-/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
- */
-
-package org.w3c.dom
-
-public abstract class Window
diff --git a/knit.properties b/knit.properties
index bc177ce4..45b2216d 100644
--- a/knit.properties
+++ b/knit.properties
@@ -1,5 +1,5 @@
#
-# Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+# Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
#
knit.include=docs/knit.code.include
@@ -13,4 +13,4 @@ test.mode.FLEXIBLE_TIME=verifyLinesFlexibleTime
test.mode.FLEXIBLE_THREAD=verifyLinesFlexibleThread
test.mode.LINES_START_UNORDERED=verifyLinesStartUnordered
test.mode.LINES_START=verifyLinesStart
-test.mode.EXCEPTION=verifyExceptions \ No newline at end of file
+test.mode.EXCEPTION=verifyExceptions
diff --git a/kotlinx-coroutines-bom/build.gradle b/kotlinx-coroutines-bom/build.gradle
index e39c9229..ef87f3f7 100644
--- a/kotlinx-coroutines-bom/build.gradle
+++ b/kotlinx-coroutines-bom/build.gradle
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
plugins {
id 'java-platform'
diff --git a/kotlinx-coroutines-core/README.md b/kotlinx-coroutines-core/README.md
index 5fe32981..c21e5048 100644
--- a/kotlinx-coroutines-core/README.md
+++ b/kotlinx-coroutines-core/README.md
@@ -4,45 +4,45 @@ Core primitives to work with coroutines.
Coroutine builder functions:
-| **Name** | **Result** | **Scope** | **Description**
-| ------------- | ------------- | ---------------- | ---------------
-| [launch] | [Job] | [CoroutineScope] | Launches coroutine that does not have any result
-| [async] | [Deferred] | [CoroutineScope] | Returns a single value with the future result
-| [produce][kotlinx.coroutines.channels.produce] | [ReceiveChannel][kotlinx.coroutines.channels.ReceiveChannel] | [ProducerScope][kotlinx.coroutines.channels.ProducerScope] | Produces a stream of elements
-| [runBlocking] | `T` | [CoroutineScope] | Blocks the thread while the coroutine runs
+| **Name** | **Result** | **Scope** | **Description**
+| ---------------------------------------------- | ------------------------------------------------------------ | ---------------------------------------------------------- | ---------------
+| [launch][kotlinx.coroutines.launch] | [Job][kotlinx.coroutines.Job] | [CoroutineScope][kotlinx.coroutines.CoroutineScope] | Launches coroutine that does not have any result
+| [async][kotlinx.coroutines.async] | [Deferred][kotlinx.coroutines.Deferred] | [CoroutineScope][kotlinx.coroutines.CoroutineScope] | Returns a single value with the future result
+| [produce][kotlinx.coroutines.channels.produce] | [ReceiveChannel][kotlinx.coroutines.channels.ReceiveChannel] | [ProducerScope][kotlinx.coroutines.channels.ProducerScope] | Produces a stream of elements
+| [runBlocking][kotlinx.coroutines.runBlocking] | `T` | [CoroutineScope][kotlinx.coroutines.CoroutineScope] | Blocks the thread while the coroutine runs
Coroutine dispatchers implementing [CoroutineDispatcher]:
-| **Name** | **Description**
-| --------------------------- | ---------------
-| [Dispatchers.Default] | Confines coroutine execution to a shared pool of background threads
-| [Dispatchers.Unconfined] | Does not confine coroutine execution in any way
+| **Name** | **Description**
+| ------------------------------------------------------------------- | ---------------
+| [Dispatchers.Default][kotlinx.coroutines.Dispatchers.Default] | Confines coroutine execution to a shared pool of background threads
+| [Dispatchers.Unconfined][kotlinx.coroutines.Dispatchers.Unconfined] | Does not confine coroutine execution in any way
More context elements:
-| **Name** | **Description**
-| --------------------------- | ---------------
-| [NonCancellable] | A non-cancelable job that is always active
-| [CoroutineExceptionHandler] | Handler for uncaught exception
+| **Name** | **Description**
+| ------------------------------------------------------------------------- | ---------------
+| [NonCancellable][kotlinx.coroutines.NonCancellable] | A non-cancelable job that is always active
+| [CoroutineExceptionHandler][kotlinx.coroutines.CoroutineExceptionHandler] | Handler for uncaught exception
Synchronization primitives for coroutines:
-| **Name** | **Suspending functions** | **Description**
-| ---------- | ----------------------------------------------------------- | ---------------
-| [Mutex][kotlinx.coroutines.sync.Mutex] | [lock][kotlinx.coroutines.sync.Mutex.lock] | Mutual exclusion
+| **Name** | **Suspending functions** | **Description**
+| ----------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- | ---------------
+| [Mutex][kotlinx.coroutines.sync.Mutex] | [lock][kotlinx.coroutines.sync.Mutex.lock] | Mutual exclusion
| [Channel][kotlinx.coroutines.channels.Channel] | [send][kotlinx.coroutines.channels.SendChannel.send], [receive][kotlinx.coroutines.channels.ReceiveChannel.receive] | Communication channel (aka queue or exchanger)
Top-level suspending functions:
-| **Name** | **Description**
-| ------------------- | ---------------
-| [delay] | Non-blocking sleep
-| [yield] | Yields thread in single-threaded dispatchers
-| [withContext] | Switches to a different context
-| [withTimeout] | Set execution time-limit with exception on timeout
-| [withTimeoutOrNull] | Set execution time-limit will null result on timeout
-| [awaitAll] | Awaits for successful completion of all given jobs or exceptional completion of any
-| [joinAll] | Joins on all given jobs
+| **Name** | **Description**
+| --------------------------------------------------------- | ---------------
+| [delay][kotlinx.coroutines.delay] | Non-blocking sleep
+| [yield][kotlinx.coroutines.yield] | Yields thread in single-threaded dispatchers
+| [withContext][kotlinx.coroutines.withContext] | Switches to a different context
+| [withTimeout][kotlinx.coroutines.withTimeout] | Set execution time-limit with exception on timeout
+| [withTimeoutOrNull][kotlinx.coroutines.withTimeoutOrNull] | Set execution time-limit will null result on timeout
+| [awaitAll][kotlinx.coroutines.awaitAll] | Awaits for successful completion of all given jobs or exceptional completion of any
+| [joinAll][kotlinx.coroutines.joinAll] | Joins on all given jobs
Cancellation support for user-defined suspending functions is available with [suspendCancellableCoroutine]
helper function. [NonCancellable] job object is provided to suppress cancellation with
@@ -50,15 +50,15 @@ helper function. [NonCancellable] job object is provided to suppress cancellatio
[Select][kotlinx.coroutines.selects.select] expression waits for the result of multiple suspending functions simultaneously:
-| **Receiver** | **Suspending function** | **Select clause** | **Non-suspending version**
-| ---------------- | --------------------------------------------- | ------------------------------------------------ | --------------------------
-| [Job] | [join][Job.join] | [onJoin][Job.onJoin] | [isCompleted][Job.isCompleted]
-| [Deferred] | [await][Deferred.await] | [onAwait][Deferred.onAwait] | [isCompleted][Job.isCompleted]
-| [SendChannel][kotlinx.coroutines.channels.SendChannel] | [send][kotlinx.coroutines.channels.SendChannel.send] | [onSend][kotlinx.coroutines.channels.SendChannel.onSend] | [offer][kotlinx.coroutines.channels.SendChannel.offer]
-| [ReceiveChannel][kotlinx.coroutines.channels.ReceiveChannel] | [receive][kotlinx.coroutines.channels.ReceiveChannel.receive] | [onReceive][kotlinx.coroutines.channels.ReceiveChannel.onReceive] | [poll][kotlinx.coroutines.channels.ReceiveChannel.poll]
-| [ReceiveChannel][kotlinx.coroutines.channels.ReceiveChannel] | [receiveOrNull][kotlinx.coroutines.channels.receiveOrNull] | [onReceiveOrNull][kotlinx.coroutines.channels.onReceiveOrNull] | [poll][kotlinx.coroutines.channels.ReceiveChannel.poll]
-| [Mutex][kotlinx.coroutines.sync.Mutex] | [lock][kotlinx.coroutines.sync.Mutex.lock] | [onLock][kotlinx.coroutines.sync.Mutex.onLock] | [tryLock][kotlinx.coroutines.sync.Mutex.tryLock]
-| none | [delay] | [onTimeout][kotlinx.coroutines.selects.SelectBuilder.onTimeout] | none
+| **Receiver** | **Suspending function** | **Select clause** | **Non-suspending version**
+| ------------------------------------------------------------ | --------------------------------------------------------------- | ----------------------------------------------------------------- | --------------------------
+| [Job][kotlinx.coroutines.Job] | [join][kotlinx.coroutines.Job.join] | [onJoin][kotlinx.coroutines.Job.onJoin] | [isCompleted][kotlinx.coroutines.Job.isCompleted]
+| [Deferred][kotlinx.coroutines.Deferred] | [await][kotlinx.coroutines.Deferred.await] | [onAwait][kotlinx.coroutines.Deferred.onAwait] | [isCompleted][kotlinx.coroutines.Job.isCompleted]
+| [SendChannel][kotlinx.coroutines.channels.SendChannel] | [send][kotlinx.coroutines.channels.SendChannel.send] | [onSend][kotlinx.coroutines.channels.SendChannel.onSend] | [trySend][kotlinx.coroutines.channels.SendChannel.trySend]
+| [ReceiveChannel][kotlinx.coroutines.channels.ReceiveChannel] | [receive][kotlinx.coroutines.channels.ReceiveChannel.receive] | [onReceive][kotlinx.coroutines.channels.ReceiveChannel.onReceive] | [tryReceive][kotlinx.coroutines.channels.ReceiveChannel.tryReceive]
+| [ReceiveChannel][kotlinx.coroutines.channels.ReceiveChannel] | [receiveCatching][kotlinx.coroutines.channels.receiveCatching] | [onReceiveCatching][kotlinx.coroutines.channels.onReceiveCatching] | [tryReceive][kotlinx.coroutines.channels.ReceiveChannel.tryReceive]
+| [Mutex][kotlinx.coroutines.sync.Mutex] | [lock][kotlinx.coroutines.sync.Mutex.lock] | [onLock][kotlinx.coroutines.sync.Mutex.onLock] | [tryLock][kotlinx.coroutines.sync.Mutex.tryLock]
+| none | [delay][kotlinx.coroutines.delay] | [onTimeout][kotlinx.coroutines.selects.SelectBuilder.onTimeout] | none
# Package kotlinx.coroutines
@@ -90,36 +90,42 @@ Obsolete and deprecated module to test coroutines. Replaced with `kotlinx-corout
<!--- MODULE kotlinx-coroutines-core -->
<!--- INDEX kotlinx.coroutines -->
-[launch]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/launch.html
-[Job]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job/index.html
-[CoroutineScope]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-scope/index.html
-[async]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/async.html
-[Deferred]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-deferred/index.html
-[runBlocking]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/run-blocking.html
+
+[kotlinx.coroutines.launch]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/launch.html
+[kotlinx.coroutines.Job]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job/index.html
+[kotlinx.coroutines.CoroutineScope]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-scope/index.html
+[kotlinx.coroutines.async]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/async.html
+[kotlinx.coroutines.Deferred]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-deferred/index.html
+[kotlinx.coroutines.runBlocking]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/run-blocking.html
[CoroutineDispatcher]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-dispatcher/index.html
-[Dispatchers.Default]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-dispatchers/-default.html
-[Dispatchers.Unconfined]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-dispatchers/-unconfined.html
-[NonCancellable]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-non-cancellable.html
-[CoroutineExceptionHandler]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-exception-handler/index.html
-[delay]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/delay.html
-[yield]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/yield.html
-[withContext]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/with-context.html
-[withTimeout]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/with-timeout.html
-[withTimeoutOrNull]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/with-timeout-or-null.html
-[awaitAll]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/await-all.html
-[joinAll]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/join-all.html
+[kotlinx.coroutines.Dispatchers.Default]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-dispatchers/-default.html
+[kotlinx.coroutines.Dispatchers.Unconfined]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-dispatchers/-unconfined.html
+[kotlinx.coroutines.NonCancellable]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-non-cancellable/index.html
+[kotlinx.coroutines.CoroutineExceptionHandler]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-exception-handler/index.html
+[kotlinx.coroutines.delay]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/delay.html
+[kotlinx.coroutines.yield]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/yield.html
+[kotlinx.coroutines.withContext]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/with-context.html
+[kotlinx.coroutines.withTimeout]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/with-timeout.html
+[kotlinx.coroutines.withTimeoutOrNull]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/with-timeout-or-null.html
+[kotlinx.coroutines.awaitAll]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/await-all.html
+[kotlinx.coroutines.joinAll]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/join-all.html
[suspendCancellableCoroutine]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/suspend-cancellable-coroutine.html
-[Job.join]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job/join.html
-[Job.onJoin]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job/on-join.html
-[Job.isCompleted]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job/is-completed.html
-[Deferred.await]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-deferred/await.html
-[Deferred.onAwait]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-deferred/on-await.html
+[NonCancellable]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-non-cancellable/index.html
+[kotlinx.coroutines.Job.join]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job/join.html
+[kotlinx.coroutines.Job.onJoin]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job/on-join.html
+[kotlinx.coroutines.Job.isCompleted]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job/is-completed.html
+[kotlinx.coroutines.Deferred.await]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-deferred/await.html
+[kotlinx.coroutines.Deferred.onAwait]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-deferred/on-await.html
+
<!--- INDEX kotlinx.coroutines.sync -->
+
[kotlinx.coroutines.sync.Mutex]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.sync/-mutex/index.html
[kotlinx.coroutines.sync.Mutex.lock]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.sync/-mutex/lock.html
[kotlinx.coroutines.sync.Mutex.onLock]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.sync/-mutex/on-lock.html
[kotlinx.coroutines.sync.Mutex.tryLock]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.sync/-mutex/try-lock.html
+
<!--- INDEX kotlinx.coroutines.channels -->
+
[kotlinx.coroutines.channels.produce]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/produce.html
[kotlinx.coroutines.channels.ReceiveChannel]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-receive-channel/index.html
[kotlinx.coroutines.channels.ProducerScope]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-producer-scope/index.html
@@ -128,13 +134,16 @@ Obsolete and deprecated module to test coroutines. Replaced with `kotlinx-corout
[kotlinx.coroutines.channels.ReceiveChannel.receive]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-receive-channel/receive.html
[kotlinx.coroutines.channels.SendChannel]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-send-channel/index.html
[kotlinx.coroutines.channels.SendChannel.onSend]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-send-channel/on-send.html
-[kotlinx.coroutines.channels.SendChannel.offer]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-send-channel/offer.html
+[kotlinx.coroutines.channels.SendChannel.trySend]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-send-channel/try-send.html
[kotlinx.coroutines.channels.ReceiveChannel.onReceive]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-receive-channel/on-receive.html
-[kotlinx.coroutines.channels.ReceiveChannel.poll]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-receive-channel/poll.html
-[kotlinx.coroutines.channels.receiveOrNull]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/receive-or-null.html
-[kotlinx.coroutines.channels.onReceiveOrNull]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/on-receive-or-null.html
+[kotlinx.coroutines.channels.ReceiveChannel.tryReceive]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-receive-channel/try-receive.html
+[kotlinx.coroutines.channels.receiveCatching]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-receive-channel/receive-catching.html
+[kotlinx.coroutines.channels.onReceiveCatching]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-receive-channel/on-receive-catching.html
+
<!--- INDEX kotlinx.coroutines.selects -->
+
[kotlinx.coroutines.selects.select]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.selects/select.html
-[kotlinx.coroutines.selects.SelectBuilder.onTimeout]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.selects/-select-builder/on-timeout.html
+[kotlinx.coroutines.selects.SelectBuilder.onTimeout]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.selects/on-timeout.html
+
<!--- INDEX kotlinx.coroutines.test -->
<!--- END -->
diff --git a/kotlinx-coroutines-core/api/kotlinx-coroutines-core.api b/kotlinx-coroutines-core/api/kotlinx-coroutines-core.api
index b86076fc..50bfb60d 100644
--- a/kotlinx-coroutines-core/api/kotlinx-coroutines-core.api
+++ b/kotlinx-coroutines-core/api/kotlinx-coroutines-core.api
@@ -1,7 +1,5 @@
public abstract class kotlinx/coroutines/AbstractCoroutine : kotlinx/coroutines/JobSupport, kotlin/coroutines/Continuation, kotlinx/coroutines/CoroutineScope, kotlinx/coroutines/Job {
- protected final field parentContext Lkotlin/coroutines/CoroutineContext;
- public fun <init> (Lkotlin/coroutines/CoroutineContext;Z)V
- public synthetic fun <init> (Lkotlin/coroutines/CoroutineContext;ZILkotlin/jvm/internal/DefaultConstructorMarker;)V
+ public fun <init> (Lkotlin/coroutines/CoroutineContext;ZZ)V
protected fun afterResume (Ljava/lang/Object;)V
protected fun cancellationExceptionMessage ()Ljava/lang/String;
public final fun getContext ()Lkotlin/coroutines/CoroutineContext;
@@ -10,10 +8,8 @@ public abstract class kotlinx/coroutines/AbstractCoroutine : kotlinx/coroutines/
protected fun onCancelled (Ljava/lang/Throwable;Z)V
protected fun onCompleted (Ljava/lang/Object;)V
protected final fun onCompletionInternal (Ljava/lang/Object;)V
- protected fun onStart ()V
public final fun resumeWith (Ljava/lang/Object;)V
public final fun start (Lkotlinx/coroutines/CoroutineStart;Ljava/lang/Object;Lkotlin/jvm/functions/Function2;)V
- public final fun start (Lkotlinx/coroutines/CoroutineStart;Lkotlin/jvm/functions/Function1;)V
}
public final class kotlinx/coroutines/AwaitKt {
@@ -89,6 +85,7 @@ public final class kotlinx/coroutines/CancellableContinuationKt {
public abstract interface class kotlinx/coroutines/ChildHandle : kotlinx/coroutines/DisposableHandle {
public abstract fun childCancelled (Ljava/lang/Throwable;)Z
+ public abstract fun getParent ()Lkotlinx/coroutines/Job;
}
public abstract interface class kotlinx/coroutines/ChildJob : kotlinx/coroutines/Job {
@@ -161,7 +158,7 @@ public abstract class kotlinx/coroutines/CoroutineDispatcher : kotlin/coroutines
public fun isDispatchNeeded (Lkotlin/coroutines/CoroutineContext;)Z
public fun minusKey (Lkotlin/coroutines/CoroutineContext$Key;)Lkotlin/coroutines/CoroutineContext;
public final fun plus (Lkotlinx/coroutines/CoroutineDispatcher;)Lkotlinx/coroutines/CoroutineDispatcher;
- public fun releaseInterceptedContinuation (Lkotlin/coroutines/Continuation;)V
+ public final fun releaseInterceptedContinuation (Lkotlin/coroutines/Continuation;)V
public fun toString ()Ljava/lang/String;
}
@@ -270,7 +267,10 @@ public final class kotlinx/coroutines/Delay$DefaultImpls {
public final class kotlinx/coroutines/DelayKt {
public static final fun awaitCancellation (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static final fun delay (JLkotlin/coroutines/Continuation;)Ljava/lang/Object;
- public static final fun delay-p9JZ4hM (DLkotlin/coroutines/Continuation;)Ljava/lang/Object;
+ public static final fun delay-VtjQ1oo (JLkotlin/coroutines/Continuation;)Ljava/lang/Object;
+}
+
+public abstract interface annotation class kotlinx/coroutines/DelicateCoroutinesApi : java/lang/annotation/Annotation {
}
public final class kotlinx/coroutines/Dispatchers {
@@ -420,6 +420,7 @@ public class kotlinx/coroutines/JobSupport : kotlinx/coroutines/ChildJob, kotlin
public final fun getKey ()Lkotlin/coroutines/CoroutineContext$Key;
public final fun getOnJoin ()Lkotlinx/coroutines/selects/SelectClause0;
protected fun handleJobException (Ljava/lang/Throwable;)Z
+ protected final fun initParentJob (Lkotlinx/coroutines/Job;)V
public final fun invokeOnCompletion (Lkotlin/jvm/functions/Function1;)Lkotlinx/coroutines/DisposableHandle;
public final fun invokeOnCompletion (ZZLkotlin/jvm/functions/Function1;)Lkotlinx/coroutines/DisposableHandle;
public fun isActive ()Z
@@ -431,6 +432,7 @@ public class kotlinx/coroutines/JobSupport : kotlinx/coroutines/ChildJob, kotlin
public fun minusKey (Lkotlin/coroutines/CoroutineContext$Key;)Lkotlin/coroutines/CoroutineContext;
protected fun onCancelling (Ljava/lang/Throwable;)V
protected fun onCompletionInternal (Ljava/lang/Object;)V
+ protected fun onStart ()V
public final fun parentCancelled (Lkotlinx/coroutines/ParentJob;)V
public fun plus (Lkotlin/coroutines/CoroutineContext;)Lkotlin/coroutines/CoroutineContext;
public fun plus (Lkotlinx/coroutines/Job;)Lkotlinx/coroutines/Job;
@@ -473,6 +475,7 @@ public final class kotlinx/coroutines/NonDisposableHandle : kotlinx/coroutines/C
public static final field INSTANCE Lkotlinx/coroutines/NonDisposableHandle;
public fun childCancelled (Ljava/lang/Throwable;)Z
public fun dispose ()V
+ public fun getParent ()Lkotlinx/coroutines/Job;
public fun toString ()Ljava/lang/String;
}
@@ -535,9 +538,9 @@ public final class kotlinx/coroutines/TimeoutCancellationException : java/util/c
public final class kotlinx/coroutines/TimeoutKt {
public static final fun withTimeout (JLkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
- public static final fun withTimeout-lwyi7ZQ (DLkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+ public static final fun withTimeout-KLykuaI (JLkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static final fun withTimeoutOrNull (JLkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
- public static final fun withTimeoutOrNull-lwyi7ZQ (DLkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+ public static final fun withTimeoutOrNull-KLykuaI (JLkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
}
public final class kotlinx/coroutines/YieldKt {
@@ -555,6 +558,9 @@ public abstract interface class kotlinx/coroutines/channels/ActorScope : kotlinx
public final class kotlinx/coroutines/channels/ActorScope$DefaultImpls {
public static synthetic fun cancel (Lkotlinx/coroutines/channels/ActorScope;)V
+ public static fun getOnReceiveOrNull (Lkotlinx/coroutines/channels/ActorScope;)Lkotlinx/coroutines/selects/SelectClause1;
+ public static fun poll (Lkotlinx/coroutines/channels/ActorScope;)Ljava/lang/Object;
+ public static fun receiveOrNull (Lkotlinx/coroutines/channels/ActorScope;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
}
public abstract interface class kotlinx/coroutines/channels/BroadcastChannel : kotlinx/coroutines/channels/SendChannel {
@@ -566,6 +572,7 @@ public abstract interface class kotlinx/coroutines/channels/BroadcastChannel : k
public final class kotlinx/coroutines/channels/BroadcastChannel$DefaultImpls {
public static synthetic fun cancel$default (Lkotlinx/coroutines/channels/BroadcastChannel;Ljava/lang/Throwable;ILjava/lang/Object;)Z
public static synthetic fun cancel$default (Lkotlinx/coroutines/channels/BroadcastChannel;Ljava/util/concurrent/CancellationException;ILjava/lang/Object;)V
+ public static fun offer (Lkotlinx/coroutines/channels/BroadcastChannel;Ljava/lang/Object;)Z
}
public final class kotlinx/coroutines/channels/BroadcastChannelKt {
@@ -598,6 +605,10 @@ public abstract interface class kotlinx/coroutines/channels/Channel : kotlinx/co
public final class kotlinx/coroutines/channels/Channel$DefaultImpls {
public static synthetic fun cancel (Lkotlinx/coroutines/channels/Channel;)V
+ public static fun getOnReceiveOrNull (Lkotlinx/coroutines/channels/Channel;)Lkotlinx/coroutines/selects/SelectClause1;
+ public static fun offer (Lkotlinx/coroutines/channels/Channel;Ljava/lang/Object;)Z
+ public static fun poll (Lkotlinx/coroutines/channels/Channel;)Ljava/lang/Object;
+ public static fun receiveOrNull (Lkotlinx/coroutines/channels/Channel;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
}
public final class kotlinx/coroutines/channels/Channel$Factory {
@@ -623,125 +634,107 @@ public final class kotlinx/coroutines/channels/ChannelKt {
public static final fun Channel (ILkotlinx/coroutines/channels/BufferOverflow;Lkotlin/jvm/functions/Function1;)Lkotlinx/coroutines/channels/Channel;
public static synthetic fun Channel$default (IILjava/lang/Object;)Lkotlinx/coroutines/channels/Channel;
public static synthetic fun Channel$default (ILkotlinx/coroutines/channels/BufferOverflow;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lkotlinx/coroutines/channels/Channel;
+ public static final fun getOrElse-WpGqRn0 (Ljava/lang/Object;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object;
+ public static final fun onClosed-WpGqRn0 (Ljava/lang/Object;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object;
+ public static final fun onFailure-WpGqRn0 (Ljava/lang/Object;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object;
+ public static final fun onSuccess-WpGqRn0 (Ljava/lang/Object;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object;
+}
+
+public final class kotlinx/coroutines/channels/ChannelResult {
+ public static final field Companion Lkotlinx/coroutines/channels/ChannelResult$Companion;
+ public static final synthetic fun box-impl (Ljava/lang/Object;)Lkotlinx/coroutines/channels/ChannelResult;
+ public static fun constructor-impl (Ljava/lang/Object;)Ljava/lang/Object;
+ public fun equals (Ljava/lang/Object;)Z
+ public static fun equals-impl (Ljava/lang/Object;Ljava/lang/Object;)Z
+ public static final fun equals-impl0 (Ljava/lang/Object;Ljava/lang/Object;)Z
+ public static final fun exceptionOrNull-impl (Ljava/lang/Object;)Ljava/lang/Throwable;
+ public static final fun getOrNull-impl (Ljava/lang/Object;)Ljava/lang/Object;
+ public static final fun getOrThrow-impl (Ljava/lang/Object;)Ljava/lang/Object;
+ public fun hashCode ()I
+ public static fun hashCode-impl (Ljava/lang/Object;)I
+ public static final fun isClosed-impl (Ljava/lang/Object;)Z
+ public static final fun isFailure-impl (Ljava/lang/Object;)Z
+ public static final fun isSuccess-impl (Ljava/lang/Object;)Z
+ public fun toString ()Ljava/lang/String;
+ public static fun toString-impl (Ljava/lang/Object;)Ljava/lang/String;
+ public final synthetic fun unbox-impl ()Ljava/lang/Object;
+}
+
+public final class kotlinx/coroutines/channels/ChannelResult$Companion {
+ public final fun closed-JP2dKIU (Ljava/lang/Throwable;)Ljava/lang/Object;
+ public final fun failure-PtdJZtk ()Ljava/lang/Object;
+ public final fun success-JP2dKIU (Ljava/lang/Object;)Ljava/lang/Object;
}
public final class kotlinx/coroutines/channels/ChannelsKt {
- public static final fun all (Lkotlinx/coroutines/channels/ReceiveChannel;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
- public static final fun any (Lkotlinx/coroutines/channels/ReceiveChannel;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
- public static final fun any (Lkotlinx/coroutines/channels/ReceiveChannel;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
- public static final fun associate (Lkotlinx/coroutines/channels/ReceiveChannel;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
- public static final fun associateBy (Lkotlinx/coroutines/channels/ReceiveChannel;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
- public static final fun associateBy (Lkotlinx/coroutines/channels/ReceiveChannel;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
- public static final fun associateByTo (Lkotlinx/coroutines/channels/ReceiveChannel;Ljava/util/Map;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
- public static final fun associateByTo (Lkotlinx/coroutines/channels/ReceiveChannel;Ljava/util/Map;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
- public static final fun associateTo (Lkotlinx/coroutines/channels/ReceiveChannel;Ljava/util/Map;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+ public static final synthetic fun any (Lkotlinx/coroutines/channels/ReceiveChannel;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static final fun cancelConsumed (Lkotlinx/coroutines/channels/ReceiveChannel;Ljava/lang/Throwable;)V
public static final fun consume (Lkotlinx/coroutines/channels/BroadcastChannel;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object;
public static final fun consume (Lkotlinx/coroutines/channels/ReceiveChannel;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object;
public static final fun consumeEach (Lkotlinx/coroutines/channels/BroadcastChannel;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static final fun consumeEach (Lkotlinx/coroutines/channels/ReceiveChannel;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
- public static final fun consumeEachIndexed (Lkotlinx/coroutines/channels/ReceiveChannel;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static final fun consumes (Lkotlinx/coroutines/channels/ReceiveChannel;)Lkotlin/jvm/functions/Function1;
public static final fun consumesAll ([Lkotlinx/coroutines/channels/ReceiveChannel;)Lkotlin/jvm/functions/Function1;
- public static final fun count (Lkotlinx/coroutines/channels/ReceiveChannel;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
- public static final fun count (Lkotlinx/coroutines/channels/ReceiveChannel;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
- public static final fun distinct (Lkotlinx/coroutines/channels/ReceiveChannel;)Lkotlinx/coroutines/channels/ReceiveChannel;
+ public static final synthetic fun count (Lkotlinx/coroutines/channels/ReceiveChannel;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+ public static final synthetic fun distinct (Lkotlinx/coroutines/channels/ReceiveChannel;)Lkotlinx/coroutines/channels/ReceiveChannel;
public static final fun distinctBy (Lkotlinx/coroutines/channels/ReceiveChannel;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;)Lkotlinx/coroutines/channels/ReceiveChannel;
public static synthetic fun distinctBy$default (Lkotlinx/coroutines/channels/ReceiveChannel;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lkotlinx/coroutines/channels/ReceiveChannel;
- public static final fun drop (Lkotlinx/coroutines/channels/ReceiveChannel;ILkotlin/coroutines/CoroutineContext;)Lkotlinx/coroutines/channels/ReceiveChannel;
+ public static final synthetic fun drop (Lkotlinx/coroutines/channels/ReceiveChannel;ILkotlin/coroutines/CoroutineContext;)Lkotlinx/coroutines/channels/ReceiveChannel;
public static synthetic fun drop$default (Lkotlinx/coroutines/channels/ReceiveChannel;ILkotlin/coroutines/CoroutineContext;ILjava/lang/Object;)Lkotlinx/coroutines/channels/ReceiveChannel;
- public static final fun dropWhile (Lkotlinx/coroutines/channels/ReceiveChannel;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;)Lkotlinx/coroutines/channels/ReceiveChannel;
+ public static final synthetic fun dropWhile (Lkotlinx/coroutines/channels/ReceiveChannel;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;)Lkotlinx/coroutines/channels/ReceiveChannel;
public static synthetic fun dropWhile$default (Lkotlinx/coroutines/channels/ReceiveChannel;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lkotlinx/coroutines/channels/ReceiveChannel;
- public static final fun elementAt (Lkotlinx/coroutines/channels/ReceiveChannel;ILkotlin/coroutines/Continuation;)Ljava/lang/Object;
- public static final fun elementAtOrElse (Lkotlinx/coroutines/channels/ReceiveChannel;ILkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
- public static final fun elementAtOrNull (Lkotlinx/coroutines/channels/ReceiveChannel;ILkotlin/coroutines/Continuation;)Ljava/lang/Object;
+ public static final synthetic fun elementAt (Lkotlinx/coroutines/channels/ReceiveChannel;ILkotlin/coroutines/Continuation;)Ljava/lang/Object;
+ public static final synthetic fun elementAtOrNull (Lkotlinx/coroutines/channels/ReceiveChannel;ILkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static final fun filter (Lkotlinx/coroutines/channels/ReceiveChannel;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;)Lkotlinx/coroutines/channels/ReceiveChannel;
public static synthetic fun filter$default (Lkotlinx/coroutines/channels/ReceiveChannel;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lkotlinx/coroutines/channels/ReceiveChannel;
- public static final fun filterIndexed (Lkotlinx/coroutines/channels/ReceiveChannel;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function3;)Lkotlinx/coroutines/channels/ReceiveChannel;
+ public static final synthetic fun filterIndexed (Lkotlinx/coroutines/channels/ReceiveChannel;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function3;)Lkotlinx/coroutines/channels/ReceiveChannel;
public static synthetic fun filterIndexed$default (Lkotlinx/coroutines/channels/ReceiveChannel;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function3;ILjava/lang/Object;)Lkotlinx/coroutines/channels/ReceiveChannel;
- public static final fun filterIndexedTo (Lkotlinx/coroutines/channels/ReceiveChannel;Ljava/util/Collection;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
- public static final fun filterIndexedTo (Lkotlinx/coroutines/channels/ReceiveChannel;Lkotlinx/coroutines/channels/SendChannel;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
- public static final fun filterNot (Lkotlinx/coroutines/channels/ReceiveChannel;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;)Lkotlinx/coroutines/channels/ReceiveChannel;
+ public static final synthetic fun filterNot (Lkotlinx/coroutines/channels/ReceiveChannel;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;)Lkotlinx/coroutines/channels/ReceiveChannel;
public static synthetic fun filterNot$default (Lkotlinx/coroutines/channels/ReceiveChannel;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lkotlinx/coroutines/channels/ReceiveChannel;
public static final fun filterNotNull (Lkotlinx/coroutines/channels/ReceiveChannel;)Lkotlinx/coroutines/channels/ReceiveChannel;
- public static final fun filterNotNullTo (Lkotlinx/coroutines/channels/ReceiveChannel;Ljava/util/Collection;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
- public static final fun filterNotNullTo (Lkotlinx/coroutines/channels/ReceiveChannel;Lkotlinx/coroutines/channels/SendChannel;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
- public static final fun filterNotTo (Lkotlinx/coroutines/channels/ReceiveChannel;Ljava/util/Collection;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
- public static final fun filterNotTo (Lkotlinx/coroutines/channels/ReceiveChannel;Lkotlinx/coroutines/channels/SendChannel;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
- public static final fun filterTo (Lkotlinx/coroutines/channels/ReceiveChannel;Ljava/util/Collection;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
- public static final fun filterTo (Lkotlinx/coroutines/channels/ReceiveChannel;Lkotlinx/coroutines/channels/SendChannel;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
- public static final fun find (Lkotlinx/coroutines/channels/ReceiveChannel;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
- public static final fun findLast (Lkotlinx/coroutines/channels/ReceiveChannel;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
- public static final fun first (Lkotlinx/coroutines/channels/ReceiveChannel;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
- public static final fun first (Lkotlinx/coroutines/channels/ReceiveChannel;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
- public static final fun firstOrNull (Lkotlinx/coroutines/channels/ReceiveChannel;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
- public static final fun firstOrNull (Lkotlinx/coroutines/channels/ReceiveChannel;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
- public static final fun flatMap (Lkotlinx/coroutines/channels/ReceiveChannel;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;)Lkotlinx/coroutines/channels/ReceiveChannel;
+ public static final synthetic fun filterNotNullTo (Lkotlinx/coroutines/channels/ReceiveChannel;Ljava/util/Collection;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+ public static final synthetic fun filterNotNullTo (Lkotlinx/coroutines/channels/ReceiveChannel;Lkotlinx/coroutines/channels/SendChannel;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+ public static final synthetic fun first (Lkotlinx/coroutines/channels/ReceiveChannel;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+ public static final synthetic fun firstOrNull (Lkotlinx/coroutines/channels/ReceiveChannel;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+ public static final synthetic fun flatMap (Lkotlinx/coroutines/channels/ReceiveChannel;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;)Lkotlinx/coroutines/channels/ReceiveChannel;
public static synthetic fun flatMap$default (Lkotlinx/coroutines/channels/ReceiveChannel;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lkotlinx/coroutines/channels/ReceiveChannel;
- public static final fun fold (Lkotlinx/coroutines/channels/ReceiveChannel;Ljava/lang/Object;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
- public static final fun foldIndexed (Lkotlinx/coroutines/channels/ReceiveChannel;Ljava/lang/Object;Lkotlin/jvm/functions/Function3;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
- public static final fun groupBy (Lkotlinx/coroutines/channels/ReceiveChannel;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
- public static final fun groupBy (Lkotlinx/coroutines/channels/ReceiveChannel;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
- public static final fun groupByTo (Lkotlinx/coroutines/channels/ReceiveChannel;Ljava/util/Map;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
- public static final fun groupByTo (Lkotlinx/coroutines/channels/ReceiveChannel;Ljava/util/Map;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
- public static final fun indexOf (Lkotlinx/coroutines/channels/ReceiveChannel;Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
- public static final fun indexOfFirst (Lkotlinx/coroutines/channels/ReceiveChannel;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
- public static final fun indexOfLast (Lkotlinx/coroutines/channels/ReceiveChannel;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
- public static final fun last (Lkotlinx/coroutines/channels/ReceiveChannel;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
- public static final fun last (Lkotlinx/coroutines/channels/ReceiveChannel;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
- public static final fun lastIndexOf (Lkotlinx/coroutines/channels/ReceiveChannel;Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
- public static final fun lastOrNull (Lkotlinx/coroutines/channels/ReceiveChannel;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
- public static final fun lastOrNull (Lkotlinx/coroutines/channels/ReceiveChannel;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+ public static final synthetic fun indexOf (Lkotlinx/coroutines/channels/ReceiveChannel;Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+ public static final synthetic fun last (Lkotlinx/coroutines/channels/ReceiveChannel;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+ public static final synthetic fun lastIndexOf (Lkotlinx/coroutines/channels/ReceiveChannel;Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+ public static final synthetic fun lastOrNull (Lkotlinx/coroutines/channels/ReceiveChannel;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static final fun map (Lkotlinx/coroutines/channels/ReceiveChannel;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;)Lkotlinx/coroutines/channels/ReceiveChannel;
public static synthetic fun map$default (Lkotlinx/coroutines/channels/ReceiveChannel;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lkotlinx/coroutines/channels/ReceiveChannel;
public static final fun mapIndexed (Lkotlinx/coroutines/channels/ReceiveChannel;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function3;)Lkotlinx/coroutines/channels/ReceiveChannel;
public static synthetic fun mapIndexed$default (Lkotlinx/coroutines/channels/ReceiveChannel;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function3;ILjava/lang/Object;)Lkotlinx/coroutines/channels/ReceiveChannel;
- public static final fun mapIndexedNotNull (Lkotlinx/coroutines/channels/ReceiveChannel;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function3;)Lkotlinx/coroutines/channels/ReceiveChannel;
+ public static final synthetic fun mapIndexedNotNull (Lkotlinx/coroutines/channels/ReceiveChannel;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function3;)Lkotlinx/coroutines/channels/ReceiveChannel;
public static synthetic fun mapIndexedNotNull$default (Lkotlinx/coroutines/channels/ReceiveChannel;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function3;ILjava/lang/Object;)Lkotlinx/coroutines/channels/ReceiveChannel;
- public static final fun mapIndexedNotNullTo (Lkotlinx/coroutines/channels/ReceiveChannel;Ljava/util/Collection;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
- public static final fun mapIndexedNotNullTo (Lkotlinx/coroutines/channels/ReceiveChannel;Lkotlinx/coroutines/channels/SendChannel;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
- public static final fun mapIndexedTo (Lkotlinx/coroutines/channels/ReceiveChannel;Ljava/util/Collection;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
- public static final fun mapIndexedTo (Lkotlinx/coroutines/channels/ReceiveChannel;Lkotlinx/coroutines/channels/SendChannel;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
- public static final fun mapNotNull (Lkotlinx/coroutines/channels/ReceiveChannel;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;)Lkotlinx/coroutines/channels/ReceiveChannel;
+ public static final synthetic fun mapNotNull (Lkotlinx/coroutines/channels/ReceiveChannel;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;)Lkotlinx/coroutines/channels/ReceiveChannel;
public static synthetic fun mapNotNull$default (Lkotlinx/coroutines/channels/ReceiveChannel;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lkotlinx/coroutines/channels/ReceiveChannel;
- public static final fun mapNotNullTo (Lkotlinx/coroutines/channels/ReceiveChannel;Ljava/util/Collection;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
- public static final fun mapNotNullTo (Lkotlinx/coroutines/channels/ReceiveChannel;Lkotlinx/coroutines/channels/SendChannel;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
- public static final fun mapTo (Lkotlinx/coroutines/channels/ReceiveChannel;Ljava/util/Collection;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
- public static final fun mapTo (Lkotlinx/coroutines/channels/ReceiveChannel;Lkotlinx/coroutines/channels/SendChannel;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
- public static final fun maxBy (Lkotlinx/coroutines/channels/ReceiveChannel;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
- public static final fun maxWith (Lkotlinx/coroutines/channels/ReceiveChannel;Ljava/util/Comparator;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
- public static final fun minBy (Lkotlinx/coroutines/channels/ReceiveChannel;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
- public static final fun minWith (Lkotlinx/coroutines/channels/ReceiveChannel;Ljava/util/Comparator;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
- public static final fun none (Lkotlinx/coroutines/channels/ReceiveChannel;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
- public static final fun none (Lkotlinx/coroutines/channels/ReceiveChannel;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+ public static final synthetic fun maxWith (Lkotlinx/coroutines/channels/ReceiveChannel;Ljava/util/Comparator;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+ public static final synthetic fun minWith (Lkotlinx/coroutines/channels/ReceiveChannel;Ljava/util/Comparator;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+ public static final synthetic fun none (Lkotlinx/coroutines/channels/ReceiveChannel;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static final fun onReceiveOrNull (Lkotlinx/coroutines/channels/ReceiveChannel;)Lkotlinx/coroutines/selects/SelectClause1;
- public static final fun partition (Lkotlinx/coroutines/channels/ReceiveChannel;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static final fun receiveOrNull (Lkotlinx/coroutines/channels/ReceiveChannel;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
- public static final fun reduce (Lkotlinx/coroutines/channels/ReceiveChannel;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
- public static final fun reduceIndexed (Lkotlinx/coroutines/channels/ReceiveChannel;Lkotlin/jvm/functions/Function3;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
- public static final fun requireNoNulls (Lkotlinx/coroutines/channels/ReceiveChannel;)Lkotlinx/coroutines/channels/ReceiveChannel;
+ public static final synthetic fun requireNoNulls (Lkotlinx/coroutines/channels/ReceiveChannel;)Lkotlinx/coroutines/channels/ReceiveChannel;
public static final fun sendBlocking (Lkotlinx/coroutines/channels/SendChannel;Ljava/lang/Object;)V
- public static final fun single (Lkotlinx/coroutines/channels/ReceiveChannel;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
- public static final fun single (Lkotlinx/coroutines/channels/ReceiveChannel;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
- public static final fun singleOrNull (Lkotlinx/coroutines/channels/ReceiveChannel;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
- public static final fun singleOrNull (Lkotlinx/coroutines/channels/ReceiveChannel;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
- public static final fun sumBy (Lkotlinx/coroutines/channels/ReceiveChannel;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
- public static final fun sumByDouble (Lkotlinx/coroutines/channels/ReceiveChannel;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
- public static final fun take (Lkotlinx/coroutines/channels/ReceiveChannel;ILkotlin/coroutines/CoroutineContext;)Lkotlinx/coroutines/channels/ReceiveChannel;
+ public static final synthetic fun single (Lkotlinx/coroutines/channels/ReceiveChannel;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+ public static final synthetic fun singleOrNull (Lkotlinx/coroutines/channels/ReceiveChannel;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+ public static final synthetic fun take (Lkotlinx/coroutines/channels/ReceiveChannel;ILkotlin/coroutines/CoroutineContext;)Lkotlinx/coroutines/channels/ReceiveChannel;
public static synthetic fun take$default (Lkotlinx/coroutines/channels/ReceiveChannel;ILkotlin/coroutines/CoroutineContext;ILjava/lang/Object;)Lkotlinx/coroutines/channels/ReceiveChannel;
- public static final fun takeWhile (Lkotlinx/coroutines/channels/ReceiveChannel;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;)Lkotlinx/coroutines/channels/ReceiveChannel;
+ public static final synthetic fun takeWhile (Lkotlinx/coroutines/channels/ReceiveChannel;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;)Lkotlinx/coroutines/channels/ReceiveChannel;
public static synthetic fun takeWhile$default (Lkotlinx/coroutines/channels/ReceiveChannel;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lkotlinx/coroutines/channels/ReceiveChannel;
public static final fun toChannel (Lkotlinx/coroutines/channels/ReceiveChannel;Lkotlinx/coroutines/channels/SendChannel;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static final fun toCollection (Lkotlinx/coroutines/channels/ReceiveChannel;Ljava/util/Collection;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static final fun toList (Lkotlinx/coroutines/channels/ReceiveChannel;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static final fun toMap (Lkotlinx/coroutines/channels/ReceiveChannel;Ljava/util/Map;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
- public static final fun toMap (Lkotlinx/coroutines/channels/ReceiveChannel;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
- public static final fun toMutableList (Lkotlinx/coroutines/channels/ReceiveChannel;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+ public static final synthetic fun toMap (Lkotlinx/coroutines/channels/ReceiveChannel;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+ public static final synthetic fun toMutableList (Lkotlinx/coroutines/channels/ReceiveChannel;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static final fun toMutableSet (Lkotlinx/coroutines/channels/ReceiveChannel;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
- public static final fun toSet (Lkotlinx/coroutines/channels/ReceiveChannel;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
- public static final fun withIndex (Lkotlinx/coroutines/channels/ReceiveChannel;Lkotlin/coroutines/CoroutineContext;)Lkotlinx/coroutines/channels/ReceiveChannel;
+ public static final synthetic fun toSet (Lkotlinx/coroutines/channels/ReceiveChannel;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+ public static final fun trySendBlocking (Lkotlinx/coroutines/channels/SendChannel;Ljava/lang/Object;)Ljava/lang/Object;
+ public static final synthetic fun withIndex (Lkotlinx/coroutines/channels/ReceiveChannel;Lkotlin/coroutines/CoroutineContext;)Lkotlinx/coroutines/channels/ReceiveChannel;
public static synthetic fun withIndex$default (Lkotlinx/coroutines/channels/ReceiveChannel;Lkotlin/coroutines/CoroutineContext;ILjava/lang/Object;)Lkotlinx/coroutines/channels/ReceiveChannel;
- public static final fun zip (Lkotlinx/coroutines/channels/ReceiveChannel;Lkotlinx/coroutines/channels/ReceiveChannel;)Lkotlinx/coroutines/channels/ReceiveChannel;
+ public static final synthetic fun zip (Lkotlinx/coroutines/channels/ReceiveChannel;Lkotlinx/coroutines/channels/ReceiveChannel;)Lkotlinx/coroutines/channels/ReceiveChannel;
public static final fun zip (Lkotlinx/coroutines/channels/ReceiveChannel;Lkotlinx/coroutines/channels/ReceiveChannel;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;)Lkotlinx/coroutines/channels/ReceiveChannel;
public static synthetic fun zip$default (Lkotlinx/coroutines/channels/ReceiveChannel;Lkotlinx/coroutines/channels/ReceiveChannel;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lkotlinx/coroutines/channels/ReceiveChannel;
}
@@ -765,10 +758,10 @@ public final class kotlinx/coroutines/channels/ConflatedBroadcastChannel : kotli
public final fun getValueOrNull ()Ljava/lang/Object;
public fun invokeOnClose (Lkotlin/jvm/functions/Function1;)V
public fun isClosedForSend ()Z
- public fun isFull ()Z
public fun offer (Ljava/lang/Object;)Z
public fun openSubscription ()Lkotlinx/coroutines/channels/ReceiveChannel;
public fun send (Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+ public fun trySend-JP2dKIU (Ljava/lang/Object;)Ljava/lang/Object;
}
public final class kotlinx/coroutines/channels/ProduceKt {
@@ -784,26 +777,34 @@ public abstract interface class kotlinx/coroutines/channels/ProducerScope : kotl
public abstract fun getChannel ()Lkotlinx/coroutines/channels/SendChannel;
}
+public final class kotlinx/coroutines/channels/ProducerScope$DefaultImpls {
+ public static fun offer (Lkotlinx/coroutines/channels/ProducerScope;Ljava/lang/Object;)Z
+}
+
public abstract interface class kotlinx/coroutines/channels/ReceiveChannel {
public abstract synthetic fun cancel ()V
public abstract synthetic fun cancel (Ljava/lang/Throwable;)Z
public abstract fun cancel (Ljava/util/concurrent/CancellationException;)V
public abstract fun getOnReceive ()Lkotlinx/coroutines/selects/SelectClause1;
- public abstract fun getOnReceiveOrClosed ()Lkotlinx/coroutines/selects/SelectClause1;
+ public abstract fun getOnReceiveCatching ()Lkotlinx/coroutines/selects/SelectClause1;
public abstract fun getOnReceiveOrNull ()Lkotlinx/coroutines/selects/SelectClause1;
public abstract fun isClosedForReceive ()Z
public abstract fun isEmpty ()Z
public abstract fun iterator ()Lkotlinx/coroutines/channels/ChannelIterator;
public abstract fun poll ()Ljava/lang/Object;
public abstract fun receive (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
- public abstract fun receiveOrClosed-ZYPwvRU (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+ public abstract fun receiveCatching-JP2dKIU (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public abstract fun receiveOrNull (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+ public abstract fun tryReceive-PtdJZtk ()Ljava/lang/Object;
}
public final class kotlinx/coroutines/channels/ReceiveChannel$DefaultImpls {
public static synthetic fun cancel (Lkotlinx/coroutines/channels/ReceiveChannel;)V
public static synthetic fun cancel$default (Lkotlinx/coroutines/channels/ReceiveChannel;Ljava/lang/Throwable;ILjava/lang/Object;)Z
public static synthetic fun cancel$default (Lkotlinx/coroutines/channels/ReceiveChannel;Ljava/util/concurrent/CancellationException;ILjava/lang/Object;)V
+ public static fun getOnReceiveOrNull (Lkotlinx/coroutines/channels/ReceiveChannel;)Lkotlinx/coroutines/selects/SelectClause1;
+ public static fun poll (Lkotlinx/coroutines/channels/ReceiveChannel;)Ljava/lang/Object;
+ public static fun receiveOrNull (Lkotlinx/coroutines/channels/ReceiveChannel;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
}
public abstract interface class kotlinx/coroutines/channels/SendChannel {
@@ -811,13 +812,14 @@ public abstract interface class kotlinx/coroutines/channels/SendChannel {
public abstract fun getOnSend ()Lkotlinx/coroutines/selects/SelectClause2;
public abstract fun invokeOnClose (Lkotlin/jvm/functions/Function1;)V
public abstract fun isClosedForSend ()Z
- public abstract fun isFull ()Z
public abstract fun offer (Ljava/lang/Object;)Z
public abstract fun send (Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+ public abstract fun trySend-JP2dKIU (Ljava/lang/Object;)Ljava/lang/Object;
}
public final class kotlinx/coroutines/channels/SendChannel$DefaultImpls {
public static synthetic fun close$default (Lkotlinx/coroutines/channels/SendChannel;Ljava/lang/Throwable;ILjava/lang/Object;)Z
+ public static fun offer (Lkotlinx/coroutines/channels/SendChannel;Ljava/lang/Object;)Z
}
public final class kotlinx/coroutines/channels/TickerChannelsKt {
@@ -832,23 +834,6 @@ public final class kotlinx/coroutines/channels/TickerMode : java/lang/Enum {
public static fun values ()[Lkotlinx/coroutines/channels/TickerMode;
}
-public final class kotlinx/coroutines/channels/ValueOrClosed {
- public static final field Companion Lkotlinx/coroutines/channels/ValueOrClosed$Companion;
- public static final synthetic fun box-impl (Ljava/lang/Object;)Lkotlinx/coroutines/channels/ValueOrClosed;
- public fun equals (Ljava/lang/Object;)Z
- public static fun equals-impl (Ljava/lang/Object;Ljava/lang/Object;)Z
- public static final fun equals-impl0 (Ljava/lang/Object;Ljava/lang/Object;)Z
- public static final fun getCloseCause-impl (Ljava/lang/Object;)Ljava/lang/Throwable;
- public static final fun getValue-impl (Ljava/lang/Object;)Ljava/lang/Object;
- public static final fun getValueOrNull-impl (Ljava/lang/Object;)Ljava/lang/Object;
- public fun hashCode ()I
- public static fun hashCode-impl (Ljava/lang/Object;)I
- public static final fun isClosed-impl (Ljava/lang/Object;)Z
- public fun toString ()Ljava/lang/String;
- public static fun toString-impl (Ljava/lang/Object;)Ljava/lang/String;
- public final synthetic fun unbox-impl ()Ljava/lang/Object;
-}
-
public final class kotlinx/coroutines/debug/internal/DebugCoroutineInfo {
public fun <init> (Lkotlinx/coroutines/debug/internal/DebugCoroutineInfoImpl;Lkotlin/coroutines/CoroutineContext;)V
public final fun getContext ()Lkotlin/coroutines/CoroutineContext;
@@ -861,10 +846,6 @@ public final class kotlinx/coroutines/debug/internal/DebugCoroutineInfo {
public final fun lastObservedStackTrace ()Ljava/util/List;
}
-public synthetic class kotlinx/coroutines/debug/internal/DebugProbesImplSequenceNumberRefVolatile {
- public fun <init> (J)V
-}
-
public final class kotlinx/coroutines/debug/internal/DebuggerInfo : java/io/Serializable {
public fun <init> (Lkotlinx/coroutines/debug/internal/DebugCoroutineInfoImpl;Lkotlin/coroutines/CoroutineContext;)V
public final fun getCoroutineId ()Ljava/lang/Long;
@@ -943,7 +924,7 @@ public final class kotlinx/coroutines/flow/FlowKt {
public static final fun count (Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static final fun debounce (Lkotlinx/coroutines/flow/Flow;J)Lkotlinx/coroutines/flow/Flow;
public static final fun debounce (Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function1;)Lkotlinx/coroutines/flow/Flow;
- public static final fun debounce-8GFy2Ro (Lkotlinx/coroutines/flow/Flow;D)Lkotlinx/coroutines/flow/Flow;
+ public static final fun debounce-HG0u8IE (Lkotlinx/coroutines/flow/Flow;J)Lkotlinx/coroutines/flow/Flow;
public static final fun debounceDuration (Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function1;)Lkotlinx/coroutines/flow/Flow;
public static final fun delayEach (Lkotlinx/coroutines/flow/Flow;J)Lkotlinx/coroutines/flow/Flow;
public static final fun delayFlow (Lkotlinx/coroutines/flow/Flow;J)Lkotlinx/coroutines/flow/Flow;
@@ -984,6 +965,8 @@ public final class kotlinx/coroutines/flow/FlowKt {
public static final fun fold (Lkotlinx/coroutines/flow/Flow;Ljava/lang/Object;Lkotlin/jvm/functions/Function3;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static final fun forEach (Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function2;)V
public static final fun getDEFAULT_CONCURRENCY ()I
+ public static final fun last (Lkotlinx/coroutines/flow/Flow;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+ public static final fun lastOrNull (Lkotlinx/coroutines/flow/Flow;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static final fun launchIn (Lkotlinx/coroutines/flow/Flow;Lkotlinx/coroutines/CoroutineScope;)Lkotlinx/coroutines/Job;
public static final fun map (Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function2;)Lkotlinx/coroutines/flow/Flow;
public static final fun mapLatest (Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function2;)Lkotlinx/coroutines/flow/Flow;
@@ -1017,9 +1000,10 @@ public final class kotlinx/coroutines/flow/FlowKt {
public static synthetic fun retry$default (Lkotlinx/coroutines/flow/Flow;ILkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lkotlinx/coroutines/flow/Flow;
public static synthetic fun retry$default (Lkotlinx/coroutines/flow/Flow;JLkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lkotlinx/coroutines/flow/Flow;
public static final fun retryWhen (Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function4;)Lkotlinx/coroutines/flow/Flow;
+ public static final fun runningFold (Lkotlinx/coroutines/flow/Flow;Ljava/lang/Object;Lkotlin/jvm/functions/Function3;)Lkotlinx/coroutines/flow/Flow;
public static final fun runningReduce (Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function3;)Lkotlinx/coroutines/flow/Flow;
public static final fun sample (Lkotlinx/coroutines/flow/Flow;J)Lkotlinx/coroutines/flow/Flow;
- public static final fun sample-8GFy2Ro (Lkotlinx/coroutines/flow/Flow;D)Lkotlinx/coroutines/flow/Flow;
+ public static final fun sample-HG0u8IE (Lkotlinx/coroutines/flow/Flow;J)Lkotlinx/coroutines/flow/Flow;
public static final fun scan (Lkotlinx/coroutines/flow/Flow;Ljava/lang/Object;Lkotlin/jvm/functions/Function3;)Lkotlinx/coroutines/flow/Flow;
public static final fun scanFold (Lkotlinx/coroutines/flow/Flow;Ljava/lang/Object;Lkotlin/jvm/functions/Function3;)Lkotlinx/coroutines/flow/Flow;
public static final fun scanReduce (Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function3;)Lkotlinx/coroutines/flow/Flow;
@@ -1064,6 +1048,7 @@ public final class kotlinx/coroutines/flow/LintKt {
}
public abstract interface class kotlinx/coroutines/flow/MutableSharedFlow : kotlinx/coroutines/flow/FlowCollector, kotlinx/coroutines/flow/SharedFlow {
+ public abstract fun emit (Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public abstract fun getSubscriptionCount ()Lkotlinx/coroutines/flow/StateFlow;
public abstract fun resetReplayCache ()V
public abstract fun tryEmit (Ljava/lang/Object;)Z
@@ -1105,8 +1090,8 @@ public final class kotlinx/coroutines/flow/SharingStarted$Companion {
}
public final class kotlinx/coroutines/flow/SharingStartedKt {
- public static final fun WhileSubscribed-9tZugJw (Lkotlinx/coroutines/flow/SharingStarted$Companion;DD)Lkotlinx/coroutines/flow/SharingStarted;
- public static synthetic fun WhileSubscribed-9tZugJw$default (Lkotlinx/coroutines/flow/SharingStarted$Companion;DDILjava/lang/Object;)Lkotlinx/coroutines/flow/SharingStarted;
+ public static final fun WhileSubscribed-5qebJ5I (Lkotlinx/coroutines/flow/SharingStarted$Companion;JJ)Lkotlinx/coroutines/flow/SharingStarted;
+ public static synthetic fun WhileSubscribed-5qebJ5I$default (Lkotlinx/coroutines/flow/SharingStarted$Companion;JJILjava/lang/Object;)Lkotlinx/coroutines/flow/SharingStarted;
}
public abstract interface class kotlinx/coroutines/flow/StateFlow : kotlinx/coroutines/flow/SharedFlow {
@@ -1115,6 +1100,9 @@ public abstract interface class kotlinx/coroutines/flow/StateFlow : kotlinx/coro
public final class kotlinx/coroutines/flow/StateFlowKt {
public static final fun MutableStateFlow (Ljava/lang/Object;)Lkotlinx/coroutines/flow/MutableStateFlow;
+ public static final fun getAndUpdate (Lkotlinx/coroutines/flow/MutableStateFlow;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object;
+ public static final fun update (Lkotlinx/coroutines/flow/MutableStateFlow;Lkotlin/jvm/functions/Function1;)V
+ public static final fun updateAndGet (Lkotlinx/coroutines/flow/MutableStateFlow;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object;
}
public abstract class kotlinx/coroutines/flow/internal/ChannelFlow : kotlinx/coroutines/flow/internal/FusibleFlow {
@@ -1123,7 +1111,6 @@ public abstract class kotlinx/coroutines/flow/internal/ChannelFlow : kotlinx/cor
public final field onBufferOverflow Lkotlinx/coroutines/channels/BufferOverflow;
public fun <init> (Lkotlin/coroutines/CoroutineContext;ILkotlinx/coroutines/channels/BufferOverflow;)V
protected fun additionalToStringProps ()Ljava/lang/String;
- public fun broadcastImpl (Lkotlinx/coroutines/CoroutineScope;Lkotlinx/coroutines/CoroutineStart;)Lkotlinx/coroutines/channels/BroadcastChannel;
public fun collect (Lkotlinx/coroutines/flow/FlowCollector;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
protected abstract fun collectTo (Lkotlinx/coroutines/channels/ProducerScope;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
protected abstract fun create (Lkotlin/coroutines/CoroutineContext;ILkotlinx/coroutines/channels/BufferOverflow;)Lkotlinx/coroutines/flow/internal/ChannelFlow;
@@ -1237,7 +1224,7 @@ public abstract interface class kotlinx/coroutines/selects/SelectInstance {
}
public final class kotlinx/coroutines/selects/SelectKt {
- public static final fun onTimeout-0lHKgQg (Lkotlinx/coroutines/selects/SelectBuilder;DLkotlin/jvm/functions/Function1;)V
+ public static final fun onTimeout-8Mi8wO0 (Lkotlinx/coroutines/selects/SelectBuilder;JLkotlin/jvm/functions/Function1;)V
public static final fun select (Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
}
diff --git a/kotlinx-coroutines-core/build.gradle b/kotlinx-coroutines-core/build.gradle
index f98f6a52..c45ca08c 100644
--- a/kotlinx-coroutines-core/build.gradle
+++ b/kotlinx-coroutines-core/build.gradle
@@ -1,12 +1,17 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
apply plugin: 'org.jetbrains.kotlin.multiplatform'
+apply plugin: 'org.jetbrains.dokka'
apply from: rootProject.file("gradle/compile-jvm-multiplatform.gradle")
apply from: rootProject.file("gradle/compile-common.gradle")
+
+if (rootProject.ext.native_targets_enabled) {
+ apply from: rootProject.file("gradle/compile-native-multiplatform.gradle")
+}
+
apply from: rootProject.file("gradle/compile-js-multiplatform.gradle")
-apply from: rootProject.file("gradle/compile-native-multiplatform.gradle")
apply from: rootProject.file('gradle/publish-npm-js.gradle')
/* ==========================================================================
@@ -52,8 +57,11 @@ static boolean isNativeDarwin(String name) { return ["ios", "macos", "tvos", "wa
static boolean isNativeOther(String name) { return ["linux", "mingw"].any { name.startsWith(it) } }
defineSourceSet("concurrent", ["common"]) { it in ["jvm", "native"] }
-defineSourceSet("nativeDarwin", ["native"]) { isNativeDarwin(it) }
-defineSourceSet("nativeOther", ["native"]) { isNativeOther(it) }
+
+if (rootProject.ext.native_targets_enabled) {
+ defineSourceSet("nativeDarwin", ["native"]) { isNativeDarwin(it) }
+ defineSourceSet("nativeOther", ["native"]) { isNativeOther(it) }
+}
/* ========================================================================== */
@@ -73,7 +81,7 @@ kotlin {
}
languageSettings {
progressiveMode = true
- experimentalAnnotations.each { useExperimentalAnnotation(it) }
+ optInAnnotations.each { useExperimentalAnnotation(it) }
}
}
@@ -129,7 +137,7 @@ def configureNativeSourceSetPreset(name, preset) {
}
// :KLUDGE: Idea.active: Configure platform libraries for native source sets when working in IDEA
-if (Idea.active) {
+if (Idea.active && rootProject.ext.native_targets_enabled) {
def manager = project.ext.hostManager
def linuxPreset = kotlin.presets.linuxX64
def macosPreset = kotlin.presets.macosX64
@@ -177,16 +185,38 @@ jvmTest {
minHeapSize = '1g'
maxHeapSize = '1g'
enableAssertions = true
- systemProperty 'java.security.manager', 'kotlinx.coroutines.TestSecurityManager'
+ if (!Idea.active) {
+ // We should not set this security manager when `jvmTest`
+ // is invoked by IntelliJ IDEA since we need to pass
+ // system properties for Lincheck and stress tests.
+ // TODO Remove once IDEA is smart enough to select between `jvmTest`/`jvmStressTest`/`jvmLincheckTest` #KTIJ-599
+ systemProperty 'java.security.manager', 'kotlinx.coroutines.TestSecurityManager'
+ }
// 'stress' is required to be able to run all subpackage tests like ":jvmTests --tests "*channels*" -Pstress=true"
if (!Idea.active && rootProject.properties['stress'] == null) {
+ exclude '**/*LincheckTest.*'
exclude '**/*StressTest.*'
}
- systemProperty 'kotlinx.coroutines.scheduler.keep.alive.sec', '100000' // any unpark problem hangs test
+ if (Idea.active) {
+ // Configure the IDEA runner for Lincheck
+ configureJvmForLincheck(jvmTest)
+ }
}
-jvmJar {
- manifest {
+// Setup manifest for kotlinx-coroutines-core-jvm.jar
+jvmJar { setupManifest(it) }
+
+/*
+ * Setup manifest for kotlinx-coroutines-core.jar
+ * This is convenient for users that pass -javaagent arg manually and also is a workaround #2619 and KTIJ-5659.
+ * This manifest contains reference to AgentPremain that belongs to
+ * kotlinx-coroutines-core-jvm, but our resolving machinery guarantees that
+ * any JVM project that depends on -core artifact also depends on -core-jvm one.
+ */
+metadataJar { setupManifest(it) }
+
+static def setupManifest(Jar jar) {
+ jar.manifest {
attributes "Premain-Class": "kotlinx.coroutines.debug.AgentPremain"
attributes "Can-Retransform-Classes": "true"
}
@@ -205,23 +235,41 @@ task jvmStressTest(type: Test, dependsOn: compileTestKotlinJvm) {
systemProperty 'kotlinx.coroutines.semaphore.maxSpinCycles', '10'
}
+task jvmLincheckTest(type: Test, dependsOn: compileTestKotlinJvm) {
+ classpath = files { jvmTest.classpath }
+ testClassesDirs = files { jvmTest.testClassesDirs }
+ include '**/*LincheckTest.*'
+ enableAssertions = true
+ testLogging.showStandardStreams = true
+ configureJvmForLincheck(jvmLincheckTest)
+}
+
+static void configureJvmForLincheck(task) {
+ task.minHeapSize = '1g'
+ task.maxHeapSize = '6g' // we may need more space for building an interleaving tree in the model checking mode
+ task.jvmArgs = ['--add-opens', 'java.base/jdk.internal.misc=ALL-UNNAMED', // required for transformation
+ '--add-exports', 'java.base/jdk.internal.util=ALL-UNNAMED'] // in the model checking mode
+ task.systemProperty 'kotlinx.coroutines.semaphore.segmentSize', '2'
+ task.systemProperty 'kotlinx.coroutines.semaphore.maxSpinCycles', '1' // better for the model checking mode
+}
+
task jdk16Test(type: Test, dependsOn: [compileTestKotlinJvm, checkJdk16]) {
classpath = files { jvmTest.classpath }
testClassesDirs = files { jvmTest.testClassesDirs }
executable = "$System.env.JDK_16/bin/java"
exclude '**/*LFStressTest.*' // lock-freedom tests use LockFreedomTestEnvironment which needs JDK8
- exclude '**/*LCStressTest.*' // lin-check tests use LinChecker which needs JDK8
+ exclude '**/*LincheckTest.*' // Lincheck tests use LinChecker which needs JDK8
exclude '**/exceptions/**' // exceptions tests check suppressed exception which needs JDK8
exclude '**/ExceptionsGuideTest.*'
exclude '**/RunInterruptibleStressTest.*' // fails on JDK 1.6 due to JDK bug
}
-// Run these tests only during nightly stress test
+// Run jdk16Test test only during nightly stress test
jdk16Test.onlyIf { project.properties['stressTest'] != null }
-// Always run those tests
-task moreTest(dependsOn: [jvmStressTest, jdk16Test])
-build.dependsOn moreTest
+// Always check additional test sets
+task moreTest(dependsOn: [jvmStressTest, jvmLincheckTest, jdk16Test])
+check.dependsOn moreTest
task testsJar(type: Jar, dependsOn: jvmTestClasses) {
classifier = 'tests'
diff --git a/kotlinx-coroutines-core/common/README.md b/kotlinx-coroutines-core/common/README.md
index e59392ee..fcfe334c 100644
--- a/kotlinx-coroutines-core/common/README.md
+++ b/kotlinx-coroutines-core/common/README.md
@@ -19,7 +19,7 @@ Coroutine dispatchers implementing [CoroutineDispatcher]:
| [Dispatchers.Unconfined] | Does not confine coroutine execution in any way
| [newSingleThreadContext] | Creates a single-threaded coroutine context
| [newFixedThreadPoolContext] | Creates a thread pool of a fixed size
-| [Executor.asCoroutineDispatcher][java.util.concurrent.Executor.asCoroutineDispatcher] | Extension to convert any executor
+| [Executor.asCoroutineDispatcher][asCoroutineDispatcher] | Extension to convert any executor
More context elements:
@@ -57,9 +57,9 @@ helper function. [NonCancellable] job object is provided to suppress cancellatio
| ---------------- | --------------------------------------------- | ------------------------------------------------ | --------------------------
| [Job] | [join][Job.join] | [onJoin][Job.onJoin] | [isCompleted][Job.isCompleted]
| [Deferred] | [await][Deferred.await] | [onAwait][Deferred.onAwait] | [isCompleted][Job.isCompleted]
-| [SendChannel][kotlinx.coroutines.channels.SendChannel] | [send][kotlinx.coroutines.channels.SendChannel.send] | [onSend][kotlinx.coroutines.channels.SendChannel.onSend] | [offer][kotlinx.coroutines.channels.SendChannel.offer]
-| [ReceiveChannel][kotlinx.coroutines.channels.ReceiveChannel] | [receive][kotlinx.coroutines.channels.ReceiveChannel.receive] | [onReceive][kotlinx.coroutines.channels.ReceiveChannel.onReceive] | [poll][kotlinx.coroutines.channels.ReceiveChannel.poll]
-| [ReceiveChannel][kotlinx.coroutines.channels.ReceiveChannel] | [receiveOrNull][kotlinx.coroutines.channels.receiveOrNull] | [onReceiveOrNull][kotlinx.coroutines.channels.onReceiveOrNull] | [poll][kotlinx.coroutines.channels.ReceiveChannel.poll]
+| [SendChannel][kotlinx.coroutines.channels.SendChannel] | [send][kotlinx.coroutines.channels.SendChannel.send] | [onSend][kotlinx.coroutines.channels.SendChannel.onSend] | [trySend][kotlinx.coroutines.channels.SendChannel.trySend]
+| [ReceiveChannel][kotlinx.coroutines.channels.ReceiveChannel] | [receive][kotlinx.coroutines.channels.ReceiveChannel.receive] | [onReceive][kotlinx.coroutines.channels.ReceiveChannel.onReceive] | [tryReceive][kotlinx.coroutines.channels.ReceiveChannel.tryReceive]
+| [ReceiveChannel][kotlinx.coroutines.channels.ReceiveChannel] | [receiveCatching][kotlinx.coroutines.channels.ReceiveChannel.receiveCatching] | [onReceiveCatching][kotlinx.coroutines.channels.ReceiveChannel.onReceiveCatching] | [tryReceive][kotlinx.coroutines.channels.ReceiveChannel.tryReceive]
| [Mutex][kotlinx.coroutines.sync.Mutex] | [lock][kotlinx.coroutines.sync.Mutex.lock] | [onLock][kotlinx.coroutines.sync.Mutex.onLock] | [tryLock][kotlinx.coroutines.sync.Mutex.tryLock]
| none | [delay] | [onTimeout][kotlinx.coroutines.selects.SelectBuilder.onTimeout] | none
@@ -97,6 +97,7 @@ Low-level primitives for finer-grained control of coroutines.
<!--- MODULE kotlinx-coroutines-core -->
<!--- INDEX kotlinx.coroutines -->
+
[launch]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/launch.html
[Job]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job/index.html
[CoroutineScope]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-scope/index.html
@@ -107,8 +108,8 @@ Low-level primitives for finer-grained control of coroutines.
[Dispatchers.Unconfined]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-dispatchers/-unconfined.html
[newSingleThreadContext]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/new-single-thread-context.html
[newFixedThreadPoolContext]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/new-fixed-thread-pool-context.html
-[java.util.concurrent.Executor.asCoroutineDispatcher]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/java.util.concurrent.-executor/as-coroutine-dispatcher.html
-[NonCancellable]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-non-cancellable.html
+[asCoroutineDispatcher]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/as-coroutine-dispatcher.html
+[NonCancellable]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-non-cancellable/index.html
[CoroutineExceptionHandler]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-exception-handler/index.html
[delay]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/delay.html
[yield]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/yield.html
@@ -125,12 +126,16 @@ Low-level primitives for finer-grained control of coroutines.
[Deferred.onAwait]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-deferred/on-await.html
[newCoroutineContext]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/new-coroutine-context.html
[DEBUG_PROPERTY_NAME]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-d-e-b-u-g_-p-r-o-p-e-r-t-y_-n-a-m-e.html
+
<!--- INDEX kotlinx.coroutines.sync -->
+
[kotlinx.coroutines.sync.Mutex]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.sync/-mutex/index.html
[kotlinx.coroutines.sync.Mutex.lock]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.sync/-mutex/lock.html
[kotlinx.coroutines.sync.Mutex.onLock]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.sync/-mutex/on-lock.html
[kotlinx.coroutines.sync.Mutex.tryLock]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.sync/-mutex/try-lock.html
+
<!--- INDEX kotlinx.coroutines.channels -->
+
[kotlinx.coroutines.channels.produce]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/produce.html
[kotlinx.coroutines.channels.ReceiveChannel]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-receive-channel/index.html
[kotlinx.coroutines.channels.ProducerScope]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-producer-scope/index.html
@@ -141,14 +146,19 @@ Low-level primitives for finer-grained control of coroutines.
[kotlinx.coroutines.channels.SendChannel.send]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-send-channel/send.html
[kotlinx.coroutines.channels.ReceiveChannel.receive]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-receive-channel/receive.html
[kotlinx.coroutines.channels.SendChannel.onSend]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-send-channel/on-send.html
-[kotlinx.coroutines.channels.SendChannel.offer]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-send-channel/offer.html
+[kotlinx.coroutines.channels.SendChannel.trySend]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-send-channel/try-send.html
[kotlinx.coroutines.channels.ReceiveChannel.onReceive]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-receive-channel/on-receive.html
-[kotlinx.coroutines.channels.ReceiveChannel.poll]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-receive-channel/poll.html
-[kotlinx.coroutines.channels.receiveOrNull]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/receive-or-null.html
-[kotlinx.coroutines.channels.onReceiveOrNull]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/on-receive-or-null.html
+[kotlinx.coroutines.channels.ReceiveChannel.tryReceive]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-receive-channel/try-receive.html
+[kotlinx.coroutines.channels.ReceiveChannel.receiveCatching]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-receive-channel/receive-catching.html
+[kotlinx.coroutines.channels.ReceiveChannel.onReceiveCatching]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-receive-channel/on-receive-catching.html
+
<!--- INDEX kotlinx.coroutines.selects -->
+
[kotlinx.coroutines.selects.select]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.selects/select.html
-[kotlinx.coroutines.selects.SelectBuilder.onTimeout]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.selects/-select-builder/on-timeout.html
+[kotlinx.coroutines.selects.SelectBuilder.onTimeout]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.selects/on-timeout.html
+
<!--- INDEX kotlinx.coroutines.test -->
+
[kotlinx.coroutines.test.TestCoroutineContext]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.test/-test-coroutine-context/index.html
+
<!--- END -->
diff --git a/kotlinx-coroutines-core/common/src/AbstractCoroutine.kt b/kotlinx-coroutines-core/common/src/AbstractCoroutine.kt
index 742c9670..439a9ac7 100644
--- a/kotlinx-coroutines-core/common/src/AbstractCoroutine.kt
+++ b/kotlinx-coroutines-core/common/src/AbstractCoroutine.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
@file:Suppress("DEPRECATION_ERROR")
@@ -8,7 +8,6 @@ package kotlinx.coroutines
import kotlinx.coroutines.CoroutineStart.*
import kotlinx.coroutines.intrinsics.*
import kotlin.coroutines.*
-import kotlin.jvm.*
/**
* Abstract base class for implementation of coroutines in coroutine builders.
@@ -26,6 +25,9 @@ import kotlin.jvm.*
* * [onCancelled] in invoked when the coroutine completes with an exception (cancelled).
*
* @param parentContext the context of the parent coroutine.
+ * @param initParentJob specifies whether the parent-child relationship should be instantiated directly
+ * in `AbstractCoroutine` constructor. If set to `false`, it's the responsibility of the child class
+ * to invoke [initParentJob] manually.
* @param active when `true` (by default), the coroutine is created in the _active_ state, otherwise it is created in the _new_ state.
* See [Job] for details.
*
@@ -33,13 +35,22 @@ import kotlin.jvm.*
*/
@InternalCoroutinesApi
public abstract class AbstractCoroutine<in T>(
- /**
- * The context of the parent coroutine.
- */
- @JvmField
- protected val parentContext: CoroutineContext,
- active: Boolean = true
+ parentContext: CoroutineContext,
+ initParentJob: Boolean,
+ active: Boolean
) : JobSupport(active), Job, Continuation<T>, CoroutineScope {
+
+ init {
+ /*
+ * Setup parent-child relationship between the parent in the context and the current coroutine.
+ * It may cause this coroutine to become _cancelling_ if the parent is already cancelled.
+ * It is dangerous to install parent-child relationship here if the coroutine class
+ * operates its state from within onCancelled or onCancelling
+ * (with exceptions for rx integrations that can't have any parent)
+ */
+ if (initParentJob) initParentJob(parentContext[Job])
+ }
+
/**
* The context of this coroutine that includes this coroutine as a [Job].
*/
@@ -54,28 +65,6 @@ public abstract class AbstractCoroutine<in T>(
override val isActive: Boolean get() = super.isActive
/**
- * Initializes the parent job from the `parentContext` of this coroutine that was passed to it during construction.
- * It shall be invoked at most once after construction after all other initialization.
- *
- * Invocation of this function may cause this coroutine to become cancelled if the parent is already cancelled,
- * in which case it synchronously invokes all the corresponding handlers.
- * @suppress **This is unstable API and it is subject to change.**
- */
- internal fun initParentJob() {
- initParentJobInternal(parentContext[Job])
- }
-
- /**
- * This function is invoked once when a non-active coroutine (constructed with `active` set to `false)
- * is [started][start].
- */
- protected open fun onStart() {}
-
- internal final override fun onStartInternal() {
- onStart()
- }
-
- /**
* This function is invoked once when the job was completed normally with the specified [value],
* right before all the waiters for the coroutine's completion are notified.
*/
@@ -127,26 +116,6 @@ public abstract class AbstractCoroutine<in T>(
/**
* Starts this coroutine with the given code [block] and [start] strategy.
* This function shall be invoked at most once on this coroutine.
- *
- * First, this function initializes parent job from the `parentContext` of this coroutine that was passed to it
- * during construction. Second, it starts the coroutine based on [start] parameter:
- *
- * * [DEFAULT] uses [startCoroutineCancellable].
- * * [ATOMIC] uses [startCoroutine].
- * * [UNDISPATCHED] uses [startCoroutineUndispatched].
- * * [LAZY] does nothing.
- */
- public fun start(start: CoroutineStart, block: suspend () -> T) {
- initParentJob()
- start(block, this)
- }
-
- /**
- * Starts this coroutine with the given code [block] and [start] strategy.
- * This function shall be invoked at most once on this coroutine.
- *
- * First, this function initializes parent job from the `parentContext` of this coroutine that was passed to it
- * during construction. Second, it starts the coroutine based on [start] parameter:
*
* * [DEFAULT] uses [startCoroutineCancellable].
* * [ATOMIC] uses [startCoroutine].
@@ -154,7 +123,6 @@ public abstract class AbstractCoroutine<in T>(
* * [LAZY] does nothing.
*/
public fun <R> start(start: CoroutineStart, receiver: R, block: suspend R.() -> T) {
- initParentJob()
start(block, receiver, this)
}
}
diff --git a/kotlinx-coroutines-core/common/src/Annotations.kt b/kotlinx-coroutines-core/common/src/Annotations.kt
index 5475c6b1..724cc8cb 100644
--- a/kotlinx-coroutines-core/common/src/Annotations.kt
+++ b/kotlinx-coroutines-core/common/src/Annotations.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines
@@ -7,6 +7,22 @@ package kotlinx.coroutines
import kotlinx.coroutines.flow.*
/**
+ * Marks declarations in the coroutines that are **delicate** &mdash;
+ * they have limited use-case and shall be used with care in general code.
+ * Any use of a delicate declaration has to be carefully reviewed to make sure it is
+ * properly used and does not create problems like memory and resource leaks.
+ * Carefully read documentation of any declaration marked as `DelicateCoroutinesApi`.
+ */
+@MustBeDocumented
+@Retention(value = AnnotationRetention.BINARY)
+@RequiresOptIn(
+ level = RequiresOptIn.Level.WARNING,
+ message = "This is a delicate API and its use requires care." +
+ " Make sure you fully read and understand documentation of the declaration that is marked as a delicate API."
+)
+public annotation class DelicateCoroutinesApi
+
+/**
* Marks declarations that are still **experimental** in coroutines API, which means that the design of the
* corresponding declarations has open issues which may (or may not) lead to their changes in the future.
* Roughly speaking, there is a chance that those declarations will be deprecated in the near future or
@@ -59,7 +75,7 @@ public annotation class ObsoleteCoroutinesApi
@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION, AnnotationTarget.TYPEALIAS, AnnotationTarget.PROPERTY)
@RequiresOptIn(
level = RequiresOptIn.Level.ERROR, message = "This is an internal kotlinx.coroutines API that " +
- "should not be used from outside of kotlinx.coroutines. No compatibility guarantees are provided." +
+ "should not be used from outside of kotlinx.coroutines. No compatibility guarantees are provided. " +
"It is recommended to report your use-case of internal API to kotlinx.coroutines issue tracker, " +
"so stable API could be provided instead"
)
diff --git a/kotlinx-coroutines-core/common/src/Await.kt b/kotlinx-coroutines-core/common/src/Await.kt
index 71893490..e06ed330 100644
--- a/kotlinx-coroutines-core/common/src/Await.kt
+++ b/kotlinx-coroutines-core/common/src/Await.kt
@@ -1,11 +1,10 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines
import kotlinx.atomicfu.*
-import kotlinx.coroutines.channels.*
import kotlin.coroutines.*
/**
@@ -75,7 +74,7 @@ private class AwaitAll<T>(private val deferreds: Array<out Deferred<T>>) {
val nodes = Array(deferreds.size) { i ->
val deferred = deferreds[i]
deferred.start() // To properly await lazily started deferreds
- AwaitAllNode(cont, deferred).apply {
+ AwaitAllNode(cont).apply {
handle = deferred.invokeOnCompletion(asHandler)
}
}
@@ -101,7 +100,7 @@ private class AwaitAll<T>(private val deferreds: Array<out Deferred<T>>) {
override fun toString(): String = "DisposeHandlersOnCancel[$nodes]"
}
- private inner class AwaitAllNode(private val continuation: CancellableContinuation<List<T>>, job: Job) : JobNode<Job>(job) {
+ private inner class AwaitAllNode(private val continuation: CancellableContinuation<List<T>>) : JobNode() {
lateinit var handle: DisposableHandle
private val _disposer = atomic<DisposeHandlersOnCancel?>(null)
diff --git a/kotlinx-coroutines-core/common/src/Builders.common.kt b/kotlinx-coroutines-core/common/src/Builders.common.kt
index b7deaccb..a11ffe9e 100644
--- a/kotlinx-coroutines-core/common/src/Builders.common.kt
+++ b/kotlinx-coroutines-core/common/src/Builders.common.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
@file:JvmMultifileClass
@@ -96,7 +96,7 @@ public fun <T> CoroutineScope.async(
private open class DeferredCoroutine<T>(
parentContext: CoroutineContext,
active: Boolean
-) : AbstractCoroutine<T>(parentContext, active), Deferred<T>, SelectClause1<T> {
+) : AbstractCoroutine<T>(parentContext, true, active = active), Deferred<T>, SelectClause1<T> {
override fun getCompleted(): T = getCompletedInternal() as T
override suspend fun await(): T = awaitInternal() as T
override val onAwait: SelectClause1<T> get() = this
@@ -133,6 +133,10 @@ private class LazyDeferredCoroutine<T>(
* which means that if the original [coroutineContext], in which `withContext` was invoked,
* is cancelled by the time its dispatcher starts to execute the code,
* it discards the result of `withContext` and throws [CancellationException].
+ *
+ * The cancellation behaviour described above is enabled if and only if the dispatcher is being changed.
+ * For example, when using `withContext(NonCancellable) { ... }` there is no change in dispatcher and
+ * this call will not be cancelled neither on entry to the block inside `withContext` nor on exit from it.
*/
public suspend fun <T> withContext(
context: CoroutineContext,
@@ -146,7 +150,7 @@ public suspend fun <T> withContext(
val oldContext = uCont.context
val newContext = oldContext + context
// always check for cancellation of new context
- newContext.checkCompletion()
+ newContext.ensureActive()
// FAST PATH #1 -- new context is the same as the old one
if (newContext === oldContext) {
val coroutine = ScopeCoroutine(newContext, uCont)
@@ -163,7 +167,6 @@ public suspend fun <T> withContext(
}
// SLOW PATH -- use new dispatcher
val coroutine = DispatchedCoroutine(newContext, uCont)
- coroutine.initParentJob()
block.startCoroutineCancellable(coroutine, coroutine)
coroutine.getResult()
}
@@ -184,7 +187,7 @@ public suspend inline operator fun <T> CoroutineDispatcher.invoke(
private open class StandaloneCoroutine(
parentContext: CoroutineContext,
active: Boolean
-) : AbstractCoroutine<Unit>(parentContext, active) {
+) : AbstractCoroutine<Unit>(parentContext, initParentJob = true, active = active) {
override fun handleJobException(exception: Throwable): Boolean {
handleCoroutineException(context, exception)
return true
@@ -203,25 +206,17 @@ private class LazyStandaloneCoroutine(
}
// Used by withContext when context changes, but dispatcher stays the same
-private class UndispatchedCoroutine<in T>(
+internal expect class UndispatchedCoroutine<in T>(
context: CoroutineContext,
uCont: Continuation<T>
-) : ScopeCoroutine<T>(context, uCont) {
- override fun afterResume(state: Any?) {
- // resume undispatched -- update context by stay on the same dispatcher
- val result = recoverResult(state, uCont)
- withCoroutineContext(uCont.context, null) {
- uCont.resumeWith(result)
- }
- }
-}
+) : ScopeCoroutine<T>
private const val UNDECIDED = 0
private const val SUSPENDED = 1
private const val RESUMED = 2
// Used by withContext when context dispatcher changes
-private class DispatchedCoroutine<in T>(
+internal class DispatchedCoroutine<in T>(
context: CoroutineContext,
uCont: Continuation<T>
) : ScopeCoroutine<T>(context, uCont) {
diff --git a/kotlinx-coroutines-core/common/src/CancellableContinuation.kt b/kotlinx-coroutines-core/common/src/CancellableContinuation.kt
index 7d9315af..2c2f1b8f 100644
--- a/kotlinx-coroutines-core/common/src/CancellableContinuation.kt
+++ b/kotlinx-coroutines-core/common/src/CancellableContinuation.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines
@@ -81,6 +81,10 @@ public interface CancellableContinuation<in T> : Continuation<T> {
* Same as [tryResume] but with [onCancellation] handler that called if and only if the value is not
* delivered to the caller because of the dispatch in the process, so that atomicity delivery
* guaranteed can be provided by having a cancellation fallback.
+ *
+ * Implementation note: current implementation always returns RESUME_TOKEN or `null`
+ *
+ * @suppress **This is unstable API and it is subject to change.**
*/
@InternalCoroutinesApi
public fun tryResume(value: T, idempotent: Any?, onCancellation: ((cause: Throwable) -> Unit)?): Any?
@@ -104,8 +108,10 @@ public interface CancellableContinuation<in T> : Continuation<T> {
public fun completeResume(token: Any)
/**
- * Legacy function that turned on cancellation behavior in [suspendCancellableCoroutine] before kotlinx.coroutines 1.1.0.
- * This function does nothing and is left only for binary compatibility with old compiled code.
+ * Internal function that setups cancellation behavior in [suspendCancellableCoroutine].
+ * It's illegal to call this function in any non-`kotlinx.coroutines` code and
+ * such calls lead to undefined behaviour.
+ * Exposed in our ABI since 1.0.0 withing `suspendCancellableCoroutine` body.
*
* @suppress **This is unstable API and it is subject to change.**
*/
@@ -332,7 +338,7 @@ internal suspend inline fun <T> suspendCancellableCoroutineReusable(
internal fun <T> getOrCreateCancellableContinuation(delegate: Continuation<T>): CancellableContinuationImpl<T> {
// If used outside of our dispatcher
if (delegate !is DispatchedContinuation<T>) {
- return CancellableContinuationImpl(delegate, MODE_CANCELLABLE_REUSABLE)
+ return CancellableContinuationImpl(delegate, MODE_CANCELLABLE)
}
/*
* Attempt to claim reusable instance.
diff --git a/kotlinx-coroutines-core/common/src/CancellableContinuationImpl.kt b/kotlinx-coroutines-core/common/src/CancellableContinuationImpl.kt
index cdb1b788..1a0169b6 100644
--- a/kotlinx-coroutines-core/common/src/CancellableContinuationImpl.kt
+++ b/kotlinx-coroutines-core/common/src/CancellableContinuationImpl.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines
@@ -72,10 +72,7 @@ internal open class CancellableContinuationImpl<in T>(
*/
private val _state = atomic<Any?>(Active)
- private val _parentHandle = atomic<DisposableHandle?>(null)
- private var parentHandle: DisposableHandle?
- get() = _parentHandle.value
- set(value) { _parentHandle.value = value }
+ private var parentHandle: DisposableHandle? = null
internal val state: Any? get() = _state.value
@@ -85,11 +82,32 @@ internal open class CancellableContinuationImpl<in T>(
public override val isCancelled: Boolean get() = state is CancelledContinuation
+ // We cannot invoke `state.toString()` since it may cause a circular dependency
+ private val stateDebugRepresentation get() = when(state) {
+ is NotCompleted -> "Active"
+ is CancelledContinuation -> "Cancelled"
+ else -> "Completed"
+ }
+
public override fun initCancellability() {
- setupCancellation()
+ /*
+ * Invariant: at the moment of invocation, `this` has not yet
+ * leaked to user code and no one is able to invoke `resume` or `cancel`
+ * on it yet. Also, this function is not invoked for reusable continuations.
+ */
+ val handle = installParentHandle()
+ ?: return // fast path -- don't do anything without parent
+ // now check our state _after_ registering, could have completed while we were registering,
+ // but only if parent was cancelled. Parent could be in a "cancelling" state for a while,
+ // so we are helping it and cleaning the node ourselves
+ if (isCompleted) {
+ // Can be invoked concurrently in 'parentCancelled', no problems here
+ handle.dispose()
+ parentHandle = NonDisposableHandle
+ }
}
- private fun isReusable(): Boolean = delegate is DispatchedContinuation<*> && delegate.isReusable(this)
+ private fun isReusable(): Boolean = resumeMode.isReusableMode && (delegate as DispatchedContinuation<*>).isReusable()
/**
* Resets cancellability state in order to [suspendCancellableCoroutineReusable] to work.
@@ -97,7 +115,7 @@ internal open class CancellableContinuationImpl<in T>(
*/
@JvmName("resetStateReusable") // Prettier stack traces
internal fun resetStateReusable(): Boolean {
- assert { resumeMode == MODE_CANCELLABLE_REUSABLE } // invalid mode for CancellableContinuationImpl
+ assert { resumeMode == MODE_CANCELLABLE_REUSABLE }
assert { parentHandle !== NonDisposableHandle }
val state = _state.value
assert { state !is NotCompleted }
@@ -111,40 +129,6 @@ internal open class CancellableContinuationImpl<in T>(
return true
}
- /**
- * Setups parent cancellation and checks for postponed cancellation in the case of reusable continuations.
- * It is only invoked from an internal [getResult] function for reusable continuations
- * and from [suspendCancellableCoroutine] to establish a cancellation before registering CC anywhere.
- */
- private fun setupCancellation() {
- if (checkCompleted()) return
- if (parentHandle !== null) return // fast path 2 -- was already initialized
- val parent = delegate.context[Job] ?: return // fast path 3 -- don't do anything without parent
- val handle = parent.invokeOnCompletion(
- onCancelling = true,
- handler = ChildContinuation(parent, this).asHandler
- )
- parentHandle = handle
- // now check our state _after_ registering (could have completed while we were registering)
- // Also note that we do not dispose parent for reusable continuations, dispatcher will do that for us
- if (isCompleted && !isReusable()) {
- handle.dispose() // it is Ok to call dispose twice -- here and in disposeParentHandle
- parentHandle = NonDisposableHandle // release it just in case, to aid GC
- }
- }
-
- private fun checkCompleted(): Boolean {
- val completed = isCompleted
- if (!resumeMode.isReusableMode) return completed // Do not check postponed cancellation for non-reusable continuations
- val dispatched = delegate as? DispatchedContinuation<*> ?: return completed
- val cause = dispatched.checkPostponedCancellation(this) ?: return completed
- if (!completed) {
- // Note: this cancel may fail if one more concurrent cancel is currently being invoked
- cancel(cause)
- }
- return true
- }
-
public override val callerFrame: CoroutineStackFrame?
get() = delegate as? CoroutineStackFrame
@@ -180,8 +164,9 @@ internal open class CancellableContinuationImpl<in T>(
* Attempt to postpone cancellation for reusable cancellable continuation
*/
private fun cancelLater(cause: Throwable): Boolean {
- if (!resumeMode.isReusableMode) return false
- val dispatched = (delegate as? DispatchedContinuation<*>) ?: return false
+ // Ensure that we are postponing cancellation to the right reusable instance
+ if (!isReusable()) return false
+ val dispatched = delegate as DispatchedContinuation<*>
return dispatched.postponeCancellation(cause)
}
@@ -209,7 +194,7 @@ internal open class CancellableContinuationImpl<in T>(
private inline fun callCancelHandlerSafely(block: () -> Unit) {
try {
- block()
+ block()
} catch (ex: Throwable) {
// Handler should never fail, if it does -- it is an unhandled exception
handleCoroutineException(
@@ -269,9 +254,37 @@ internal open class CancellableContinuationImpl<in T>(
@PublishedApi
internal fun getResult(): Any? {
- setupCancellation()
- if (trySuspend()) return COROUTINE_SUSPENDED
+ val isReusable = isReusable()
+ // trySuspend may fail either if 'block' has resumed/cancelled a continuation
+ // or we got async cancellation from parent.
+ if (trySuspend()) {
+ /*
+ * Invariant: parentHandle is `null` *only* for reusable continuations.
+ * We were neither resumed nor cancelled, time to suspend.
+ * But first we have to install parent cancellation handle (if we didn't yet),
+ * so CC could be properly resumed on parent cancellation.
+ *
+ * This read has benign data-race with write of 'NonDisposableHandle'
+ * in 'detachChildIfNotReusable'.
+ */
+ if (parentHandle == null) {
+ installParentHandle()
+ }
+ /*
+ * Release the continuation after installing the handle (if needed).
+ * If we were successful, then do nothing, it's ok to reuse the instance now.
+ * Otherwise, dispose the handle by ourselves.
+ */
+ if (isReusable) {
+ releaseClaimedReusableContinuation()
+ }
+ return COROUTINE_SUSPENDED
+ }
// otherwise, onCompletionInternal was already invoked & invoked tryResume, and the result is in the state
+ if (isReusable) {
+ // release claimed reusable continuation for the future reuse
+ releaseClaimedReusableContinuation()
+ }
val state = this.state
if (state is CompletedExceptionally) throw recoverStackTrace(state.cause, this)
// if the parent job was already cancelled, then throw the corresponding cancellation exception
@@ -289,6 +302,28 @@ internal open class CancellableContinuationImpl<in T>(
return getSuccessfulResult(state)
}
+ private fun installParentHandle(): DisposableHandle? {
+ val parent = context[Job] ?: return null // don't do anything without a parent
+ // Install the handle
+ val handle = parent.invokeOnCompletion(
+ onCancelling = true,
+ handler = ChildContinuation(this).asHandler
+ )
+ parentHandle = handle
+ return handle
+ }
+
+ /**
+ * Tries to release reusable continuation. It can fail is there was an asynchronous cancellation,
+ * in which case it detaches from the parent and cancels this continuation.
+ */
+ private fun releaseClaimedReusableContinuation() {
+ // Cannot be casted if e.g. invoked from `installParentHandleReusable` for context without dispatchers, but with Job in it
+ val cancellationCause = (delegate as? DispatchedContinuation<*>)?.tryReleaseClaimedContinuation(this) ?: return
+ detachChild()
+ cancel(cancellationCause)
+ }
+
override fun resumeWith(result: Result<T>) =
resumeImpl(result.toState(this), resumeMode)
@@ -455,11 +490,10 @@ internal open class CancellableContinuationImpl<in T>(
/**
* Detaches from the parent.
- * Invariant: used from [CoroutineDispatcher.releaseInterceptedContinuation] iff [isReusable] is `true`
*/
internal fun detachChild() {
- val handle = parentHandle
- handle?.dispose()
+ val handle = parentHandle ?: return
+ handle.dispose()
parentHandle = NonDisposableHandle
}
@@ -503,7 +537,7 @@ internal open class CancellableContinuationImpl<in T>(
// For nicer debugging
public override fun toString(): String =
- "${nameString()}(${delegate.toDebugString()}){$state}@$hexAddress"
+ "${nameString()}(${delegate.toDebugString()}){$stateDebugRepresentation}@$hexAddress"
protected open fun nameString(): String =
"CancellableContinuation"
diff --git a/kotlinx-coroutines-core/common/src/CompletableDeferred.kt b/kotlinx-coroutines-core/common/src/CompletableDeferred.kt
index 2f008472..5e76593d 100644
--- a/kotlinx-coroutines-core/common/src/CompletableDeferred.kt
+++ b/kotlinx-coroutines-core/common/src/CompletableDeferred.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
@file:Suppress("DEPRECATION_ERROR")
@@ -80,7 +80,7 @@ public fun <T> CompletableDeferred(value: T): CompletableDeferred<T> = Completab
private class CompletableDeferredImpl<T>(
parent: Job?
) : JobSupport(true), CompletableDeferred<T>, SelectClause1<T> {
- init { initParentJobInternal(parent) }
+ init { initParentJob(parent) }
override val onCancelComplete get() = true
override fun getCompleted(): T = getCompletedInternal() as T
override suspend fun await(): T = awaitInternal() as T
diff --git a/kotlinx-coroutines-core/common/src/CompletableJob.kt b/kotlinx-coroutines-core/common/src/CompletableJob.kt
index 74a92e36..beafdaf2 100644
--- a/kotlinx-coroutines-core/common/src/CompletableJob.kt
+++ b/kotlinx-coroutines-core/common/src/CompletableJob.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines
@@ -21,7 +21,7 @@ public interface CompletableJob : Job {
*
* Subsequent invocations of this function have no effect and always produce `false`.
*
- * This function transitions this job into _completed- state if it was not completed or cancelled yet.
+ * This function transitions this job into _completed_ state if it was not completed or cancelled yet.
* However, that if this job has children, then it transitions into _completing_ state and becomes _complete_
* once all its children are [complete][isCompleted]. See [Job] for details.
*/
diff --git a/kotlinx-coroutines-core/common/src/CompletionHandler.common.kt b/kotlinx-coroutines-core/common/src/CompletionHandler.common.kt
index bf690008..e712ff1f 100644
--- a/kotlinx-coroutines-core/common/src/CompletionHandler.common.kt
+++ b/kotlinx-coroutines-core/common/src/CompletionHandler.common.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines
diff --git a/kotlinx-coroutines-core/common/src/CompletionState.kt b/kotlinx-coroutines-core/common/src/CompletionState.kt
index f09aa3cc..b9042874 100644
--- a/kotlinx-coroutines-core/common/src/CompletionState.kt
+++ b/kotlinx-coroutines-core/common/src/CompletionState.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines
diff --git a/kotlinx-coroutines-core/common/src/CoroutineContext.common.kt b/kotlinx-coroutines-core/common/src/CoroutineContext.common.kt
index 51374603..68b4b1a3 100644
--- a/kotlinx-coroutines-core/common/src/CoroutineContext.common.kt
+++ b/kotlinx-coroutines-core/common/src/CoroutineContext.common.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines
@@ -19,5 +19,6 @@ internal expect val DefaultDelay: Delay
// countOrElement -- pre-cached value for ThreadContext.kt
internal expect inline fun <T> withCoroutineContext(context: CoroutineContext, countOrElement: Any?, block: () -> T): T
+internal expect inline fun <T> withContinuationContext(continuation: Continuation<*>, countOrElement: Any?, block: () -> T): T
internal expect fun Continuation<*>.toDebugString(): String
internal expect val CoroutineContext.coroutineName: String?
diff --git a/kotlinx-coroutines-core/common/src/CoroutineDispatcher.kt b/kotlinx-coroutines-core/common/src/CoroutineDispatcher.kt
index ab1e814b..d5613d41 100644
--- a/kotlinx-coroutines-core/common/src/CoroutineDispatcher.kt
+++ b/kotlinx-coroutines-core/common/src/CoroutineDispatcher.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines
@@ -99,9 +99,13 @@ public abstract class CoroutineDispatcher :
public final override fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T> =
DispatchedContinuation(this, continuation)
- @InternalCoroutinesApi
- public override fun releaseInterceptedContinuation(continuation: Continuation<*>) {
- (continuation as DispatchedContinuation<*>).reusableCancellableContinuation?.detachChild()
+ public final override fun releaseInterceptedContinuation(continuation: Continuation<*>) {
+ /*
+ * Unconditional cast is safe here: we only return DispatchedContinuation from `interceptContinuation`,
+ * any ClassCastException can only indicate compiler bug
+ */
+ val dispatched = continuation as DispatchedContinuation<*>
+ dispatched.release()
}
/**
diff --git a/kotlinx-coroutines-core/common/src/CoroutineExceptionHandler.kt b/kotlinx-coroutines-core/common/src/CoroutineExceptionHandler.kt
index b49a6faa..49923a92 100644
--- a/kotlinx-coroutines-core/common/src/CoroutineExceptionHandler.kt
+++ b/kotlinx-coroutines-core/common/src/CoroutineExceptionHandler.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines
diff --git a/kotlinx-coroutines-core/common/src/CoroutineName.kt b/kotlinx-coroutines-core/common/src/CoroutineName.kt
index 7f09a589..dc3f1c0c 100644
--- a/kotlinx-coroutines-core/common/src/CoroutineName.kt
+++ b/kotlinx-coroutines-core/common/src/CoroutineName.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines
diff --git a/kotlinx-coroutines-core/common/src/CoroutineScope.kt b/kotlinx-coroutines-core/common/src/CoroutineScope.kt
index 0dde6c93..3ed233bf 100644
--- a/kotlinx-coroutines-core/common/src/CoroutineScope.kt
+++ b/kotlinx-coroutines-core/common/src/CoroutineScope.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
@file:OptIn(ExperimentalContracts::class)
@@ -16,7 +16,10 @@ import kotlin.coroutines.intrinsics.*
* is an extension on [CoroutineScope] and inherits its [coroutineContext][CoroutineScope.coroutineContext]
* to automatically propagate all its elements and cancellation.
*
- * The best ways to obtain a standalone instance of the scope are [CoroutineScope()] and [MainScope()] factory functions.
+ * The best ways to obtain a standalone instance of the scope are [CoroutineScope()] and [MainScope()] factory functions,
+ * taking care to cancel these coroutine scopes when they are no longer needed (see section on custom usage below for
+ * explanation and example).
+ *
* Additional context elements can be appended to the scope using the [plus][CoroutineScope.plus] operator.
*
* ### Convention for structured concurrency
@@ -38,12 +41,23 @@ import kotlin.coroutines.intrinsics.*
*
* ### Custom usage
*
- * [CoroutineScope] should be implemented or declared as a property on entities with a well-defined lifecycle that are
- * responsible for launching children coroutines, for example:
+ * `CoroutineScope` should be declared as a property on entities with a well-defined lifecycle that are
+ * responsible for launching children coroutines. The corresponding instance of `CoroutineScope` shall be created
+ * with either `CoroutineScope()` or `MainScope()` functions. The difference between them is only in the
+ * [CoroutineDispatcher]:
+ *
+ * * `CoroutineScope()` uses [Dispatchers.Default] for its coroutines.
+ * * `MainScope()` uses [Dispatchers.Main] for its coroutines.
+ *
+ * **The key part of custom usage of `CustomScope` is cancelling it at the end of the lifecycle.**
+ * The [CoroutineScope.cancel] extension function shall be used when the entity that was launching coroutines
+ * is no longer needed. It cancels all the coroutines that might still be running on behalf of it.
+ *
+ * For example:
*
* ```
* class MyUIClass {
- * val scope = MainScope() // the scope of MyUIClass
+ * val scope = MainScope() // the scope of MyUIClass, uses Dispatchers.Main
*
* fun destroy() { // destroys an instance of MyUIClass
* scope.cancel() // cancels all coroutines launched in this scope
@@ -124,25 +138,81 @@ public val CoroutineScope.isActive: Boolean
/**
* A global [CoroutineScope] not bound to any job.
- *
* Global scope is used to launch top-level coroutines which are operating on the whole application lifetime
* and are not cancelled prematurely.
- * Another use of the global scope is operators running in [Dispatchers.Unconfined], which don't have any job associated with them.
*
- * Application code usually should use an application-defined [CoroutineScope]. Using
- * [async][CoroutineScope.async] or [launch][CoroutineScope.launch]
- * on the instance of [GlobalScope] is highly discouraged.
+ * Active coroutines launched in `GlobalScope` do not keep the process alive. They are like daemon threads.
+ *
+ * This is a **delicate** API. It is easy to accidentally create resource or memory leaks when
+ * `GlobalScope` is used. A coroutine launched in `GlobalScope` is not subject to the principle of structured
+ * concurrency, so if it hangs or gets delayed due to a problem (e.g. due to a slow network), it will stay working
+ * and consuming resources. For example, consider the following code:
+ *
+ * ```
+ * fun loadConfiguration() {
+ * GlobalScope.launch {
+ * val config = fetchConfigFromServer() // network request
+ * updateConfiguration(config)
+ * }
+ * }
+ * ```
+ *
+ * A call to `loadConfiguration` creates a coroutine in the `GlobalScope` that works in background without any
+ * provision to cancel it or to wait for its completion. If a network is slow, it keeps waiting in background,
+ * consuming resources. Repeated calls to `loadConfiguration` will consume more and more resources.
+ *
+ * ### Possible replacements
+ *
+ * In many cases uses of `GlobalScope` should be removed, marking the containing operation with `suspend`, for example:
+ *
+ * ```
+ * suspend fun loadConfiguration() {
+ * val config = fetchConfigFromServer() // network request
+ * updateConfiguration(config)
+ * }
+ * ```
+ *
+ * In cases when `GlobalScope.launch` was used to launch multiple concurrent operations, the corresponding
+ * operations shall be grouped with [coroutineScope] instead:
+ *
+ * ```
+ * // concurrently load configuration and data
+ * suspend fun loadConfigurationAndData() {
+ * coroutinesScope {
+ * launch { loadConfiguration() }
+ * launch { loadData() }
+ * }
+ * }
+ * ```
+ *
+ * In top-level code, when launching a concurrent operation from a non-suspending context, an appropriately
+ * confined instance of [CoroutineScope] shall be used instead of a `GlobalScope`. See docs on [CoroutineScope] for
+ * details.
+ *
+ * ### GlobalScope vs custom scope
+ *
+ * Do not replace `GlobalScope.launch { ... }` with `CoroutineScope().launch { ... }` constructor function call.
+ * The latter has the same pitfalls as `GlobalScope`. See [CoroutineScope] documentation on the intended usage of
+ * `CoroutineScope()` constructor function.
+ *
+ * ### Legitimate use-cases
*
- * Usage of this interface may look like this:
+ * There are limited circumstances under which `GlobalScope` can be legitimately and safely used, such as top-level background
+ * processes that must stay active for the whole duration of the application's lifetime. Because of that, any use
+ * of `GlobalScope` requires an explicit opt-in with `@OptIn(DelicateCoroutinesApi::class)`, like this:
*
* ```
- * fun ReceiveChannel<Int>.sqrt(): ReceiveChannel<Double> = GlobalScope.produce(Dispatchers.Unconfined) {
- * for (number in this) {
- * send(Math.sqrt(number))
+ * // A global coroutine to log statistics every second, must be always active
+ * @OptIn(DelicateCoroutinesApi::class)
+ * val globalScopeReporter = GlobalScope.launch {
+ * while (true) {
+ * delay(1000)
+ * logStatistics()
* }
* }
* ```
*/
+@DelicateCoroutinesApi
public object GlobalScope : CoroutineScope {
/**
* Returns [EmptyCoroutineContext].
diff --git a/kotlinx-coroutines-core/common/src/CoroutineStart.kt b/kotlinx-coroutines-core/common/src/CoroutineStart.kt
index d5791c79..6059829c 100644
--- a/kotlinx-coroutines-core/common/src/CoroutineStart.kt
+++ b/kotlinx-coroutines-core/common/src/CoroutineStart.kt
@@ -1,7 +1,6 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
-@file:Suppress("NO_EXPLICIT_VISIBILITY_IN_API_MODE")
package kotlinx.coroutines
import kotlinx.coroutines.CoroutineStart.*
@@ -59,8 +58,8 @@ public enum class CoroutineStart {
ATOMIC,
/**
- * Immediately executes the coroutine until its first suspension point _in the current thread_ as if the
- * coroutine was started using [Dispatchers.Unconfined]. However, when the coroutine is resumed from suspension
+ * Immediately executes the coroutine until its first suspension point _in the current thread_ similarly to
+ * the coroutine being started using [Dispatchers.Unconfined]. However, when the coroutine is resumed from suspension
* it is dispatched according to the [CoroutineDispatcher] in its context.
*
* This is similar to [ATOMIC] in the sense that coroutine starts executing even if it was already cancelled,
@@ -69,9 +68,11 @@ public enum class CoroutineStart {
* Cancellability of coroutine at suspension points depends on the particular implementation details of
* suspending functions as in [DEFAULT].
*
- * **Note: This is an experimental api.** Execution semantics of coroutines may change in the future when this mode is used.
+ * ### Unconfined event loop
+ *
+ * Unlike [Dispatchers.Unconfined] and [MainCoroutineDispatcher.immediate], nested undispatched coroutines do not form
+ * an event loop that otherwise prevents potential stack overflow in case of unlimited nesting.
*/
- @ExperimentalCoroutinesApi // Since 1.0.0, no ETA on stability
UNDISPATCHED;
/**
diff --git a/kotlinx-coroutines-core/common/src/Debug.common.kt b/kotlinx-coroutines-core/common/src/Debug.common.kt
index 949b05c6..185ad295 100644
--- a/kotlinx-coroutines-core/common/src/Debug.common.kt
+++ b/kotlinx-coroutines-core/common/src/Debug.common.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines
@@ -32,9 +32,15 @@ public interface CopyableThrowable<T> where T : Throwable, T : CopyableThrowable
/**
* Creates a copy of the current instance.
+ *
* For better debuggability, it is recommended to use original exception as [cause][Throwable.cause] of the resulting one.
* Stacktrace of copied exception will be overwritten by stacktrace recovery machinery by [Throwable.setStackTrace] call.
* An exception can opt-out of copying by returning `null` from this function.
+ * Suppressed exceptions of the original exception should not be copied in order to avoid circular exceptions.
+ *
+ * This function is allowed to create a copy with a modified [message][Throwable.message], but it should be noted
+ * that the copy can be later recovered as well and message modification code should handle this situation correctly
+ * (e.g. by also storing the original message and checking it) to produce a human-readable result.
*/
public fun createCopy(): T?
}
diff --git a/kotlinx-coroutines-core/common/src/Deferred.kt b/kotlinx-coroutines-core/common/src/Deferred.kt
index ff996756..595700e2 100644
--- a/kotlinx-coroutines-core/common/src/Deferred.kt
+++ b/kotlinx-coroutines-core/common/src/Deferred.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines
diff --git a/kotlinx-coroutines-core/common/src/Delay.kt b/kotlinx-coroutines-core/common/src/Delay.kt
index aae623d5..4543c5dd 100644
--- a/kotlinx-coroutines-core/common/src/Delay.kt
+++ b/kotlinx-coroutines-core/common/src/Delay.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines
@@ -150,4 +150,4 @@ internal val CoroutineContext.delay: Delay get() = get(ContinuationInterceptor)
*/
@ExperimentalTime
internal fun Duration.toDelayMillis(): Long =
- if (this > Duration.ZERO) toLongMilliseconds().coerceAtLeast(1) else 0
+ if (this > Duration.ZERO) inWholeMilliseconds.coerceAtLeast(1) else 0
diff --git a/kotlinx-coroutines-core/common/src/Dispatchers.common.kt b/kotlinx-coroutines-core/common/src/Dispatchers.common.kt
index dba57abc..8681b182 100644
--- a/kotlinx-coroutines-core/common/src/Dispatchers.common.kt
+++ b/kotlinx-coroutines-core/common/src/Dispatchers.common.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines
diff --git a/kotlinx-coroutines-core/common/src/EventLoop.common.kt b/kotlinx-coroutines-core/common/src/EventLoop.common.kt
index 69ea9fe3..e6a57c92 100644
--- a/kotlinx-coroutines-core/common/src/EventLoop.common.kt
+++ b/kotlinx-coroutines-core/common/src/EventLoop.common.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines
diff --git a/kotlinx-coroutines-core/common/src/Exceptions.common.kt b/kotlinx-coroutines-core/common/src/Exceptions.common.kt
index 64f8911e..6d5442df 100644
--- a/kotlinx-coroutines-core/common/src/Exceptions.common.kt
+++ b/kotlinx-coroutines-core/common/src/Exceptions.common.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines
diff --git a/kotlinx-coroutines-core/common/src/Job.kt b/kotlinx-coroutines-core/common/src/Job.kt
index 2e05635a..9552153a 100644
--- a/kotlinx-coroutines-core/common/src/Job.kt
+++ b/kotlinx-coroutines-core/common/src/Job.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
@file:JvmMultifileClass
@@ -113,16 +113,7 @@ public interface Job : CoroutineContext.Element {
/**
* Key for [Job] instance in the coroutine context.
*/
- public companion object Key : CoroutineContext.Key<Job> {
- init {
- /*
- * Here we make sure that CoroutineExceptionHandler is always initialized in advance, so
- * that if a coroutine fails due to StackOverflowError we don't fail to report this error
- * trying to initialize CoroutineExceptionHandler
- */
- CoroutineExceptionHandler
- }
- }
+ public companion object Key : CoroutineContext.Key<Job>
// ------------ state query ------------
@@ -177,7 +168,7 @@ public interface Job : CoroutineContext.Element {
/**
* Starts coroutine related to this job (if any) if it was not started yet.
- * The result `true` if this invocation actually started coroutine or `false`
+ * The result is `true` if this invocation actually started coroutine or `false`
* if it was already started or completed.
*/
public fun start(): Boolean
@@ -217,11 +208,10 @@ public interface Job : CoroutineContext.Element {
* immediately cancels all its children.
* * Parent cannot complete until all its children are complete. Parent waits for all its children to
* complete in _completing_ or _cancelling_ state.
- * * Uncaught exception in a child, by default, cancels parent. In particular, this applies to
- * children created with [launch][CoroutineScope.launch] coroutine builder. Note that
- * [async][CoroutineScope.async] and other future-like
- * coroutine builders do not have uncaught exceptions by definition, since all their exceptions are
- * caught and are encapsulated in their result.
+ * * Uncaught exception in a child, by default, cancels parent. This applies even to
+ * children created with [async][CoroutineScope.async] and other future-like
+ * coroutine builders, even though their exceptions are caught and are encapsulated in their result.
+ * This default behavior can be overridden with [SupervisorJob].
*/
public val children: Sequence<Job>
@@ -262,9 +252,9 @@ public interface Job : CoroutineContext.Element {
* suspending function is invoked or while it is suspended, this function
* throws [CancellationException].
*
- * In particular, it means that a parent coroutine invoking `join` on a child coroutine that was started using
- * `launch(coroutineContext) { ... }` builder throws [CancellationException] if the child
- * had crashed, unless a non-standard [CoroutineExceptionHandler] is installed in the context.
+ * In particular, it means that a parent coroutine invoking `join` on a child coroutine throws
+ * [CancellationException] if the child had failed, since a failure of a child coroutine cancels parent by default,
+ * unless the child was launched from within [supervisorScope].
*
* This function can be used in [select] invocation with [onJoin] clause.
* Use [isCompleted] to check for a completion of this job without waiting.
@@ -467,6 +457,14 @@ public interface ParentJob : Job {
@InternalCoroutinesApi
@Deprecated(level = DeprecationLevel.ERROR, message = "This is internal API and may be removed in the future releases")
public interface ChildHandle : DisposableHandle {
+
+ /**
+ * Returns the parent of the current parent-child relationship.
+ * @suppress **This is unstable API and it is subject to change.**
+ */
+ @InternalCoroutinesApi
+ public val parent: Job?
+
/**
* Child is cancelling its parent by invoking this method.
* This method is invoked by the child twice. The first time child report its root cause as soon as possible,
@@ -490,7 +488,7 @@ public interface ChildHandle : DisposableHandle {
* ```
*/
internal fun Job.disposeOnCompletion(handle: DisposableHandle): DisposableHandle =
- invokeOnCompletion(handler = DisposeOnCompletion(this, handle).asHandler)
+ invokeOnCompletion(handler = DisposeOnCompletion(handle).asHandler)
/**
* Cancels the job and suspends the invoking coroutine until the cancelled job is complete.
@@ -500,9 +498,9 @@ internal fun Job.disposeOnCompletion(handle: DisposableHandle): DisposableHandle
* suspending function is invoked or while it is suspended, this function
* throws [CancellationException].
*
- * In particular, it means that a parent coroutine invoking `cancelAndJoin` on a child coroutine that was started using
- * `launch(coroutineContext) { ... }` builder throws [CancellationException] if the child
- * had crashed, unless a non-standard [CoroutineExceptionHandler] is installed in the context.
+ * In particular, it means that a parent coroutine invoking `cancelAndJoin` on a child coroutine throws
+ * [CancellationException] if the child had failed, since a failure of a child coroutine cancels parent by default,
+ * unless the child was launched from within [supervisorScope].
*
* This is a shortcut for the invocation of [cancel][Job.cancel] followed by [join][Job.join].
*/
@@ -660,6 +658,9 @@ private fun Throwable?.orCancellation(job: Job): Throwable = this ?: JobCancella
*/
@InternalCoroutinesApi
public object NonDisposableHandle : DisposableHandle, ChildHandle {
+
+ override val parent: Job? get() = null
+
/**
* Does not do anything.
* @suppress
diff --git a/kotlinx-coroutines-core/common/src/JobSupport.kt b/kotlinx-coroutines-core/common/src/JobSupport.kt
index 020d00a3..0a3dd234 100644
--- a/kotlinx-coroutines-core/common/src/JobSupport.kt
+++ b/kotlinx-coroutines-core/common/src/JobSupport.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
@file:Suppress("DEPRECATION_ERROR")
@@ -96,7 +96,7 @@ public open class JobSupport constructor(active: Boolean) : Job, ChildJob, Paren
~ waits for start
>> start / join / await invoked
## ACTIVE: state == EMPTY_ACTIVE | is JobNode | is NodeList
- + onStartInternal / onStart (lazy coroutine is started)
+ + onStart (lazy coroutine is started)
~ active coroutine is working (or scheduled to execution)
>> childCancelled / cancelImpl invoked
## CANCELLING: state is Finishing, state.rootCause != null
@@ -139,7 +139,7 @@ public open class JobSupport constructor(active: Boolean) : Job, ChildJob, Paren
* Initializes parent job.
* It shall be invoked at most once after construction after all other initialization.
*/
- internal fun initParentJobInternal(parent: Job?) {
+ protected fun initParentJob(parent: Job?) {
assert { parentHandle == null }
if (parent == null) {
parentHandle = NonDisposableHandle
@@ -287,7 +287,7 @@ public open class JobSupport constructor(active: Boolean) : Job, ChildJob, Paren
// fast-path method to finalize normally completed coroutines without children
// returns true if complete, and afterCompletion(update) shall be called
private fun tryFinalizeSimpleState(state: Incomplete, update: Any?): Boolean {
- assert { state is Empty || state is JobNode<*> } // only simple state without lists where children can concurrently add
+ assert { state is Empty || state is JobNode } // only simple state without lists where children can concurrently add
assert { update !is CompletedExceptionally } // only for normal completion
if (!_state.compareAndSet(state, update.boxIncomplete())) return false
onCancelling(null) // simple state is not a failure
@@ -313,7 +313,7 @@ public open class JobSupport constructor(active: Boolean) : Job, ChildJob, Paren
* 2) Invoke completion handlers: .join(), callbacks etc.
* It's important to invoke them only AFTER exception handling and everything else, see #208
*/
- if (state is JobNode<*>) { // SINGLE/SINGLE+ state -- one completion handler (common case)
+ if (state is JobNode) { // SINGLE/SINGLE+ state -- one completion handler (common case)
try {
state.invoke(cause)
} catch (ex: Throwable) {
@@ -327,7 +327,7 @@ public open class JobSupport constructor(active: Boolean) : Job, ChildJob, Paren
private fun notifyCancelling(list: NodeList, cause: Throwable) {
// first cancel our own children
onCancelling(cause)
- notifyHandlers<JobCancellingNode<*>>(list, cause)
+ notifyHandlers<JobCancellingNode>(list, cause)
// then cancel parent
cancelParent(cause) // tentative cancellation -- does not matter if there is no parent
}
@@ -359,9 +359,9 @@ public open class JobSupport constructor(active: Boolean) : Job, ChildJob, Paren
}
private fun NodeList.notifyCompletion(cause: Throwable?) =
- notifyHandlers<JobNode<*>>(this, cause)
+ notifyHandlers<JobNode>(this, cause)
- private inline fun <reified T: JobNode<*>> notifyHandlers(list: NodeList, cause: Throwable?) {
+ private inline fun <reified T: JobNode> notifyHandlers(list: NodeList, cause: Throwable?) {
var exception: Throwable? = null
list.forEach<T> { node ->
try {
@@ -393,12 +393,12 @@ public open class JobSupport constructor(active: Boolean) : Job, ChildJob, Paren
is Empty -> { // EMPTY_X state -- no completion handlers
if (state.isActive) return FALSE // already active
if (!_state.compareAndSet(state, EMPTY_ACTIVE)) return RETRY
- onStartInternal()
+ onStart()
return TRUE
}
is InactiveNodeList -> { // LIST state -- inactive with a list of completion handlers
if (!_state.compareAndSet(state, state.list)) return RETRY
- onStartInternal()
+ onStart()
return TRUE
}
else -> return FALSE // not a new state
@@ -409,7 +409,7 @@ public open class JobSupport constructor(active: Boolean) : Job, ChildJob, Paren
* Override to provide the actual [start] action.
* This function is invoked exactly once when non-active coroutine is [started][start].
*/
- internal open fun onStartInternal() {}
+ protected open fun onStart() {}
public final override fun getCancellationException(): CancellationException =
when (val state = this.state) {
@@ -453,13 +453,14 @@ public open class JobSupport constructor(active: Boolean) : Job, ChildJob, Paren
invokeImmediately: Boolean,
handler: CompletionHandler
): DisposableHandle {
- var nodeCache: JobNode<*>? = null
+ // Create node upfront -- for common cases it just initializes JobNode.job field,
+ // for user-defined handlers it allocates a JobNode object that we might not need, but this is Ok.
+ val node: JobNode = makeNode(handler, onCancelling)
loopOnState { state ->
when (state) {
is Empty -> { // EMPTY_X state -- no completion handlers
if (state.isActive) {
// try move to SINGLE state
- val node = nodeCache ?: makeNode(handler, onCancelling).also { nodeCache = it }
if (_state.compareAndSet(state, node)) return node
} else
promoteEmptyToNodeList(state) // that way we can add listener for non-active coroutine
@@ -467,7 +468,7 @@ public open class JobSupport constructor(active: Boolean) : Job, ChildJob, Paren
is Incomplete -> {
val list = state.list
if (list == null) { // SINGLE/SINGLE+
- promoteSingleToNodeList(state as JobNode<*>)
+ promoteSingleToNodeList(state as JobNode)
} else {
var rootCause: Throwable? = null
var handle: DisposableHandle = NonDisposableHandle
@@ -479,7 +480,6 @@ public open class JobSupport constructor(active: Boolean) : Job, ChildJob, Paren
// or we are adding a child to a coroutine that is not completing yet
if (rootCause == null || handler.isHandlerOf<ChildHandleNode>() && !state.isCompleting) {
// Note: add node the list while holding lock on state (make sure it cannot change)
- val node = nodeCache ?: makeNode(handler, onCancelling).also { nodeCache = it }
if (!addLastAtomic(state, list, node)) return@loopOnState // retry
// just return node if we don't have to invoke handler (not cancelling yet)
if (rootCause == null) return node
@@ -493,7 +493,6 @@ public open class JobSupport constructor(active: Boolean) : Job, ChildJob, Paren
if (invokeImmediately) handler.invokeIt(rootCause)
return handle
} else {
- val node = nodeCache ?: makeNode(handler, onCancelling).also { nodeCache = it }
if (addLastAtomic(state, list, node)) return node
}
}
@@ -508,16 +507,20 @@ public open class JobSupport constructor(active: Boolean) : Job, ChildJob, Paren
}
}
- private fun makeNode(handler: CompletionHandler, onCancelling: Boolean): JobNode<*> {
- return if (onCancelling)
- (handler as? JobCancellingNode<*>)?.also { assert { it.job === this } }
- ?: InvokeOnCancelling(this, handler)
- else
- (handler as? JobNode<*>)?.also { assert { it.job === this && it !is JobCancellingNode } }
- ?: InvokeOnCompletion(this, handler)
+ private fun makeNode(handler: CompletionHandler, onCancelling: Boolean): JobNode {
+ val node = if (onCancelling) {
+ (handler as? JobCancellingNode)
+ ?: InvokeOnCancelling(handler)
+ } else {
+ (handler as? JobNode)
+ ?.also { assert { it !is JobCancellingNode } }
+ ?: InvokeOnCompletion(handler)
+ }
+ node.job = this
+ return node
}
- private fun addLastAtomic(expect: Any, list: NodeList, node: JobNode<*>) =
+ private fun addLastAtomic(expect: Any, list: NodeList, node: JobNode) =
list.addLastIf(node) { this.state === expect }
private fun promoteEmptyToNodeList(state: Empty) {
@@ -527,7 +530,7 @@ public open class JobSupport constructor(active: Boolean) : Job, ChildJob, Paren
_state.compareAndSet(state, update)
}
- private fun promoteSingleToNodeList(state: JobNode<*>) {
+ private fun promoteSingleToNodeList(state: JobNode) {
// try to promote it to list (SINGLE+ state)
state.addOneIfEmpty(NodeList())
// it must be in SINGLE+ state or state has changed (node could have need removed from state)
@@ -538,7 +541,7 @@ public open class JobSupport constructor(active: Boolean) : Job, ChildJob, Paren
public final override suspend fun join() {
if (!joinInternal()) { // fast-path no wait
- coroutineContext.checkCompletion()
+ coroutineContext.ensureActive()
return // do not suspend
}
return joinSuspend() // slow-path wait
@@ -553,7 +556,7 @@ public open class JobSupport constructor(active: Boolean) : Job, ChildJob, Paren
private suspend fun joinSuspend() = suspendCancellableCoroutine<Unit> { cont ->
// We have to invoke join() handler only on cancellation, on completion we will be resumed regularly without handlers
- cont.disposeOnCancellation(invokeOnCompletion(handler = ResumeOnCompletion(this, cont).asHandler))
+ cont.disposeOnCancellation(invokeOnCompletion(handler = ResumeOnCompletion(cont).asHandler))
}
public final override val onJoin: SelectClause0
@@ -573,7 +576,7 @@ public open class JobSupport constructor(active: Boolean) : Job, ChildJob, Paren
}
if (startInternal(state) == 0) {
// slow-path -- register waiter for completion
- select.disposeOnSelect(invokeOnCompletion(handler = SelectJoinOnCompletion(this, select, block).asHandler))
+ select.disposeOnSelect(invokeOnCompletion(handler = SelectJoinOnCompletion(select, block).asHandler))
return
}
}
@@ -582,11 +585,11 @@ public open class JobSupport constructor(active: Boolean) : Job, ChildJob, Paren
/**
* @suppress **This is unstable API and it is subject to change.**
*/
- internal fun removeNode(node: JobNode<*>) {
+ internal fun removeNode(node: JobNode) {
// remove logic depends on the state of the job
loopOnState { state ->
when (state) {
- is JobNode<*> -> { // SINGE/SINGLE+ state -- one completion handler
+ is JobNode -> { // SINGE/SINGLE+ state -- one completion handler
if (state !== node) return // a different job node --> we were already removed
// try remove and revert back to empty state
if (_state.compareAndSet(state, EMPTY_ACTIVE)) return
@@ -770,7 +773,7 @@ public open class JobSupport constructor(active: Boolean) : Job, ChildJob, Paren
private fun getOrPromoteCancellingList(state: Incomplete): NodeList? = state.list ?:
when (state) {
is Empty -> NodeList() // we can allocate new empty list that'll get integrated into Cancelling state
- is JobNode<*> -> {
+ is JobNode -> {
// SINGLE/SINGLE+ must be promoted to NodeList first, because otherwise we cannot
// correctly capture a reference to it
promoteSingleToNodeList(state)
@@ -849,7 +852,7 @@ public open class JobSupport constructor(active: Boolean) : Job, ChildJob, Paren
* Otherwise, there can be a race between (completed state -> handled exception and newly attached child/join)
* which may miss unhandled exception.
*/
- if ((state is Empty || state is JobNode<*>) && state !is ChildHandleNode && proposedUpdate !is CompletedExceptionally) {
+ if ((state is Empty || state is JobNode) && state !is ChildHandleNode && proposedUpdate !is CompletedExceptionally) {
if (tryFinalizeSimpleState(state, proposedUpdate)) {
// Completed successfully on fast path -- return updated state
return proposedUpdate
@@ -964,7 +967,7 @@ public open class JobSupport constructor(active: Boolean) : Job, ChildJob, Paren
* If child is attached when the job is already being cancelled, such child will receive immediate notification on
* cancellation, but parent *will* wait for that child before completion and will handle its exception.
*/
- return invokeOnCompletion(onCancelling = true, handler = ChildHandleNode(this, child).asHandler) as ChildHandle
+ return invokeOnCompletion(onCancelling = true, handler = ChildHandleNode(child).asHandler) as ChildHandle
}
/**
@@ -1147,12 +1150,10 @@ public open class JobSupport constructor(active: Boolean) : Job, ChildJob, Paren
private val state: Finishing,
private val child: ChildHandleNode,
private val proposedUpdate: Any?
- ) : JobNode<Job>(child.childJob) {
+ ) : JobNode() {
override fun invoke(cause: Throwable?) {
parent.continueCompleting(state, child, proposedUpdate)
}
- override fun toString(): String =
- "ChildCompletion[$child, $proposedUpdate]"
}
private class AwaitContinuation<T>(
@@ -1227,7 +1228,9 @@ public open class JobSupport constructor(active: Boolean) : Job, ChildJob, Paren
* thrown and not a JobCancellationException.
*/
val cont = AwaitContinuation(uCont.intercepted(), this)
- cont.disposeOnCancellation(invokeOnCompletion(ResumeAwaitOnCompletion(this, cont).asHandler))
+ // we are mimicking suspendCancellableCoroutine here and call initCancellability, too.
+ cont.initCancellability()
+ cont.disposeOnCancellation(invokeOnCompletion(ResumeAwaitOnCompletion(cont).asHandler))
cont.getResult()
}
@@ -1254,7 +1257,7 @@ public open class JobSupport constructor(active: Boolean) : Job, ChildJob, Paren
}
if (startInternal(state) == 0) {
// slow-path -- register waiter for completion
- select.disposeOnSelect(invokeOnCompletion(handler = SelectAwaitOnCompletion(this, select, block).asHandler))
+ select.disposeOnSelect(invokeOnCompletion(handler = SelectAwaitOnCompletion(select, block).asHandler))
return
}
}
@@ -1310,7 +1313,7 @@ private class Empty(override val isActive: Boolean) : Incomplete {
}
internal open class JobImpl(parent: Job?) : JobSupport(true), CompletableJob {
- init { initParentJobInternal(parent) }
+ init { initParentJob(parent) }
override val onCancelComplete get() = true
/*
* Check whether parent is able to handle exceptions as well.
@@ -1344,12 +1347,15 @@ internal interface Incomplete {
val list: NodeList? // is null only for Empty and JobNode incomplete state objects
}
-internal abstract class JobNode<out J : Job>(
- @JvmField val job: J
-) : CompletionHandlerBase(), DisposableHandle, Incomplete {
+internal abstract class JobNode : CompletionHandlerBase(), DisposableHandle, Incomplete {
+ /**
+ * Initialized by [JobSupport.makeNode].
+ */
+ lateinit var job: JobSupport
override val isActive: Boolean get() = true
override val list: NodeList? get() = null
- override fun dispose() = (job as JobSupport).removeNode(this)
+ override fun dispose() = job.removeNode(this)
+ override fun toString() = "$classSimpleName@$hexAddress[job@${job.hexAddress}]"
}
internal class NodeList : LockFreeLinkedListHead(), Incomplete {
@@ -1361,7 +1367,7 @@ internal class NodeList : LockFreeLinkedListHead(), Incomplete {
append(state)
append("}[")
var first = true
- this@NodeList.forEach<JobNode<*>> { node ->
+ this@NodeList.forEach<JobNode> { node ->
if (first) first = false else append(", ")
append(node)
}
@@ -1380,25 +1386,20 @@ internal class InactiveNodeList(
}
private class InvokeOnCompletion(
- job: Job,
private val handler: CompletionHandler
-) : JobNode<Job>(job) {
+) : JobNode() {
override fun invoke(cause: Throwable?) = handler.invoke(cause)
- override fun toString() = "InvokeOnCompletion[$classSimpleName@$hexAddress]"
}
private class ResumeOnCompletion(
- job: Job,
private val continuation: Continuation<Unit>
-) : JobNode<Job>(job) {
+) : JobNode() {
override fun invoke(cause: Throwable?) = continuation.resume(Unit)
- override fun toString() = "ResumeOnCompletion[$continuation]"
}
private class ResumeAwaitOnCompletion<T>(
- job: JobSupport,
private val continuation: CancellableContinuationImpl<T>
-) : JobNode<JobSupport>(job) {
+) : JobNode() {
override fun invoke(cause: Throwable?) {
val state = job.state
assert { state !is Incomplete }
@@ -1411,39 +1412,32 @@ private class ResumeAwaitOnCompletion<T>(
continuation.resume(state.unboxState() as T)
}
}
- override fun toString() = "ResumeAwaitOnCompletion[$continuation]"
}
internal class DisposeOnCompletion(
- job: Job,
private val handle: DisposableHandle
-) : JobNode<Job>(job) {
+) : JobNode() {
override fun invoke(cause: Throwable?) = handle.dispose()
- override fun toString(): String = "DisposeOnCompletion[$handle]"
}
private class SelectJoinOnCompletion<R>(
- job: JobSupport,
private val select: SelectInstance<R>,
private val block: suspend () -> R
-) : JobNode<JobSupport>(job) {
+) : JobNode() {
override fun invoke(cause: Throwable?) {
if (select.trySelect())
block.startCoroutineCancellable(select.completion)
}
- override fun toString(): String = "SelectJoinOnCompletion[$select]"
}
private class SelectAwaitOnCompletion<T, R>(
- job: JobSupport,
private val select: SelectInstance<R>,
private val block: suspend (T) -> R
-) : JobNode<JobSupport>(job) {
+) : JobNode() {
override fun invoke(cause: Throwable?) {
if (select.trySelect())
job.selectAwaitCompletion(select, block)
}
- override fun toString(): String = "SelectAwaitOnCompletion[$select]"
}
// -------- invokeOnCancellation nodes
@@ -1452,38 +1446,32 @@ private class SelectAwaitOnCompletion<T, R>(
* Marker for node that shall be invoked on in _cancelling_ state.
* **Note: may be invoked multiple times.**
*/
-internal abstract class JobCancellingNode<out J : Job>(job: J) : JobNode<J>(job)
+internal abstract class JobCancellingNode : JobNode()
private class InvokeOnCancelling(
- job: Job,
private val handler: CompletionHandler
-) : JobCancellingNode<Job>(job) {
+) : JobCancellingNode() {
// delegate handler shall be invoked at most once, so here is an additional flag
private val _invoked = atomic(0) // todo: replace with atomic boolean after migration to recent atomicFu
override fun invoke(cause: Throwable?) {
if (_invoked.compareAndSet(0, 1)) handler.invoke(cause)
}
- override fun toString() = "InvokeOnCancelling[$classSimpleName@$hexAddress]"
}
internal class ChildHandleNode(
- parent: JobSupport,
@JvmField val childJob: ChildJob
-) : JobCancellingNode<JobSupport>(parent), ChildHandle {
+) : JobCancellingNode(), ChildHandle {
+ override val parent: Job get() = job
override fun invoke(cause: Throwable?) = childJob.parentCancelled(job)
override fun childCancelled(cause: Throwable): Boolean = job.childCancelled(cause)
- override fun toString(): String = "ChildHandle[$childJob]"
}
// Same as ChildHandleNode, but for cancellable continuation
internal class ChildContinuation(
- parent: Job,
@JvmField val child: CancellableContinuationImpl<*>
-) : JobCancellingNode<Job>(parent) {
+) : JobCancellingNode() {
override fun invoke(cause: Throwable?) {
child.parentCancelled(child.getContinuationCancellationCause(job))
}
- override fun toString(): String =
- "ChildContinuation[$child]"
}
diff --git a/kotlinx-coroutines-core/common/src/MainCoroutineDispatcher.kt b/kotlinx-coroutines-core/common/src/MainCoroutineDispatcher.kt
index daba38f0..602da6e0 100644
--- a/kotlinx-coroutines-core/common/src/MainCoroutineDispatcher.kt
+++ b/kotlinx-coroutines-core/common/src/MainCoroutineDispatcher.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines
diff --git a/kotlinx-coroutines-core/common/src/NonCancellable.kt b/kotlinx-coroutines-core/common/src/NonCancellable.kt
index 45803cf9..c2781092 100644
--- a/kotlinx-coroutines-core/common/src/NonCancellable.kt
+++ b/kotlinx-coroutines-core/common/src/NonCancellable.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
@file:Suppress("DEPRECATION_ERROR")
@@ -18,41 +18,51 @@ import kotlin.coroutines.*
* // this code will not be cancelled
* }
* ```
+ *
+ * **WARNING**: This object is not designed to be used with [launch], [async], and other coroutine builders.
+ * if you write `launch(NonCancellable) { ... }` then not only the newly launched job will not be cancelled
+ * when the parent is cancelled, the whole parent-child relation between parent and child is severed.
+ * The parent will not wait for the child's completion, nor will be cancelled when the child crashed.
*/
+@Suppress("DeprecatedCallableAddReplaceWith")
public object NonCancellable : AbstractCoroutineContextElement(Job), Job {
+
+ private const val message = "NonCancellable can be used only as an argument for 'withContext', direct usages of its API are prohibited"
+
/**
* Always returns `true`.
* @suppress **This an internal API and should not be used from general code.**
*/
- @InternalCoroutinesApi
- override val isActive: Boolean get() = true
+ @Deprecated(level = DeprecationLevel.WARNING, message = message)
+ override val isActive: Boolean
+ get() = true
/**
* Always returns `false`.
* @suppress **This an internal API and should not be used from general code.**
*/
- @InternalCoroutinesApi
+ @Deprecated(level = DeprecationLevel.WARNING, message = message)
override val isCompleted: Boolean get() = false
/**
* Always returns `false`.
* @suppress **This an internal API and should not be used from general code.**
*/
- @InternalCoroutinesApi
+ @Deprecated(level = DeprecationLevel.WARNING, message = message)
override val isCancelled: Boolean get() = false
/**
* Always returns `false`.
* @suppress **This an internal API and should not be used from general code.**
*/
- @InternalCoroutinesApi
+ @Deprecated(level = DeprecationLevel.WARNING, message = message)
override fun start(): Boolean = false
/**
* Always throws [UnsupportedOperationException].
* @suppress **This an internal API and should not be used from general code.**
*/
- @InternalCoroutinesApi
+ @Deprecated(level = DeprecationLevel.WARNING, message = message)
override suspend fun join() {
throw UnsupportedOperationException("This job is always active")
}
@@ -61,6 +71,7 @@ public object NonCancellable : AbstractCoroutineContextElement(Job), Job {
* Always throws [UnsupportedOperationException].
* @suppress **This an internal API and should not be used from general code.**
*/
+ @Deprecated(level = DeprecationLevel.WARNING, message = message)
override val onJoin: SelectClause0
get() = throw UnsupportedOperationException("This job is always active")
@@ -68,14 +79,13 @@ public object NonCancellable : AbstractCoroutineContextElement(Job), Job {
* Always throws [IllegalStateException].
* @suppress **This an internal API and should not be used from general code.**
*/
- @InternalCoroutinesApi
+ @Deprecated(level = DeprecationLevel.WARNING, message = message)
override fun getCancellationException(): CancellationException = throw IllegalStateException("This job is always active")
/**
* @suppress **This an internal API and should not be used from general code.**
*/
- @Suppress("OverridingDeprecatedMember")
- @InternalCoroutinesApi
+ @Deprecated(level = DeprecationLevel.WARNING, message = message)
override fun invokeOnCompletion(handler: CompletionHandler): DisposableHandle =
NonDisposableHandle
@@ -83,7 +93,7 @@ public object NonCancellable : AbstractCoroutineContextElement(Job), Job {
* Always returns no-op handle.
* @suppress **This an internal API and should not be used from general code.**
*/
- @InternalCoroutinesApi
+ @Deprecated(level = DeprecationLevel.WARNING, message = message)
override fun invokeOnCompletion(onCancelling: Boolean, invokeImmediately: Boolean, handler: CompletionHandler): DisposableHandle =
NonDisposableHandle
@@ -91,7 +101,7 @@ public object NonCancellable : AbstractCoroutineContextElement(Job), Job {
* Does nothing.
* @suppress **This an internal API and should not be used from general code.**
*/
- @InternalCoroutinesApi
+ @Deprecated(level = DeprecationLevel.WARNING, message = message)
override fun cancel(cause: CancellationException?) {}
/**
@@ -105,7 +115,7 @@ public object NonCancellable : AbstractCoroutineContextElement(Job), Job {
* Always returns [emptySequence].
* @suppress **This an internal API and should not be used from general code.**
*/
- @InternalCoroutinesApi
+ @Deprecated(level = DeprecationLevel.WARNING, message = message)
override val children: Sequence<Job>
get() = emptySequence()
@@ -113,7 +123,7 @@ public object NonCancellable : AbstractCoroutineContextElement(Job), Job {
* Always returns [NonDisposableHandle] and does not do anything.
* @suppress **This an internal API and should not be used from general code.**
*/
- @InternalCoroutinesApi
+ @Deprecated(level = DeprecationLevel.WARNING, message = message)
override fun attachChild(child: ChildJob): ChildHandle = NonDisposableHandle
/** @suppress */
diff --git a/kotlinx-coroutines-core/common/src/Runnable.common.kt b/kotlinx-coroutines-core/common/src/Runnable.common.kt
index 692c700b..5a7f6678 100644
--- a/kotlinx-coroutines-core/common/src/Runnable.common.kt
+++ b/kotlinx-coroutines-core/common/src/Runnable.common.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines
diff --git a/kotlinx-coroutines-core/common/src/SchedulerTask.common.kt b/kotlinx-coroutines-core/common/src/SchedulerTask.common.kt
index dbdf45ea..45e2542b 100644
--- a/kotlinx-coroutines-core/common/src/SchedulerTask.common.kt
+++ b/kotlinx-coroutines-core/common/src/SchedulerTask.common.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines
diff --git a/kotlinx-coroutines-core/common/src/Supervisor.kt b/kotlinx-coroutines-core/common/src/Supervisor.kt
index 542e4fef..8411c5c6 100644
--- a/kotlinx-coroutines-core/common/src/Supervisor.kt
+++ b/kotlinx-coroutines-core/common/src/Supervisor.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
@file:OptIn(ExperimentalContracts::class)
@file:Suppress("DEPRECATION_ERROR")
@@ -42,11 +42,15 @@ public fun SupervisorJob0(parent: Job? = null) : Job = SupervisorJob(parent)
* Creates a [CoroutineScope] with [SupervisorJob] and calls the specified suspend block with this scope.
* The provided scope inherits its [coroutineContext][CoroutineScope.coroutineContext] from the outer scope, but overrides
* context's [Job] with [SupervisorJob].
+ * This function returns as soon as the given block and all its child coroutines are completed.
*
- * A failure of a child does not cause this scope to fail and does not affect its other children,
- * so a custom policy for handling failures of its children can be implemented. See [SupervisorJob] for details.
- * A failure of the scope itself (exception thrown in the [block] or cancellation) fails the scope with all its children,
+ * Unlike [coroutineScope], a failure of a child does not cause this scope to fail and does not affect its other children,
+ * so a custom policy for handling failures of its children can be implemented. See [SupervisorJob] for additional details.
+ * A failure of the scope itself (exception thrown in the [block] or external cancellation) fails the scope with all its children,
* but does not cancel parent job.
+ *
+ * The method may throw a [CancellationException] if the current job was cancelled externally,
+ * or rethrow an exception thrown by the given [block].
*/
public suspend fun <R> supervisorScope(block: suspend CoroutineScope.() -> R): R {
contract {
diff --git a/kotlinx-coroutines-core/common/src/Timeout.kt b/kotlinx-coroutines-core/common/src/Timeout.kt
index 4bfff118..264a2b9d 100644
--- a/kotlinx-coroutines-core/common/src/Timeout.kt
+++ b/kotlinx-coroutines-core/common/src/Timeout.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
@file:OptIn(ExperimentalContracts::class)
diff --git a/kotlinx-coroutines-core/common/src/Unconfined.kt b/kotlinx-coroutines-core/common/src/Unconfined.kt
index a0997a51..4f486458 100644
--- a/kotlinx-coroutines-core/common/src/Unconfined.kt
+++ b/kotlinx-coroutines-core/common/src/Unconfined.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines
diff --git a/kotlinx-coroutines-core/common/src/Yield.kt b/kotlinx-coroutines-core/common/src/Yield.kt
index 0d8bd3bc..98e21041 100644
--- a/kotlinx-coroutines-core/common/src/Yield.kt
+++ b/kotlinx-coroutines-core/common/src/Yield.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines
@@ -9,7 +9,8 @@ import kotlin.coroutines.*
import kotlin.coroutines.intrinsics.*
/**
- * Yields the thread (or thread pool) of the current coroutine dispatcher to other coroutines to run if possible.
+ * Yields the thread (or thread pool) of the current coroutine dispatcher
+ * to other coroutines on the same dispatcher to run if possible.
*
* This suspending function is cancellable.
* If the [Job] of the current coroutine is cancelled or completed when this suspending function is invoked or while
@@ -29,7 +30,7 @@ import kotlin.coroutines.intrinsics.*
*/
public suspend fun yield(): Unit = suspendCoroutineUninterceptedOrReturn sc@ { uCont ->
val context = uCont.context
- context.checkCompletion()
+ context.ensureActive()
val cont = uCont.intercepted() as? DispatchedContinuation<Unit> ?: return@sc Unit
if (cont.dispatcher.isDispatchNeeded(context)) {
// this is a regular dispatcher -- do simple dispatchYield
@@ -49,8 +50,3 @@ public suspend fun yield(): Unit = suspendCoroutineUninterceptedOrReturn sc@ { u
}
COROUTINE_SUSPENDED
}
-
-internal fun CoroutineContext.checkCompletion() {
- val job = get(Job)
- if (job != null && !job.isActive) throw job.getCancellationException()
-}
diff --git a/kotlinx-coroutines-core/common/src/channels/AbstractChannel.kt b/kotlinx-coroutines-core/common/src/channels/AbstractChannel.kt
index 8edd2b31..4751296c 100644
--- a/kotlinx-coroutines-core/common/src/channels/AbstractChannel.kt
+++ b/kotlinx-coroutines-core/common/src/channels/AbstractChannel.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.channels
@@ -127,8 +127,7 @@ internal abstract class AbstractSendChannel<E>(
// ------ SendChannel ------
public final override val isClosedForSend: Boolean get() = closedForSend != null
- public override val isFull: Boolean get() = isFullImpl
- protected val isFullImpl: Boolean get() = queue.nextNode !is ReceiveOrClosed<*> && isBufferFull
+ private val isFullImpl: Boolean get() = queue.nextNode !is ReceiveOrClosed<*> && isBufferFull
public final override suspend fun send(element: E) {
// fast path -- try offer non-blocking
@@ -137,23 +136,44 @@ internal abstract class AbstractSendChannel<E>(
return sendSuspend(element)
}
- public final override fun offer(element: E): Boolean {
+ @Suppress("DEPRECATION")
+ override fun offer(element: E): Boolean {
+ // Temporary migration for offer users who rely on onUndeliveredElement
+ try {
+ return super.offer(element)
+ } catch (e: Throwable) {
+ onUndeliveredElement?.callUndeliveredElementCatchingException(element)?.let {
+ // If it crashes, add send exception as suppressed for better diagnostics
+ it.addSuppressed(e)
+ throw it
+ }
+ throw e
+ }
+ }
+
+ public final override fun trySend(element: E): ChannelResult<Unit> {
val result = offerInternal(element)
return when {
- result === OFFER_SUCCESS -> true
+ result === OFFER_SUCCESS -> ChannelResult.success(Unit)
result === OFFER_FAILED -> {
- // We should check for closed token on offer as well, otherwise offer won't be linearizable
+ // We should check for closed token on trySend as well, otherwise trySend won't be linearizable
// in the face of concurrent close()
// See https://github.com/Kotlin/kotlinx.coroutines/issues/359
- throw recoverStackTrace(helpCloseAndGetSendException(element, closedForSend ?: return false))
+ val closedForSend = closedForSend ?: return ChannelResult.failure()
+ ChannelResult.closed(helpCloseAndGetSendException(closedForSend))
}
result is Closed<*> -> {
- throw recoverStackTrace(helpCloseAndGetSendException(element, result))
+ ChannelResult.closed(helpCloseAndGetSendException(result))
}
- else -> error("offerInternal returned $result")
+ else -> error("trySend returned $result")
}
}
+ private fun helpCloseAndGetSendException(closed: Closed<*>): Throwable {
+ helpClose(closed)
+ return closed.sendException
+ }
+
private fun helpCloseAndGetSendException(element: E, closed: Closed<*>): Throwable {
// To ensure linearizablity we must ALWAYS help close the channel when we observe that it was closed
// See https://github.com/Kotlin/kotlinx.coroutines/issues/1419
@@ -477,7 +497,14 @@ internal abstract class AbstractSendChannel<E>(
override val pollResult: Any? get() = element
override fun tryResumeSend(otherOp: PrepareOp?): Symbol? = RESUME_TOKEN.also { otherOp?.finishPrepare() }
override fun completeResumeSend() {}
- override fun resumeSendClosed(closed: Closed<*>) {}
+
+ /**
+ * This method should be never called, see special logic in [LinkedListChannel.onCancelIdempotentList].
+ */
+ override fun resumeSendClosed(closed: Closed<*>) {
+ assert { false }
+ }
+
override fun toString(): String = "SendBuffered@$hexAddress($element)"
}
}
@@ -597,26 +624,8 @@ internal abstract class AbstractChannel<E>(
if (result) onReceiveEnqueued()
}
- public final override suspend fun receiveOrNull(): E? {
- // fast path -- try poll non-blocking
- val result = pollInternal()
- @Suppress("UNCHECKED_CAST")
- if (result !== POLL_FAILED && result !is Closed<*>) return result as E
- // slow-path does suspend
- return receiveSuspend(RECEIVE_NULL_ON_CLOSE)
- }
-
- @Suppress("UNCHECKED_CAST")
- private fun receiveOrNullResult(result: Any?): E? {
- if (result is Closed<*>) {
- if (result.closeCause != null) throw recoverStackTrace(result.closeCause)
- return null
- }
- return result as E
- }
-
@Suppress("UNCHECKED_CAST")
- public final override suspend fun receiveOrClosed(): ValueOrClosed<E> {
+ public final override suspend fun receiveCatching(): ChannelResult<E> {
// fast path -- try poll non-blocking
val result = pollInternal()
if (result !== POLL_FAILED) return result.toResult()
@@ -625,9 +634,11 @@ internal abstract class AbstractChannel<E>(
}
@Suppress("UNCHECKED_CAST")
- public final override fun poll(): E? {
+ public final override fun tryReceive(): ChannelResult<E> {
val result = pollInternal()
- return if (result === POLL_FAILED) null else receiveOrNullResult(result)
+ if (result === POLL_FAILED) return ChannelResult.failure()
+ if (result is Closed<*>) return ChannelResult.closed(result.closeCause)
+ return ChannelResult.success(result as E)
}
@Deprecated(level = DeprecationLevel.HIDDEN, message = "Since 1.2.0, binary compatibility with versions <= 1.1.x")
@@ -635,6 +646,13 @@ internal abstract class AbstractChannel<E>(
cancelInternal(cause)
final override fun cancel(cause: CancellationException?) {
+ /*
+ * Do not create an exception if channel is already cancelled.
+ * Channel is closed for receive when either it is cancelled (then we are free to bail out)
+ * or was closed and elements were received.
+ * Then `onCancelIdempotent` does nothing for all implementations.
+ */
+ if (isClosedForReceive) return
cancelInternal(cause ?: CancellationException("$classSimpleName was cancelled"))
}
@@ -668,6 +686,13 @@ internal abstract class AbstractChannel<E>(
// Add to the list only **after** successful removal
list += previous as Send
}
+ onCancelIdempotentList(list, closed)
+ }
+
+ /**
+ * This method is overridden by [LinkedListChannel] to handle cancellation of [SendBuffered] elements from the list.
+ */
+ protected open fun onCancelIdempotentList(list: InlineList<Send>, closed: Closed<*>) {
list.forEachReversed { it.resumeSendClosed(closed) }
}
@@ -713,18 +738,10 @@ internal abstract class AbstractChannel<E>(
}
}
- final override val onReceiveOrNull: SelectClause1<E?>
- get() = object : SelectClause1<E?> {
+ final override val onReceiveCatching: SelectClause1<ChannelResult<E>>
+ get() = object : SelectClause1<ChannelResult<E>> {
@Suppress("UNCHECKED_CAST")
- override fun <R> registerSelectClause1(select: SelectInstance<R>, block: suspend (E?) -> R) {
- registerSelectReceiveMode(select, RECEIVE_NULL_ON_CLOSE, block as suspend (Any?) -> R)
- }
- }
-
- final override val onReceiveOrClosed: SelectClause1<ValueOrClosed<E>>
- get() = object : SelectClause1<ValueOrClosed<E>> {
- @Suppress("UNCHECKED_CAST")
- override fun <R> registerSelectClause1(select: SelectInstance<R>, block: suspend (ValueOrClosed<E>) -> R) {
+ override fun <R> registerSelectClause1(select: SelectInstance<R>, block: suspend (ChannelResult<E>) -> R) {
registerSelectReceiveMode(select, RECEIVE_RESULT, block as suspend (Any?) -> R)
}
}
@@ -755,15 +772,7 @@ internal abstract class AbstractChannel<E>(
}
RECEIVE_RESULT -> {
if (!select.trySelect()) return
- startCoroutineUnintercepted(ValueOrClosed.closed<Any>(value.closeCause), select.completion)
- }
- RECEIVE_NULL_ON_CLOSE -> {
- if (value.closeCause == null) {
- if (!select.trySelect()) return
- startCoroutineUnintercepted(null, select.completion)
- } else {
- throw recoverStackTrace(value.receiveException)
- }
+ startCoroutineUnintercepted(ChannelResult.closed<Any>(value.closeCause), select.completion)
}
}
}
@@ -884,7 +893,7 @@ internal abstract class AbstractChannel<E>(
@JvmField val receiveMode: Int
) : Receive<E>() {
fun resumeValue(value: E): Any? = when (receiveMode) {
- RECEIVE_RESULT -> ValueOrClosed.value(value)
+ RECEIVE_RESULT -> ChannelResult.success(value)
else -> value
}
@@ -900,7 +909,6 @@ internal abstract class AbstractChannel<E>(
override fun resumeReceiveClosed(closed: Closed<*>) {
when {
- receiveMode == RECEIVE_NULL_ON_CLOSE && closed.closeCause == null -> cont.resume(null)
receiveMode == RECEIVE_RESULT -> cont.resume(closed.toResult<Any>())
else -> cont.resumeWithException(closed.receiveException)
}
@@ -969,7 +977,7 @@ internal abstract class AbstractChannel<E>(
@Suppress("UNCHECKED_CAST")
override fun completeResumeReceive(value: E) {
block.startCoroutineCancellable(
- if (receiveMode == RECEIVE_RESULT) ValueOrClosed.value(value) else value,
+ if (receiveMode == RECEIVE_RESULT) ChannelResult.success(value) else value,
select.completion,
resumeOnCancellationFun(value)
)
@@ -979,12 +987,7 @@ internal abstract class AbstractChannel<E>(
if (!select.trySelect()) return
when (receiveMode) {
RECEIVE_THROWS_ON_CLOSE -> select.resumeSelectWithException(closed.receiveException)
- RECEIVE_RESULT -> block.startCoroutineCancellable(ValueOrClosed.closed<R>(closed.closeCause), select.completion)
- RECEIVE_NULL_ON_CLOSE -> if (closed.closeCause == null) {
- block.startCoroutineCancellable(null, select.completion)
- } else {
- select.resumeSelectWithException(closed.receiveException)
- }
+ RECEIVE_RESULT -> block.startCoroutineCancellable(ChannelResult.closed<R>(closed.closeCause), select.completion)
}
}
@@ -1002,8 +1005,7 @@ internal abstract class AbstractChannel<E>(
// receiveMode values
internal const val RECEIVE_THROWS_ON_CLOSE = 0
-internal const val RECEIVE_NULL_ON_CLOSE = 1
-internal const val RECEIVE_RESULT = 2
+internal const val RECEIVE_RESULT = 1
@JvmField
@SharedImmutable
@@ -1107,9 +1109,9 @@ internal class Closed<in E>(
override val offerResult get() = this
override val pollResult get() = this
- override fun tryResumeSend(otherOp: PrepareOp?): Symbol? = RESUME_TOKEN.also { otherOp?.finishPrepare() }
+ override fun tryResumeSend(otherOp: PrepareOp?): Symbol = RESUME_TOKEN.also { otherOp?.finishPrepare() }
override fun completeResumeSend() {}
- override fun tryResumeReceive(value: E, otherOp: PrepareOp?): Symbol? = RESUME_TOKEN.also { otherOp?.finishPrepare() }
+ override fun tryResumeReceive(value: E, otherOp: PrepareOp?): Symbol = RESUME_TOKEN.also { otherOp?.finishPrepare() }
override fun completeResumeReceive(value: E) {}
override fun resumeSendClosed(closed: Closed<*>) = assert { false } // "Should be never invoked"
override fun toString(): String = "Closed@$hexAddress[$closeCause]"
@@ -1122,8 +1124,8 @@ internal abstract class Receive<in E> : LockFreeLinkedListNode(), ReceiveOrClose
}
@Suppress("NOTHING_TO_INLINE", "UNCHECKED_CAST")
-private inline fun <E> Any?.toResult(): ValueOrClosed<E> =
- if (this is Closed<*>) ValueOrClosed.closed(closeCause) else ValueOrClosed.value(this as E)
+private inline fun <E> Any?.toResult(): ChannelResult<E> =
+ if (this is Closed<*>) ChannelResult.closed(closeCause) else ChannelResult.success(this as E)
@Suppress("NOTHING_TO_INLINE")
-private inline fun <E> Closed<*>.toResult(): ValueOrClosed<E> = ValueOrClosed.closed(closeCause)
+private inline fun <E> Closed<*>.toResult(): ChannelResult<E> = ChannelResult.closed(closeCause)
diff --git a/kotlinx-coroutines-core/common/src/channels/ArrayBroadcastChannel.kt b/kotlinx-coroutines-core/common/src/channels/ArrayBroadcastChannel.kt
index 91b5473c..600eb6a9 100644
--- a/kotlinx-coroutines-core/common/src/channels/ArrayBroadcastChannel.kt
+++ b/kotlinx-coroutines-core/common/src/channels/ArrayBroadcastChannel.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.channels
diff --git a/kotlinx-coroutines-core/common/src/channels/ArrayChannel.kt b/kotlinx-coroutines-core/common/src/channels/ArrayChannel.kt
index 80cb8aa0..7e6c0e68 100644
--- a/kotlinx-coroutines-core/common/src/channels/ArrayChannel.kt
+++ b/kotlinx-coroutines-core/common/src/channels/ArrayChannel.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.channels
@@ -49,7 +49,6 @@ internal open class ArrayChannel<E>(
protected final override val isBufferAlwaysFull: Boolean get() = false
protected final override val isBufferFull: Boolean get() = size.value == capacity && onBufferOverflow == BufferOverflow.SUSPEND
- override val isFull: Boolean get() = lock.withLock { isFullImpl }
override val isEmpty: Boolean get() = lock.withLock { isEmptyImpl }
override val isClosedForReceive: Boolean get() = lock.withLock { super.isClosedForReceive }
@@ -298,7 +297,7 @@ internal open class ArrayChannel<E>(
}
// then clean all queued senders
super.onCancelIdempotent(wasClosed)
- undeliveredElementException?.let { throw it } // throw cancel exception at the end if there was one
+ undeliveredElementException?.let { throw it } // throw UndeliveredElementException at the end if there was one
}
// ------ debug ------
diff --git a/kotlinx-coroutines-core/common/src/channels/Broadcast.kt b/kotlinx-coroutines-core/common/src/channels/Broadcast.kt
index 0193ed06..b1c24b45 100644
--- a/kotlinx-coroutines-core/common/src/channels/Broadcast.kt
+++ b/kotlinx-coroutines-core/common/src/channels/Broadcast.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.channels
@@ -35,11 +35,13 @@ import kotlin.coroutines.intrinsics.*
* [send][BroadcastChannel.send] and [close][BroadcastChannel.close] operations that interfere with
* the broadcasting coroutine in hard-to-specify ways.
*
- * **Note: This API is obsolete.** It will be deprecated and replaced with the
- * [Flow.shareIn][kotlinx.coroutines.flow.shareIn] operator when it becomes stable.
+ * **Note: This API is obsolete since 1.5.0.** It will be deprecated with warning in 1.6.0
+ * and with error in 1.7.0. It is replaced with [Flow.shareIn][kotlinx.coroutines.flow.shareIn]
+ * operator.
*
* @param start coroutine start option. The default value is [CoroutineStart.LAZY].
*/
+@ObsoleteCoroutinesApi
public fun <E> ReceiveChannel<E>.broadcast(
capacity: Int = 1,
start: CoroutineStart = CoroutineStart.LAZY
@@ -95,10 +97,12 @@ public fun <E> ReceiveChannel<E>.broadcast(
*
* ### Future replacement
*
+ * This API is obsolete since 1.5.0.
* This function has an inappropriate result type of [BroadcastChannel] which provides
* [send][BroadcastChannel.send] and [close][BroadcastChannel.close] operations that interfere with
- * the broadcasting coroutine in hard-to-specify ways. It will be replaced with
- * sharing operators on [Flow][kotlinx.coroutines.flow.Flow] in the future.
+ * the broadcasting coroutine in hard-to-specify ways. It will be deprecated with warning in 1.6.0
+ * and with error in 1.7.0. It is replaced with [Flow.shareIn][kotlinx.coroutines.flow.shareIn]
+ * operator.
*
* @param context additional to [CoroutineScope.coroutineContext] context of the coroutine.
* @param capacity capacity of the channel's buffer (1 by default).
@@ -106,6 +110,7 @@ public fun <E> ReceiveChannel<E>.broadcast(
* @param onCompletion optional completion handler for the producer coroutine (see [Job.invokeOnCompletion]).
* @param block the coroutine code.
*/
+@ObsoleteCoroutinesApi
public fun <E> CoroutineScope.broadcast(
context: CoroutineContext = EmptyCoroutineContext,
capacity: Int = 1,
@@ -127,7 +132,13 @@ private open class BroadcastCoroutine<E>(
parentContext: CoroutineContext,
protected val _channel: BroadcastChannel<E>,
active: Boolean
-) : AbstractCoroutine<Unit>(parentContext, active), ProducerScope<E>, BroadcastChannel<E> by _channel {
+) : AbstractCoroutine<Unit>(parentContext, initParentJob = false, active = active),
+ ProducerScope<E>, BroadcastChannel<E> by _channel {
+
+ init {
+ initParentJob(parentContext[Job])
+ }
+
override val isActive: Boolean get() = super.isActive
override val channel: SendChannel<E>
diff --git a/kotlinx-coroutines-core/common/src/channels/BroadcastChannel.kt b/kotlinx-coroutines-core/common/src/channels/BroadcastChannel.kt
index d356566f..c82b8dbd 100644
--- a/kotlinx-coroutines-core/common/src/channels/BroadcastChannel.kt
+++ b/kotlinx-coroutines-core/common/src/channels/BroadcastChannel.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
@file:Suppress("FunctionName")
@@ -20,10 +20,10 @@ import kotlinx.coroutines.channels.Channel.Factory.UNLIMITED
* See `BroadcastChannel()` factory function for the description of available
* broadcast channel implementations.
*
- * **Note: This API is obsolete.** It will be deprecated and replaced by [SharedFlow][kotlinx.coroutines.flow.SharedFlow]
- * when it becomes stable.
+ * **Note: This API is obsolete since 1.5.0.** It will be deprecated with warning in 1.6.0
+ * and with error in 1.7.0. It is replaced with [SharedFlow][kotlinx.coroutines.flow.SharedFlow].
*/
-@ExperimentalCoroutinesApi // not @ObsoleteCoroutinesApi to reduce burden for people who are still using it
+@ObsoleteCoroutinesApi
public interface BroadcastChannel<E> : SendChannel<E> {
/**
* Subscribes to this [BroadcastChannel] and returns a channel to receive elements from it.
@@ -60,9 +60,11 @@ public interface BroadcastChannel<E> : SendChannel<E> {
* * when `capacity` is [BUFFERED] -- creates `ArrayBroadcastChannel` with a default capacity.
* * otherwise -- throws [IllegalArgumentException].
*
- * **Note: This is an experimental api.** It may be changed in the future updates.
+ * **Note: This API is obsolete since 1.5.0.** It will be deprecated with warning in 1.6.0
+ * and with error in 1.7.0. It is replaced with [StateFlow][kotlinx.coroutines.flow.StateFlow]
+ * and [SharedFlow][kotlinx.coroutines.flow.SharedFlow].
*/
-@ExperimentalCoroutinesApi
+@ObsoleteCoroutinesApi
public fun <E> BroadcastChannel(capacity: Int): BroadcastChannel<E> =
when (capacity) {
0 -> throw IllegalArgumentException("Unsupported 0 capacity for BroadcastChannel")
diff --git a/kotlinx-coroutines-core/common/src/channels/BufferOverflow.kt b/kotlinx-coroutines-core/common/src/channels/BufferOverflow.kt
index a89c633f..48b89ce6 100644
--- a/kotlinx-coroutines-core/common/src/channels/BufferOverflow.kt
+++ b/kotlinx-coroutines-core/common/src/channels/BufferOverflow.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.channels
diff --git a/kotlinx-coroutines-core/common/src/channels/Channel.kt b/kotlinx-coroutines-core/common/src/channels/Channel.kt
index 72c08e1a..b15c4262 100644
--- a/kotlinx-coroutines-core/common/src/channels/Channel.kt
+++ b/kotlinx-coroutines-core/common/src/channels/Channel.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
@file:Suppress("FunctionName")
@@ -14,6 +14,7 @@ import kotlinx.coroutines.channels.Channel.Factory.RENDEZVOUS
import kotlinx.coroutines.channels.Channel.Factory.UNLIMITED
import kotlinx.coroutines.internal.*
import kotlinx.coroutines.selects.*
+import kotlin.contracts.*
import kotlin.internal.*
import kotlin.jvm.*
@@ -23,7 +24,7 @@ import kotlin.jvm.*
public interface SendChannel<in E> {
/**
* Returns `true` if this channel was closed by an invocation of [close]. This means that
- * calling [send] or [offer] will result in an exception.
+ * calling [send] will result in an exception.
*
* **Note: This is an experimental api.** This property may change its semantics and/or name in the future.
*/
@@ -31,16 +32,6 @@ public interface SendChannel<in E> {
public val isClosedForSend: Boolean
/**
- * Returns `true` if the channel is full (out of capacity), which means that an attempt to [send] will suspend.
- * This function returns `false` if the channel [is closed for `send`][isClosedForSend].
- *
- * @suppress **Will be removed in next releases, no replacement.**
- */
- @ExperimentalCoroutinesApi
- @Deprecated(level = DeprecationLevel.ERROR, message = "Will be removed in next releases without replacement")
- public val isFull: Boolean
-
- /**
* Sends the specified [element] to this channel, suspending the caller while the buffer of this channel is full
* or if it does not exist, or throws an exception if the channel [is closed for `send`][isClosedForSend] (see [close] for details).
*
@@ -60,7 +51,7 @@ public interface SendChannel<in E> {
* Use [yield] or [CoroutineScope.isActive] to periodically check for cancellation in tight loops if needed.
*
* This function can be used in [select] invocations with the [onSend] clause.
- * Use [offer] to try sending to this channel without waiting.
+ * Use [trySend] to try sending to this channel without waiting.
*/
public suspend fun send(element: E)
@@ -73,31 +64,29 @@ public interface SendChannel<in E> {
*/
public val onSend: SelectClause2<E, SendChannel<E>>
+
/**
* Immediately adds the specified [element] to this channel, if this doesn't violate its capacity restrictions,
- * and returns `true`. Otherwise, just returns `false`. This is a synchronous variant of [send] which backs off
- * in situations when `send` suspends.
- *
- * Throws an exception if the channel [is closed for `send`][isClosedForSend] (see [close] for details).
+ * and returns the successful result. Otherwise, returns failed or closed result.
+ * This is synchronous variant of [send], which backs off in situations when `send` suspends or throws.
*
- * When `offer` call returns `false` it guarantees that the element was not delivered to the consumer and it
- * it does not call `onUndeliveredElement` that was installed for this channel. If the channel was closed,
- * then it calls `onUndeliveredElement` before throwing an exception.
+ * When `trySend` call returns a non-successful result, it guarantees that the element was not delivered to the consumer, and
+ * it does not call `onUndeliveredElement` that was installed for this channel.
* See "Undelivered elements" section in [Channel] documentation for details on handling undelivered elements.
*/
- public fun offer(element: E): Boolean
+ public fun trySend(element: E): ChannelResult<Unit>
/**
* Closes this channel.
* This is an idempotent operation &mdash; subsequent invocations of this function have no effect and return `false`.
- * Conceptually, its sends a special "close token" over this channel.
+ * Conceptually, it sends a special "close token" over this channel.
*
* Immediately after invocation of this function,
* [isClosedForSend] starts returning `true`. However, [isClosedForReceive][ReceiveChannel.isClosedForReceive]
* on the side of [ReceiveChannel] starts returning `true` only after all previously sent elements
* are received.
*
- * A channel that was closed without a [cause] throws a [ClosedSendChannelException] on attempts to [send] or [offer]
+ * A channel that was closed without a [cause] throws a [ClosedSendChannelException] on attempts to [send]
* and [ClosedReceiveChannelException] on attempts to [receive][ReceiveChannel.receive].
* A channel that was closed with non-null [cause] is called a _failed_ channel. Attempts to send or
* receive on a failed channel throw the specified [cause] exception.
@@ -116,10 +105,11 @@ public interface SendChannel<in E> {
* * the cause of `close` or `cancel` otherwise.
*
* Example of usage (exception handling is omitted):
+ *
* ```
* val events = Channel(UNLIMITED)
* callbackBasedApi.registerCallback { event ->
- * events.offer(event)
+ * events.trySend(event)
* }
*
* val uiUpdater = launch(Dispatchers.Main, parent = UILifecycle) {
@@ -128,7 +118,6 @@ public interface SendChannel<in E> {
* }
*
* events.invokeOnClose { callbackBasedApi.stop() }
- *
* ```
*
* **Note: This is an experimental api.** This function may change its semantics, parameters or return type in the future.
@@ -140,6 +129,44 @@ public interface SendChannel<in E> {
*/
@ExperimentalCoroutinesApi
public fun invokeOnClose(handler: (cause: Throwable?) -> Unit)
+
+ /**
+ * **Deprecated** offer method.
+ *
+ * This method was deprecated in the favour of [trySend].
+ * It has proven itself as the most error-prone method in Channel API:
+ *
+ * * `Boolean` return type creates the false sense of security, implying that `false`
+ * is returned instead of throwing an exception.
+ * * It was used mostly from non-suspending APIs where CancellationException triggered
+ * internal failures in the application (the most common source of bugs).
+ * * Due to signature and explicit `if (ch.offer(...))` checks it was easy to
+ * oversee such error during code review.
+ * * Its name was not aligned with the rest of the API and tried to mimic Java's queue instead.
+ *
+ * **NB** Automatic migration provides best-effort for the user experience, but requires removal
+ * or adjusting of the code that relied on the exception handling.
+ * The complete replacement has a more verbose form:
+ * ```
+ * channel.trySend(element)
+ * .onClosed { throw it ?: ClosedSendChannelException("Channel was closed normally") }
+ * .isSuccess
+ * ```
+ *
+ * See https://github.com/Kotlin/kotlinx.coroutines/issues/974 for more context.
+ *
+ * @suppress **Deprecated**.
+ */
+ @Deprecated(
+ level = DeprecationLevel.WARNING,
+ message = "Deprecated in the favour of 'trySend' method",
+ replaceWith = ReplaceWith("trySend(element).isSuccess")
+ ) // Warning since 1.5.0
+ public fun offer(element: E): Boolean {
+ val result = trySend(element)
+ if (result.isSuccess) return true
+ throw recoverStackTrace(result.exceptionOrNull() ?: return false)
+ }
}
/**
@@ -182,7 +209,7 @@ public interface ReceiveChannel<out E> {
* Use [yield] or [CoroutineScope.isActive] to periodically check for cancellation in tight loops if needed.
*
* This function can be used in [select] invocations with the [onReceive] clause.
- * Use [poll] to try receiving from this channel without waiting.
+ * Use [tryReceive] to try receiving from this channel without waiting.
*/
public suspend fun receive(): E
@@ -195,94 +222,39 @@ public interface ReceiveChannel<out E> {
public val onReceive: SelectClause1<E>
/**
- * Retrieves and removes an element from this channel if it's not empty, or suspends the caller while the channel is empty,
- * or returns `null` if the channel is [closed for `receive`][isClosedForReceive] without cause,
- * or throws the original [close][SendChannel.close] cause exception if the channel has _failed_.
- *
- * This suspending function is cancellable. If the [Job] of the current coroutine is cancelled or completed while this
- * function is suspended, this function immediately resumes with a [CancellationException].
- * There is a **prompt cancellation guarantee**. If the job was cancelled while this function was
- * suspended, it will not resume successfully. The `receiveOrNull` call can retrieve the element from the channel,
- * but then throw [CancellationException], thus failing to deliver the element.
- * See "Undelivered elements" section in [Channel] documentation for details on handling undelivered elements.
- *
- * Note that this function does not check for cancellation when it is not suspended.
- * Use [yield] or [CoroutineScope.isActive] to periodically check for cancellation in tight loops if needed.
- *
- * This function can be used in [select] invocations with the [onReceiveOrNull] clause.
- * Use [poll] to try receiving from this channel without waiting.
- *
- * @suppress **Deprecated**: in favor of receiveOrClosed and receiveOrNull extension.
- */
- @ObsoleteCoroutinesApi
- @Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER")
- @LowPriorityInOverloadResolution
- @Deprecated(
- message = "Deprecated in favor of receiveOrClosed and receiveOrNull extension",
- level = DeprecationLevel.WARNING,
- replaceWith = ReplaceWith("receiveOrNull", "kotlinx.coroutines.channels.receiveOrNull")
- )
- public suspend fun receiveOrNull(): E?
-
- /**
- * Clause for the [select] expression of the [receiveOrNull] suspending function that selects with the element
- * received from the channel or `null` if the channel is
- * [closed for `receive`][isClosedForReceive] without a cause. The [select] invocation fails with
- * the original [close][SendChannel.close] cause exception if the channel has _failed_.
- *
- * @suppress **Deprecated**: in favor of onReceiveOrClosed and onReceiveOrNull extension.
- */
- @ObsoleteCoroutinesApi
- @Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER")
- @LowPriorityInOverloadResolution
- @Deprecated(
- message = "Deprecated in favor of onReceiveOrClosed and onReceiveOrNull extension",
- level = DeprecationLevel.WARNING,
- replaceWith = ReplaceWith("onReceiveOrNull", "kotlinx.coroutines.channels.onReceiveOrNull")
- )
- public val onReceiveOrNull: SelectClause1<E?>
-
- /**
* Retrieves and removes an element from this channel if it's not empty, or suspends the caller while this channel is empty.
- * This method returns [ValueOrClosed] with the value of an element successfully retrieved from the channel
- * or the close cause if the channel was closed.
+ * This method returns [ChannelResult] with the value of an element successfully retrieved from the channel
+ * or the close cause if the channel was closed. Closed cause may be `null` if the channel was closed normally.
+ * The result cannot be [failed][ChannelResult.isFailure] without being [closed][ChannelResult.isClosed].
*
* This suspending function is cancellable. If the [Job] of the current coroutine is cancelled or completed while this
* function is suspended, this function immediately resumes with a [CancellationException].
* There is a **prompt cancellation guarantee**. If the job was cancelled while this function was
- * suspended, it will not resume successfully. The `receiveOrClosed` call can retrieve the element from the channel,
+ * suspended, it will not resume successfully. The `receiveCatching` call can retrieve the element from the channel,
* but then throw [CancellationException], thus failing to deliver the element.
* See "Undelivered elements" section in [Channel] documentation for details on handling undelivered elements.
*
* Note that this function does not check for cancellation when it is not suspended.
* Use [yield] or [CoroutineScope.isActive] to periodically check for cancellation in tight loops if needed.
*
- * This function can be used in [select] invocations with the [onReceiveOrClosed] clause.
- * Use [poll] to try receiving from this channel without waiting.
- *
- * @suppress *This is an internal API, do not use*: Inline classes ABI is not stable yet and
- * [KT-27524](https://youtrack.jetbrains.com/issue/KT-27524) needs to be fixed.
+ * This function can be used in [select] invocations with the [onReceiveCatching] clause.
+ * Use [tryReceive] to try receiving from this channel without waiting.
*/
- @InternalCoroutinesApi // until https://youtrack.jetbrains.com/issue/KT-27524 is fixed
- public suspend fun receiveOrClosed(): ValueOrClosed<E>
+ public suspend fun receiveCatching(): ChannelResult<E>
/**
- * Clause for the [select] expression of the [receiveOrClosed] suspending function that selects with the [ValueOrClosed] with a value
+ * Clause for the [select] expression of the [onReceiveCatching] suspending function that selects with the [ChannelResult] with a value
* that is received from the channel or with a close cause if the channel
* [is closed for `receive`][isClosedForReceive].
- *
- * @suppress *This is an internal API, do not use*: Inline classes ABI is not stable yet and
- * [KT-27524](https://youtrack.jetbrains.com/issue/KT-27524) needs to be fixed.
*/
- @InternalCoroutinesApi // until https://youtrack.jetbrains.com/issue/KT-27524 is fixed
- public val onReceiveOrClosed: SelectClause1<ValueOrClosed<E>>
+ public val onReceiveCatching: SelectClause1<ChannelResult<E>>
/**
- * Retrieves and removes an element from this channel if its not empty, or returns `null` if the channel is empty
- * or is [is closed for `receive`][isClosedForReceive] without a cause.
- * It throws the original [close][SendChannel.close] cause exception if the channel has _failed_.
+ * Retrieves and removes an element from this channel if it's not empty, returning a [successful][ChannelResult.success]
+ * result, returns [failed][ChannelResult.failed] result if the channel is empty, and [closed][ChannelResult.closed]
+ * result if the channel is closed.
*/
- public fun poll(): E?
+ public fun tryReceive(): ChannelResult<E>
/**
* Returns a new iterator to receive elements from this channel using a `for` loop.
@@ -318,107 +290,262 @@ public interface ReceiveChannel<out E> {
*/
@Deprecated(level = DeprecationLevel.HIDDEN, message = "Since 1.2.0, binary compatibility with versions <= 1.1.x")
public fun cancel(cause: Throwable? = null): Boolean
+
+ /**
+ * **Deprecated** poll method.
+ *
+ * This method was deprecated in the favour of [tryReceive].
+ * It has proven itself as error-prone method in Channel API:
+ *
+ * * Nullable return type creates the false sense of security, implying that `null`
+ * is returned instead of throwing an exception.
+ * * It was used mostly from non-suspending APIs where CancellationException triggered
+ * internal failures in the application (the most common source of bugs).
+ * * Its name was not aligned with the rest of the API and tried to mimic Java's queue instead.
+ *
+ * See https://github.com/Kotlin/kotlinx.coroutines/issues/974 for more context.
+ *
+ * ### Replacement note
+ *
+ * The replacement `tryReceive().getOrNull()` is a default that ignores all close exceptions and
+ * proceeds with `null`, while `poll` throws an exception if the channel was closed with an exception.
+ * Replacement with the very same 'poll' semantics is `tryReceive().onClosed { if (it != null) throw it }.getOrNull()`
+ *
+ * @suppress **Deprecated**.
+ */
+ @Deprecated(
+ level = DeprecationLevel.WARNING,
+ message = "Deprecated in the favour of 'tryReceive'. " +
+ "Please note that the provided replacement does not rethrow channel's close cause as 'poll' did, " +
+ "for the precise replacement please refer to the 'poll' documentation",
+ replaceWith = ReplaceWith("tryReceive().getOrNull()")
+ ) // Warning since 1.5.0
+ public fun poll(): E? {
+ val result = tryReceive()
+ if (result.isSuccess) return result.getOrThrow()
+ throw recoverStackTrace(result.exceptionOrNull() ?: return null)
+ }
+
+ /**
+ * This function was deprecated since 1.3.0 and is no longer recommended to use
+ * or to implement in subclasses.
+ *
+ * It had the following pitfalls:
+ * - Didn't allow to distinguish 'null' as "closed channel" from "null as a value"
+ * - Was throwing if the channel has failed even though its signature may suggest it returns 'null'
+ * - It didn't really belong to core channel API and can be exposed as an extension instead.
+ *
+ * ### Replacement note
+ *
+ * The replacement `receiveCatching().getOrNull()` is a safe default that ignores all close exceptions and
+ * proceeds with `null`, while `receiveOrNull` throws an exception if the channel was closed with an exception.
+ * Replacement with the very same `receiveOrNull` semantics is `receiveCatching().onClosed { if (it != null) throw it }.getOrNull()`.
+ *
+ * @suppress **Deprecated**
+ */
+ @Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER")
+ @LowPriorityInOverloadResolution
+ @Deprecated(
+ message = "Deprecated in favor of 'receiveCatching'. " +
+ "Please note that the provided replacement does not rethrow channel's close cause as 'receiveOrNull' did, " +
+ "for the detailed replacement please refer to the 'receiveOrNull' documentation",
+ level = DeprecationLevel.ERROR,
+ replaceWith = ReplaceWith("receiveCatching().getOrNull()")
+ ) // Warning since 1.3.0, error in 1.5.0, will be hidden in 1.6.0
+ public suspend fun receiveOrNull(): E? = receiveCatching().getOrNull()
+
+ /**
+ * This function was deprecated since 1.3.0 and is no longer recommended to use
+ * or to implement in subclasses.
+ * See [receiveOrNull] documentation.
+ *
+ * @suppress **Deprecated**: in favor of onReceiveCatching extension.
+ */
+ @Deprecated(
+ message = "Deprecated in favor of onReceiveCatching extension",
+ level = DeprecationLevel.ERROR,
+ replaceWith = ReplaceWith("onReceiveCatching")
+ ) // Warning since 1.3.0, error in 1.5.0, will be hidden or removed in 1.6.0
+ public val onReceiveOrNull: SelectClause1<E?>
+ get() {
+ return object : SelectClause1<E?> {
+ @InternalCoroutinesApi
+ override fun <R> registerSelectClause1(select: SelectInstance<R>, block: suspend (E?) -> R) {
+ onReceiveCatching.registerSelectClause1(select) {
+ it.exceptionOrNull()?.let { throw it }
+ block(it.getOrNull())
+ }
+ }
+ }
+ }
}
/**
- * A discriminated union of [ReceiveChannel.receiveOrClosed] result
- * that encapsulates either an element of type [T] successfully received from the channel or a close cause.
+ * A discriminated union of channel operation result.
+ * It encapsulates the successful or failed result of a channel operation or a failed operation to a closed channel with
+ * an optional cause.
*
- * :todo: Do not make it public before resolving todos in the code of this class.
+ * The successful result represents a successful operation with a value of type [T], for example,
+ * the result of [Channel.receiveCatching] operation or a successfully sent element as a result of [Channel.trySend].
*
- * @suppress *This is an internal API, do not use*: Inline classes ABI is not stable yet and
- * [KT-27524](https://youtrack.jetbrains.com/issue/KT-27524) needs to be fixed.
+ * The failed result represents a failed operation attempt to a channel, but it doesn't necessary indicate that the channel is failed.
+ * E.g. when the channel is full, [Channel.trySend] returns failed result, but the channel itself is not in the failed state.
+ *
+ * The closed result represents an operation attempt to a closed channel and also implies that the operation has failed.
+ * It is guaranteed that if the result is _closed_, then the target channel is either [closed for send][Channel.isClosedForSend]
+ * or is [closed for receive][Channel.isClosedForReceive] depending on whether the failed operation was sending or receiving.
*/
-@Suppress("NON_PUBLIC_PRIMARY_CONSTRUCTOR_OF_INLINE_CLASS", "EXPERIMENTAL_FEATURE_WARNING")
-@InternalCoroutinesApi // until https://youtrack.jetbrains.com/issue/KT-27524 is fixed
-public inline class ValueOrClosed<out T>
-internal constructor(private val holder: Any?) {
+@JvmInline
+public value class ChannelResult<out T>
+@PublishedApi internal constructor(@PublishedApi internal val holder: Any?) {
/**
- * Returns `true` if this instance represents a received element.
- * In this case [isClosed] returns `false`.
- * todo: it is commented for now, because it is not used
+ * Returns `true` if this instance represents a successful
+ * operation outcome.
+ *
+ * In this case [isFailure] and [isClosed] return `false`.
*/
- //public val isValue: Boolean get() = holder !is Closed
+ public val isSuccess: Boolean get() = holder !is Failed
/**
- * Returns `true` if this instance represents a close cause.
- * In this case [isValue] returns `false`.
+ * Returns `true` if this instance represents unsuccessful operation.
+ *
+ * In this case [isSuccess] returns false, but it does not imply
+ * that the channel is failed or closed.
+ *
+ * Example of a failed operation without an exception and channel being closed
+ * is [Channel.trySend] attempt to a channel that is full.
*/
- public val isClosed: Boolean get() = holder is Closed
+ public val isFailure: Boolean get() = holder is Failed
/**
- * Returns the received value if this instance represents a received value, or throws an [IllegalStateException] otherwise.
+ * Returns `true` if this instance represents unsuccessful operation
+ * to a closed or cancelled channel.
*
- * :todo: Decide, if it is needed, how it shall be named with relation to [valueOrThrow]:
+ * In this case [isSuccess] returns `false`, [isFailure] returns `true`, but it does not imply
+ * that [exceptionOrNull] returns non-null value.
*
- * So we have the following methods on `ValueOrClosed`: `value`, `valueOrNull`, `valueOrThrow`.
- * On the other hand, the channel has the following `receive` variants:
- * * `receive` which corresponds to `receiveOrClosed().valueOrThrow`... huh?
- * * `receiveOrNull` which corresponds to `receiveOrClosed().valueOrNull`
- * * `receiveOrClosed`
- * For the sake of simplicity consider dropping this version of `value` and rename [valueOrThrow] to simply `value`.
+ * It can happen if the channel was [closed][Channel.close] normally without an exception.
*/
- @Suppress("UNCHECKED_CAST")
- public val value: T
- get() = if (holder is Closed) error(DEFAULT_CLOSE_MESSAGE) else holder as T
+ public val isClosed: Boolean get() = holder is Closed
/**
- * Returns the received value if this element represents a received value, or `null` otherwise.
- * :todo: Decide if it shall be made into extension that is available only for non-null T.
- * Note: it might become inconsistent with kotlin.Result
+ * Returns the encapsulated value if this instance represents success or `null` if it represents failed result.
*/
@Suppress("UNCHECKED_CAST")
- public val valueOrNull: T?
- get() = if (holder is Closed) null else holder as T
+ public fun getOrNull(): T? = if (holder !is Failed) holder as T else null
/**
- * :todo: Decide, if it is needed, how it shall be named with relation to [value].
- * Note that `valueOrThrow` rethrows the cause adding no meaningful information about the call site,
- * so if one is sure that `ValueOrClosed` always holds a value, this very property should be used.
- * Otherwise, it could be very hard to locate the source of the exception.
- * todo: it is commented for now, because it is not used
+ * Returns the encapsulated value if this instance represents success or throws an exception if it is closed or failed.
*/
- //@Suppress("UNCHECKED_CAST")
- //public val valueOrThrow: T
- // get() = if (holder is Closed) throw holder.exception else holder as T
+ public fun getOrThrow(): T {
+ @Suppress("UNCHECKED_CAST")
+ if (holder !is Failed) return holder as T
+ if (holder is Closed && holder.cause != null) throw holder.cause
+ error("Trying to call 'getOrThrow' on a failed channel result: $holder")
+ }
/**
- * Returns the close cause of the channel if this instance represents a close cause, or throws
- * an [IllegalStateException] otherwise.
+ * Returns the encapsulated exception if this instance represents failure or `null` if it is success
+ * or unsuccessful operation to closed channel.
*/
- @Suppress("UNCHECKED_CAST")
- public val closeCause: Throwable? get() =
- if (holder is Closed) holder.cause else error("Channel was not closed")
+ public fun exceptionOrNull(): Throwable? = (holder as? Closed)?.cause
+
+ internal open class Failed {
+ override fun toString(): String = "Failed"
+ }
+
+ internal class Closed(@JvmField val cause: Throwable?): Failed() {
+ override fun equals(other: Any?): Boolean = other is Closed && cause == other.cause
+ override fun hashCode(): Int = cause.hashCode()
+ override fun toString(): String = "Closed($cause)"
+ }
+
+ @Suppress("NOTHING_TO_INLINE")
+ @InternalCoroutinesApi
+ public companion object {
+ private val failed = Failed()
+
+ @InternalCoroutinesApi
+ public fun <E> success(value: E): ChannelResult<E> =
+ ChannelResult(value)
+
+ @InternalCoroutinesApi
+ public fun <E> failure(): ChannelResult<E> =
+ ChannelResult(failed)
+
+ @InternalCoroutinesApi
+ public fun <E> closed(cause: Throwable?): ChannelResult<E> =
+ ChannelResult(Closed(cause))
+ }
- /**
- * @suppress
- */
public override fun toString(): String =
when (holder) {
is Closed -> holder.toString()
else -> "Value($holder)"
+ }
+}
+
+/**
+ * Returns the encapsulated value if this instance represents [success][ChannelResult.isSuccess] or the
+ * result of [onFailure] function for the encapsulated [Throwable] exception if it is failed or closed
+ * result.
+ */
+@OptIn(ExperimentalContracts::class)
+public inline fun <T> ChannelResult<T>.getOrElse(onFailure: (exception: Throwable?) -> T): T {
+ contract {
+ callsInPlace(onFailure, InvocationKind.AT_MOST_ONCE)
}
+ @Suppress("UNCHECKED_CAST")
+ return if (holder is ChannelResult.Failed) onFailure(exceptionOrNull()) else holder as T
+}
- internal class Closed(@JvmField val cause: Throwable?) {
- // todo: it is commented for now, because it is not used
- //val exception: Throwable get() = cause ?: ClosedReceiveChannelException(DEFAULT_CLOSE_MESSAGE)
- override fun equals(other: Any?): Boolean = other is Closed && cause == other.cause
- override fun hashCode(): Int = cause.hashCode()
- override fun toString(): String = "Closed($cause)"
+/**
+ * Performs the given [action] on the encapsulated value if this instance represents [success][ChannelResult.isSuccess].
+ * Returns the original `ChannelResult` unchanged.
+ */
+@OptIn(ExperimentalContracts::class)
+public inline fun <T> ChannelResult<T>.onSuccess(action: (value: T) -> Unit): ChannelResult<T> {
+ contract {
+ callsInPlace(action, InvocationKind.AT_MOST_ONCE)
}
+ @Suppress("UNCHECKED_CAST")
+ if (holder !is ChannelResult.Failed) action(holder as T)
+ return this
+}
- /**
- * todo: consider making value/closed constructors public in the future.
- */
- internal companion object {
- @Suppress("NOTHING_TO_INLINE")
- internal inline fun <E> value(value: E): ValueOrClosed<E> =
- ValueOrClosed(value)
-
- @Suppress("NOTHING_TO_INLINE")
- internal inline fun <E> closed(cause: Throwable?): ValueOrClosed<E> =
- ValueOrClosed(Closed(cause))
+/**
+ * Performs the given [action] on the encapsulated [Throwable] exception if this instance represents [failure][ChannelResult.isFailure].
+ * The result of [ChannelResult.exceptionOrNull] is passed to the [action] parameter.
+ *
+ * Returns the original `ChannelResult` unchanged.
+ */
+@OptIn(ExperimentalContracts::class)
+public inline fun <T> ChannelResult<T>.onFailure(action: (exception: Throwable?) -> Unit): ChannelResult<T> {
+ contract {
+ callsInPlace(action, InvocationKind.AT_MOST_ONCE)
}
+ @Suppress("UNCHECKED_CAST")
+ if (holder is ChannelResult.Failed) action(exceptionOrNull())
+ return this
+}
+
+/**
+ * Performs the given [action] on the encapsulated [Throwable] exception if this instance represents [failure][ChannelResult.isFailure]
+ * due to channel being [closed][Channel.close].
+ * The result of [ChannelResult.exceptionOrNull] is passed to the [action] parameter.
+ * It is guaranteed that if action is invoked, then the channel is either [closed for send][Channel.isClosedForSend]
+ * or is [closed for receive][Channel.isClosedForReceive] depending on the failed operation.
+ *
+ * Returns the original `ChannelResult` unchanged.
+ */
+@OptIn(ExperimentalContracts::class)
+public inline fun <T> ChannelResult<T>.onClosed(action: (exception: Throwable?) -> Unit): ChannelResult<T> {
+ contract {
+ callsInPlace(action, InvocationKind.AT_MOST_ONCE)
+ }
+ @Suppress("UNCHECKED_CAST")
+ if (holder is ChannelResult.Closed) action(exceptionOrNull())
+ return this
}
/**
@@ -493,14 +620,14 @@ public interface ChannelIterator<out E> {
*
* * When `capacity` is [Channel.UNLIMITED] &mdash; it creates a channel with effectively unlimited buffer.
* This channel has a linked-list buffer of unlimited capacity (limited only by available memory).
- * [Sending][send] to this channel never suspends, and [offer] always returns `true`.
+ * [Sending][send] to this channel never suspends, and [trySend] always succeeds.
*
* * When `capacity` is [Channel.CONFLATED] &mdash; it creates a _conflated_ channel
- * This channel buffers at most one element and conflates all subsequent `send` and `offer` invocations,
+ * This channel buffers at most one element and conflates all subsequent `send` and `trySend` invocations,
* so that the receiver always gets the last element sent.
- * Back-to-send sent elements are conflated &mdash; only the last sent element is received,
+ * Back-to-back sent elements are conflated &mdash; only the last sent element is received,
* while previously sent elements **are lost**.
- * [Sending][send] to this channel never suspends, and [offer] always returns `true`.
+ * [Sending][send] to this channel never suspends, and [trySend] always succeeds.
*
* * When `capacity` is positive but less than [UNLIMITED] &mdash; it creates an array-based channel with the specified capacity.
* This channel has an array buffer of a fixed `capacity`.
@@ -547,8 +674,6 @@ public interface ChannelIterator<out E> {
*
* * When [send][SendChannel.send] operation throws an exception because it was cancelled before it had a chance to actually
* send the element or because the channel was [closed][SendChannel.close] or [cancelled][ReceiveChannel.cancel].
- * * When [offer][SendChannel.offer] operation throws an exception when
- * the channel was [closed][SendChannel.close] or [cancelled][ReceiveChannel.cancel].
* * When [receive][ReceiveChannel.receive], [receiveOrNull][ReceiveChannel.receiveOrNull], or [hasNext][ChannelIterator.hasNext]
* operation throws an exception when it had retrieved the element from the
* channel but was cancelled before the code following the receive call resumed.
diff --git a/kotlinx-coroutines-core/common/src/channels/ChannelCoroutine.kt b/kotlinx-coroutines-core/common/src/channels/ChannelCoroutine.kt
index a75d4661..57b2797d 100644
--- a/kotlinx-coroutines-core/common/src/channels/ChannelCoroutine.kt
+++ b/kotlinx-coroutines-core/common/src/channels/ChannelCoroutine.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.channels
@@ -11,8 +11,10 @@ import kotlin.coroutines.*
internal open class ChannelCoroutine<E>(
parentContext: CoroutineContext,
protected val _channel: Channel<E>,
+ initParentJob: Boolean,
active: Boolean
-) : AbstractCoroutine<Unit>(parentContext, active), Channel<E> by _channel {
+) : AbstractCoroutine<Unit>(parentContext, initParentJob, active), Channel<E> by _channel {
+
val channel: Channel<E> get() = this
override fun cancel() {
@@ -26,6 +28,7 @@ internal open class ChannelCoroutine<E>(
}
final override fun cancel(cause: CancellationException?) {
+ if (isCancelled) return // Do not create an exception if the coroutine (-> the channel) is already cancelled
cancelInternal(cause ?: defaultCancellationException())
}
diff --git a/kotlinx-coroutines-core/common/src/channels/Channels.common.kt b/kotlinx-coroutines-core/common/src/channels/Channels.common.kt
index 398d5ca4..e0b4f9d2 100644
--- a/kotlinx-coroutines-core/common/src/channels/Channels.common.kt
+++ b/kotlinx-coroutines-core/common/src/channels/Channels.common.kt
@@ -1,14 +1,16 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
@file:JvmMultifileClass
@file:JvmName("ChannelsKt")
@file:Suppress("DEPRECATION_ERROR")
+@file:OptIn(ExperimentalContracts::class)
package kotlinx.coroutines.channels
import kotlinx.coroutines.*
import kotlinx.coroutines.selects.*
+import kotlin.contracts.*
import kotlin.coroutines.*
import kotlin.jvm.*
@@ -35,122 +37,49 @@ public inline fun <E, R> BroadcastChannel<E>.consume(block: ReceiveChannel<E>.()
}
/**
- * Retrieves and removes the element from this channel suspending the caller while this channel [isEmpty]
- * or returns `null` if the channel is [closed][Channel.isClosedForReceive].
+ * This function is deprecated in the favour of [ReceiveChannel.receiveCatching].
*
- * This suspending function is cancellable. If the [Job] of the current coroutine is cancelled or completed while this
- * function is suspended, this function immediately resumes with [CancellationException].
- * There is a **prompt cancellation guarantee**. If the job was cancelled while this function was
- * suspended, it will not resume successfully. If the `receiveOrNull` call threw [CancellationException] there is no way
- * to tell if some element was already received from the channel or not. See [Channel] documentation for details.
+ * This function is considered error-prone for the following reasons;
+ * * Is throwing if the channel has failed even though its signature may suggest it returns 'null'
+ * * It is easy to forget that exception handling still have to be explicit
+ * * During code reviews and code reading, intentions of the code are frequently unclear:
+ * are potential exceptions ignored deliberately or not?
*
- * Note, that this function does not check for cancellation when it is not suspended.
- * Use [yield] or [CoroutineScope.isActive] to periodically check for cancellation in tight loops if needed.
- *
- * This extension is defined only for channels on non-null types, so that generic functions defined using
- * these extensions do not accidentally confuse `null` value and a normally closed channel, leading to hard
- * to find bugs.
+ * @suppress doc
*/
+@Deprecated(
+ "Deprecated in the favour of 'receiveCatching'",
+ ReplaceWith("receiveCatching().getOrNull()"),
+ DeprecationLevel.WARNING
+) // Warning since 1.5.0, ERROR in 1.6.0, HIDDEN in 1.7.0
@Suppress("EXTENSION_SHADOWED_BY_MEMBER")
-@ExperimentalCoroutinesApi // since 1.3.0, tentatively stable in 1.4.x
public suspend fun <E : Any> ReceiveChannel<E>.receiveOrNull(): E? {
@Suppress("DEPRECATION", "UNCHECKED_CAST")
return (this as ReceiveChannel<E?>).receiveOrNull()
}
/**
- * Clause for [select] expression of [receiveOrNull] suspending function that selects with the element that
- * is received from the channel or selects with `null` if the channel
- * [isClosedForReceive][ReceiveChannel.isClosedForReceive] without cause. The [select] invocation fails with
- * the original [close][SendChannel.close] cause exception if the channel has _failed_.
- *
- * This extension is defined only for channels on non-null types, so that generic functions defined using
- * these extensions do not accidentally confuse `null` value and a normally closed channel, leading to hard
- * to find bugs.
- **/
-@ExperimentalCoroutinesApi // since 1.3.0, tentatively stable in 1.4.x
+ * This function is deprecated in the favour of [ReceiveChannel.onReceiveCatching]
+ */
+@Deprecated(
+ "Deprecated in the favour of 'onReceiveCatching'",
+ level = DeprecationLevel.WARNING
+) // Warning since 1.5.0, ERROR in 1.6.0, HIDDEN in 1.7.0
public fun <E : Any> ReceiveChannel<E>.onReceiveOrNull(): SelectClause1<E?> {
@Suppress("DEPRECATION", "UNCHECKED_CAST")
return (this as ReceiveChannel<E?>).onReceiveOrNull
}
/**
- * Subscribes to this [BroadcastChannel] and performs the specified action for each received element.
- *
- * **Note: This API will become obsolete in future updates with introduction of lazy asynchronous streams.**
- * See [issue #254](https://github.com/Kotlin/kotlinx.coroutines/issues/254).
- */
-@ObsoleteCoroutinesApi
-public suspend inline fun <E> BroadcastChannel<E>.consumeEach(action: (E) -> Unit): Unit =
- consume {
- for (element in this) action(element)
- }
-
-// -------- Operations on ReceiveChannel --------
-
-/**
- * Returns a [CompletionHandler] that invokes [cancel][ReceiveChannel.cancel] on the [ReceiveChannel]
- * with the corresponding cause. See also [ReceiveChannel.consume].
- *
- * **WARNING**: It is planned that in the future a second invocation of this method
- * on an channel that is already being consumed is going to fail fast, that it
- * immediately throws an [IllegalStateException].
- * See [this issue](https://github.com/Kotlin/kotlinx.coroutines/issues/167)
- * for details.
- *
- * **Note: This API will become obsolete in future updates with introduction of lazy asynchronous streams.**
- * See [issue #254](https://github.com/Kotlin/kotlinx.coroutines/issues/254).
- */
-@Deprecated(
- message = "Channel operators are deprecated in favour of Flow and will be removed in 1.4.x",
- level = DeprecationLevel.ERROR
-)
-public fun ReceiveChannel<*>.consumes(): CompletionHandler = { cause: Throwable? ->
- cancelConsumed(cause)
-}
-
-@PublishedApi
-internal fun ReceiveChannel<*>.cancelConsumed(cause: Throwable?) {
- cancel(cause?.let {
- it as? CancellationException ?: CancellationException("Channel was consumed, consumer had failed", it)
- })
-}
-
-/**
- * Returns a [CompletionHandler] that invokes [cancel][ReceiveChannel.cancel] on all the
- * specified [ReceiveChannel] instances with the corresponding cause.
- * See also [ReceiveChannel.consumes()] for a version on one channel.
- *
- * **Note: This API will become obsolete in future updates with introduction of lazy asynchronous streams.**
- * See [issue #254](https://github.com/Kotlin/kotlinx.coroutines/issues/254).
- */
-@Deprecated(
- message = "Channel operators are deprecated in favour of Flow and will be removed in 1.4.x",
- level = DeprecationLevel.ERROR
-)
-public fun consumesAll(vararg channels: ReceiveChannel<*>): CompletionHandler =
- { cause: Throwable? ->
- var exception: Throwable? = null
- for (channel in channels)
- try {
- channel.cancelConsumed(cause)
- } catch (e: Throwable) {
- if (exception == null) {
- exception = e
- } else {
- exception.addSuppressedThrowable(e)
- }
- }
- exception?.let { throw it }
- }
-
-/**
* Makes sure that the given [block] consumes all elements from the given channel
* by always invoking [cancel][ReceiveChannel.cancel] after the execution of the block.
*
* The operation is _terminal_.
*/
public inline fun <E, R> ReceiveChannel<E>.consume(block: ReceiveChannel<E>.() -> R): R {
+ contract {
+ callsInPlace(block, InvocationKind.EXACTLY_ONCE)
+ }
var cause: Throwable? = null
try {
return block()
@@ -176,2015 +105,35 @@ public suspend inline fun <E> ReceiveChannel<E>.consumeEach(action: (E) -> Unit)
}
/**
- * Performs the given [action] for each received element.
- *
- * The operation is _terminal_.
- * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel].
- *
- * **Note: This API will become obsolete in future updates with introduction of lazy asynchronous streams.**
- * See [issue #254](https://github.com/Kotlin/kotlinx.coroutines/issues/254).
- */
-@Deprecated(
- message = "Channel operators are deprecated in favour of Flow and will be removed in 1.4.x",
- level = DeprecationLevel.ERROR
-)
-public suspend inline fun <E> ReceiveChannel<E>.consumeEachIndexed(action: (IndexedValue<E>) -> Unit) {
- var index = 0
- consumeEach {
- action(IndexedValue(index++, it))
- }
-}
-
-/**
- * Returns an element at the given [index] or throws an [IndexOutOfBoundsException] if the [index] is out of bounds of this channel.
- *
- * The operation is _terminal_.
- * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel].
- *
- * **Note: This API will become obsolete in future updates with introduction of lazy asynchronous streams.**
- * See [issue #254](https://github.com/Kotlin/kotlinx.coroutines/issues/254).
- */
-@Deprecated(
- message = "Channel operators are deprecated in favour of Flow and will be removed in 1.4.x",
- level = DeprecationLevel.ERROR
-)
-public suspend fun <E> ReceiveChannel<E>.elementAt(index: Int): E =
- elementAtOrElse(index) { throw IndexOutOfBoundsException("ReceiveChannel doesn't contain element at index $index.") }
-
-/**
- * Returns an element at the given [index] or the result of calling the [defaultValue] function if the [index] is out of bounds of this channel.
- *
- * The operation is _terminal_.
- * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel].
- *
- * **Note: This API will become obsolete in future updates with introduction of lazy asynchronous streams.**
- * See [issue #254](https://github.com/Kotlin/kotlinx.coroutines/issues/254).
- */
-@Deprecated(
- message = "Channel operators are deprecated in favour of Flow and will be removed in 1.4.x",
- level = DeprecationLevel.ERROR
-)
-public suspend inline fun <E> ReceiveChannel<E>.elementAtOrElse(index: Int, defaultValue: (Int) -> E): E =
- consume {
- if (index < 0)
- return defaultValue(index)
- var count = 0
- for (element in this) {
- if (index == count++)
- return element
- }
- return defaultValue(index)
- }
-
-/**
- * Returns an element at the given [index] or `null` if the [index] is out of bounds of this channel.
- *
- * The operation is _terminal_.
- * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel].
- *
- * **Note: This API will become obsolete in future updates with introduction of lazy asynchronous streams.**
- * See [issue #254](https://github.com/Kotlin/kotlinx.coroutines/issues/254).
- */
-@Deprecated(
- message = "Channel operators are deprecated in favour of Flow and will be removed in 1.4.x",
- level = DeprecationLevel.ERROR
-)
-public suspend fun <E> ReceiveChannel<E>.elementAtOrNull(index: Int): E? =
- consume {
- if (index < 0)
- return null
- var count = 0
- for (element in this) {
- if (index == count++)
- return element
- }
- return null
- }
-
-/**
- * Returns the first element matching the given [predicate], or `null` if no such element was found.
- *
- * The operation is _terminal_.
- * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel].
- *
- * **Note: This API will become obsolete in future updates with introduction of lazy asynchronous streams.**
- * See [issue #254](https://github.com/Kotlin/kotlinx.coroutines/issues/254).
- */
-@Deprecated(
- message = "Channel operators are deprecated in favour of Flow and will be removed in 1.4.x",
- level = DeprecationLevel.ERROR
-)
-public suspend inline fun <E> ReceiveChannel<E>.find(predicate: (E) -> Boolean): E? =
- firstOrNull(predicate)
-
-/**
- * Returns the last element matching the given [predicate], or `null` if no such element was found.
- *
- * The operation is _terminal_.
- * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel].
- *
- * **Note: This API will become obsolete in future updates with introduction of lazy asynchronous streams.**
- * See [issue #254](https://github.com/Kotlin/kotlinx.coroutines/issues/254).
- */
-@Deprecated(
- message = "Channel operators are deprecated in favour of Flow and will be removed in 1.4.x",
- level = DeprecationLevel.ERROR
-)
-public suspend inline fun <E> ReceiveChannel<E>.findLast(predicate: (E) -> Boolean): E? =
- lastOrNull(predicate)
-
-/**
- * Returns first element.
- * @throws [NoSuchElementException] if the channel is empty.
- *
- * The operation is _terminal_.
- * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel].
- *
- * **Note: This API will become obsolete in future updates with introduction of lazy asynchronous streams.**
- * See [issue #254](https://github.com/Kotlin/kotlinx.coroutines/issues/254).
- */
-@Deprecated(
- message = "Channel operators are deprecated in favour of Flow and will be removed in 1.4.x",
- level = DeprecationLevel.ERROR
-)
-public suspend fun <E> ReceiveChannel<E>.first(): E =
- consume {
- val iterator = iterator()
- if (!iterator.hasNext())
- throw NoSuchElementException("ReceiveChannel is empty.")
- return iterator.next()
- }
-
-/**
- * Returns the first element matching the given [predicate].
- * @throws [NoSuchElementException] if no such element is found.
- *
- * The operation is _terminal_.
- * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel].
- *
- * **Note: This API will become obsolete in future updates with introduction of lazy asynchronous streams.**
- * See [issue #254](https://github.com/Kotlin/kotlinx.coroutines/issues/254).
- */
-@Deprecated(
- message = "Channel operators are deprecated in favour of Flow and will be removed in 1.4.x",
- level = DeprecationLevel.ERROR
-)
-public suspend inline fun <E> ReceiveChannel<E>.first(predicate: (E) -> Boolean): E {
- consumeEach {
- if (predicate(it)) return it
- }
- throw NoSuchElementException("ReceiveChannel contains no element matching the predicate.")
-}
-
-/**
- * Returns the first element, or `null` if the channel is empty.
- *
- * The operation is _terminal_.
- * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel].
- *
- * **Note: This API will become obsolete in future updates with introduction of lazy asynchronous streams.**
- * See [issue #254](https://github.com/Kotlin/kotlinx.coroutines/issues/254).
- */
-@Deprecated(
- message = "Channel operators are deprecated in favour of Flow and will be removed in 1.4.x",
- level = DeprecationLevel.ERROR
-)
-public suspend fun <E> ReceiveChannel<E>.firstOrNull(): E? =
- consume {
- val iterator = iterator()
- if (!iterator.hasNext())
- return null
- return iterator.next()
- }
-
-/**
- * Returns the first element matching the given [predicate], or `null` if element was not found.
- *
- * The operation is _terminal_.
- * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel].
- *
- * **Note: This API will become obsolete in future updates with introduction of lazy asynchronous streams.**
- * See [issue #254](https://github.com/Kotlin/kotlinx.coroutines/issues/254).
- */
-@Deprecated(
- message = "Channel operators are deprecated in favour of Flow and will be removed in 1.4.x",
- level = DeprecationLevel.ERROR
-)
-public suspend inline fun <E> ReceiveChannel<E>.firstOrNull(predicate: (E) -> Boolean): E? {
- consumeEach {
- if (predicate(it)) return it
- }
- return null
-}
-
-/**
- * Returns first index of [element], or -1 if the channel does not contain element.
- *
- * The operation is _terminal_.
- * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel].
- *
- * **Note: This API will become obsolete in future updates with introduction of lazy asynchronous streams.**
- * See [issue #254](https://github.com/Kotlin/kotlinx.coroutines/issues/254).
- */
-@Deprecated(
- message = "Channel operators are deprecated in favour of Flow and will be removed in 1.4.x",
- level = DeprecationLevel.ERROR
-)
-public suspend fun <E> ReceiveChannel<E>.indexOf(element: E): Int {
- var index = 0
- consumeEach {
- if (element == it)
- return index
- index++
- }
- return -1
-}
-
-/**
- * Returns index of the first element matching the given [predicate], or -1 if the channel does not contain such element.
- *
- * The operation is _terminal_.
- * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel].
- *
- * **Note: This API will become obsolete in future updates with introduction of lazy asynchronous streams.**
- * See [issue #254](https://github.com/Kotlin/kotlinx.coroutines/issues/254).
- */
-@Deprecated(
- message = "Channel operators are deprecated in favour of Flow and will be removed in 1.4.x",
- level = DeprecationLevel.ERROR
-)
-public suspend inline fun <E> ReceiveChannel<E>.indexOfFirst(predicate: (E) -> Boolean): Int {
- var index = 0
- consumeEach {
- if (predicate(it))
- return index
- index++
- }
- return -1
-}
-
-/**
- * Returns index of the last element matching the given [predicate], or -1 if the channel does not contain such element.
- *
- * The operation is _terminal_.
- * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel].
- *
- * **Note: This API will become obsolete in future updates with introduction of lazy asynchronous streams.**
- * See [issue #254](https://github.com/Kotlin/kotlinx.coroutines/issues/254).
- */
-@Deprecated(
- message = "Channel operators are deprecated in favour of Flow and will be removed in 1.4.x",
- level = DeprecationLevel.ERROR
-)
-public suspend inline fun <E> ReceiveChannel<E>.indexOfLast(predicate: (E) -> Boolean): Int {
- var lastIndex = -1
- var index = 0
- consumeEach {
- if (predicate(it))
- lastIndex = index
- index++
- }
- return lastIndex
-}
-
-/**
- * Returns the last element.
- * @throws [NoSuchElementException] if the channel is empty.
- *
- * The operation is _terminal_.
- * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel].
- *
- * **Note: This API will become obsolete in future updates with introduction of lazy asynchronous streams.**
- * See [issue #254](https://github.com/Kotlin/kotlinx.coroutines/issues/254).
- */
-@Deprecated(
- message = "Channel operators are deprecated in favour of Flow and will be removed in 1.4.x",
- level = DeprecationLevel.ERROR
-)
-public suspend fun <E> ReceiveChannel<E>.last(): E =
- consume {
- val iterator = iterator()
- if (!iterator.hasNext())
- throw NoSuchElementException("ReceiveChannel is empty.")
- var last = iterator.next()
- while (iterator.hasNext())
- last = iterator.next()
- return last
- }
-
-/**
- * Returns the last element matching the given [predicate].
- * @throws [NoSuchElementException] if no such element is found.
- *
- * The operation is _terminal_.
- * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel].
- *
- * **Note: This API will become obsolete in future updates with introduction of lazy asynchronous streams.**
- * See [issue #254](https://github.com/Kotlin/kotlinx.coroutines/issues/254).
- */
-@Deprecated(
- message = "Channel operators are deprecated in favour of Flow and will be removed in 1.4.x",
- level = DeprecationLevel.ERROR
-)
-public suspend inline fun <E> ReceiveChannel<E>.last(predicate: (E) -> Boolean): E {
- var last: E? = null
- var found = false
- consumeEach {
- if (predicate(it)) {
- last = it
- found = true
- }
- }
- if (!found) throw NoSuchElementException("ReceiveChannel contains no element matching the predicate.")
- @Suppress("UNCHECKED_CAST")
- return last as E
-}
-
-/**
- * Returns last index of [element], or -1 if the channel does not contain element.
- *
- * The operation is _terminal_.
- * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel].
- *
- * **Note: This API will become obsolete in future updates with introduction of lazy asynchronous streams.**
- * See [issue #254](https://github.com/Kotlin/kotlinx.coroutines/issues/254).
- */
-@Deprecated(
- message = "Channel operators are deprecated in favour of Flow and will be removed in 1.4.x",
- level = DeprecationLevel.ERROR
-)
-public suspend fun <E> ReceiveChannel<E>.lastIndexOf(element: E): Int {
- var lastIndex = -1
- var index = 0
- consumeEach {
- if (element == it)
- lastIndex = index
- index++
- }
- return lastIndex
-}
-
-/**
- * Returns the last element, or `null` if the channel is empty.
- *
- * The operation is _terminal_.
- * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel].
- *
- * **Note: This API will become obsolete in future updates with introduction of lazy asynchronous streams.**
- * See [issue #254](https://github.com/Kotlin/kotlinx.coroutines/issues/254).
- */
-@Deprecated(
- message = "Channel operators are deprecated in favour of Flow and will be removed in 1.4.x",
- level = DeprecationLevel.ERROR
-)
-public suspend fun <E> ReceiveChannel<E>.lastOrNull(): E? =
- consume {
- val iterator = iterator()
- if (!iterator.hasNext())
- return null
- var last = iterator.next()
- while (iterator.hasNext())
- last = iterator.next()
- return last
- }
-
-/**
- * Returns the last element matching the given [predicate], or `null` if no such element was found.
- *
- * The operation is _terminal_.
- * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel].
- *
- * **Note: This API will become obsolete in future updates with introduction of lazy asynchronous streams.**
- * See [issue #254](https://github.com/Kotlin/kotlinx.coroutines/issues/254).
- */
-@Deprecated(
- message = "Channel operators are deprecated in favour of Flow and will be removed in 1.4.x",
- level = DeprecationLevel.ERROR
-)
-public suspend inline fun <E> ReceiveChannel<E>.lastOrNull(predicate: (E) -> Boolean): E? {
- var last: E? = null
- consumeEach {
- if (predicate(it)) {
- last = it
- }
- }
- return last
-}
-
-/**
- * Returns the single element, or throws an exception if the channel is empty or has more than one element.
- *
- * The operation is _terminal_.
- * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel].
- *
- * **Note: This API will become obsolete in future updates with introduction of lazy asynchronous streams.**
- * See [issue #254](https://github.com/Kotlin/kotlinx.coroutines/issues/254).
- */
-@Deprecated(
- message = "Channel operators are deprecated in favour of Flow and will be removed in 1.4.x",
- level = DeprecationLevel.ERROR
-)
-public suspend fun <E> ReceiveChannel<E>.single(): E =
- consume {
- val iterator = iterator()
- if (!iterator.hasNext())
- throw NoSuchElementException("ReceiveChannel is empty.")
- val single = iterator.next()
- if (iterator.hasNext())
- throw IllegalArgumentException("ReceiveChannel has more than one element.")
- return single
- }
-
-/**
- * Returns the single element matching the given [predicate], or throws exception if there is no or more than one matching element.
- *
- * The operation is _terminal_.
- * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel].
- *
- * **Note: This API will become obsolete in future updates with introduction of lazy asynchronous streams.**
- * See [issue #254](https://github.com/Kotlin/kotlinx.coroutines/issues/254).
- */
-@Deprecated(
- message = "Channel operators are deprecated in favour of Flow and will be removed in 1.4.x",
- level = DeprecationLevel.ERROR
-)
-public suspend inline fun <E> ReceiveChannel<E>.single(predicate: (E) -> Boolean): E {
- var single: E? = null
- var found = false
- consumeEach {
- if (predicate(it)) {
- if (found) throw IllegalArgumentException("ReceiveChannel contains more than one matching element.")
- single = it
- found = true
- }
- }
- if (!found) throw NoSuchElementException("ReceiveChannel contains no element matching the predicate.")
- @Suppress("UNCHECKED_CAST")
- return single as E
-}
-
-/**
- * Returns single element, or `null` if the channel is empty or has more than one element.
- *
- * The operation is _terminal_.
- * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel].
- *
- * **Note: This API will become obsolete in future updates with introduction of lazy asynchronous streams.**
- * See [issue #254](https://github.com/Kotlin/kotlinx.coroutines/issues/254).
- */
-@Deprecated(
- message = "Channel operators are deprecated in favour of Flow and will be removed in 1.4.x",
- level = DeprecationLevel.ERROR
-)
-public suspend fun <E> ReceiveChannel<E>.singleOrNull(): E? =
- consume {
- val iterator = iterator()
- if (!iterator.hasNext())
- return null
- val single = iterator.next()
- if (iterator.hasNext())
- return null
- return single
- }
-
-/**
- * Returns the single element matching the given [predicate], or `null` if element was not found or more than one element was found.
- *
- * The operation is _terminal_.
- * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel].
- *
- * **Note: This API will become obsolete in future updates with introduction of lazy asynchronous streams.**
- * See [issue #254](https://github.com/Kotlin/kotlinx.coroutines/issues/254).
- */
-@Deprecated(
- message = "Channel operators are deprecated in favour of Flow and will be removed in 1.4.x",
- level = DeprecationLevel.ERROR
-)
-public suspend inline fun <E> ReceiveChannel<E>.singleOrNull(predicate: (E) -> Boolean): E? {
- var single: E? = null
- var found = false
- consumeEach {
- if (predicate(it)) {
- if (found) return null
- single = it
- found = true
- }
- }
- if (!found) return null
- return single
-}
-
-/**
- * Returns a channel containing all elements except first [n] elements.
- *
- * The operation is _intermediate_ and _stateless_.
- * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel].
- *
- * **Note: This API will become obsolete in future updates with introduction of lazy asynchronous streams.**
- * See [issue #254](https://github.com/Kotlin/kotlinx.coroutines/issues/254).
- */
-@Deprecated(
- message = "Channel operators are deprecated in favour of Flow and will be removed in 1.4.x",
- level = DeprecationLevel.ERROR
-)
-public fun <E> ReceiveChannel<E>.drop(n: Int, context: CoroutineContext = Dispatchers.Unconfined): ReceiveChannel<E> =
- GlobalScope.produce(context, onCompletion = consumes()) {
- require(n >= 0) { "Requested element count $n is less than zero." }
- var remaining: Int = n
- if (remaining > 0)
- for (e in this@drop) {
- remaining--
- if (remaining == 0)
- break
- }
- for (e in this@drop) {
- send(e)
- }
- }
-
-/**
- * Returns a channel containing all elements except first elements that satisfy the given [predicate].
- *
- * The operation is _intermediate_ and _stateless_.
- * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel].
- *
- * **Note: This API will become obsolete in future updates with introduction of lazy asynchronous streams.**
- * See [issue #254](https://github.com/Kotlin/kotlinx.coroutines/issues/254).
- */
-@Deprecated(
- message = "Channel operators are deprecated in favour of Flow and will be removed in 1.4.x",
- level = DeprecationLevel.ERROR
-)
-public fun <E> ReceiveChannel<E>.dropWhile(context: CoroutineContext = Dispatchers.Unconfined, predicate: suspend (E) -> Boolean): ReceiveChannel<E> =
- GlobalScope.produce(context, onCompletion = consumes()) {
- for (e in this@dropWhile) {
- if (!predicate(e)) {
- send(e)
- break
- }
- }
- for (e in this@dropWhile) {
- send(e)
- }
- }
-
-/**
- * Returns a channel containing only elements matching the given [predicate].
- *
- * The operation is _intermediate_ and _stateless_.
- * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel].
- *
- * **Note: This API will become obsolete in future updates with introduction of lazy asynchronous streams.**
- * See [issue #254](https://github.com/Kotlin/kotlinx.coroutines/issues/254).
- */
-@Deprecated(
- message = "Channel operators are deprecated in favour of Flow and will be removed in 1.4.x",
- level = DeprecationLevel.ERROR
-)
-public fun <E> ReceiveChannel<E>.filter(context: CoroutineContext = Dispatchers.Unconfined, predicate: suspend (E) -> Boolean): ReceiveChannel<E> =
- GlobalScope.produce(context, onCompletion = consumes()) {
- for (e in this@filter) {
- if (predicate(e)) send(e)
- }
- }
-
-/**
- * Returns a channel containing only elements matching the given [predicate].
- * @param [predicate] function that takes the index of an element and the element itself
- * and returns the result of predicate evaluation on the element.
- *
- * The operation is _intermediate_ and _stateless_.
- * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel].
- *
- * **Note: This API will become obsolete in future updates with introduction of lazy asynchronous streams.**
- * See [issue #254](https://github.com/Kotlin/kotlinx.coroutines/issues/254).
- */
-@Deprecated(
- message = "Channel operators are deprecated in favour of Flow and will be removed in 1.4.x",
- level = DeprecationLevel.ERROR
-)
-public fun <E> ReceiveChannel<E>.filterIndexed(context: CoroutineContext = Dispatchers.Unconfined, predicate: suspend (index: Int, E) -> Boolean): ReceiveChannel<E> =
- GlobalScope.produce(context, onCompletion = consumes()) {
- var index = 0
- for (e in this@filterIndexed) {
- if (predicate(index++, e)) send(e)
- }
- }
-
-/**
- * Appends all elements matching the given [predicate] to the given [destination].
- * @param [predicate] function that takes the index of an element and the element itself
- * and returns the result of predicate evaluation on the element.
- *
- * The operation is _terminal_.
- * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel].
- *
- * **Note: This API will become obsolete in future updates with introduction of lazy asynchronous streams.**
- * See [issue #254](https://github.com/Kotlin/kotlinx.coroutines/issues/254).
- */
-@Deprecated(
- message = "Channel operators are deprecated in favour of Flow and will be removed in 1.4.x",
- level = DeprecationLevel.ERROR
-)
-public suspend inline fun <E, C : MutableCollection<in E>> ReceiveChannel<E>.filterIndexedTo(destination: C, predicate: (index: Int, E) -> Boolean): C {
- consumeEachIndexed { (index, element) ->
- if (predicate(index, element)) destination.add(element)
- }
- return destination
-}
-
-/**
- * Appends all elements matching the given [predicate] to the given [destination].
- * @param [predicate] function that takes the index of an element and the element itself
- * and returns the result of predicate evaluation on the element.
- *
- * The operation is _terminal_.
- * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel].
- *
- * **Note: This API will become obsolete in future updates with introduction of lazy asynchronous streams.**
- * See [issue #254](https://github.com/Kotlin/kotlinx.coroutines/issues/254).
- */
-@Deprecated(
- message = "Channel operators are deprecated in favour of Flow and will be removed in 1.4.x",
- level = DeprecationLevel.ERROR
-)
-public suspend inline fun <E, C : SendChannel<E>> ReceiveChannel<E>.filterIndexedTo(destination: C, predicate: (index: Int, E) -> Boolean): C {
- consumeEachIndexed { (index, element) ->
- if (predicate(index, element)) destination.send(element)
- }
- return destination
-}
-
-/**
- * Returns a channel containing all elements not matching the given [predicate].
- *
- * The operation is _intermediate_ and _stateless_.
- * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel].
- *
- * **Note: This API will become obsolete in future updates with introduction of lazy asynchronous streams.**
- * See [issue #254](https://github.com/Kotlin/kotlinx.coroutines/issues/254).
- */
-@Deprecated(
- message = "Channel operators are deprecated in favour of Flow and will be removed in 1.4.x",
- level = DeprecationLevel.ERROR
-)
-public fun <E> ReceiveChannel<E>.filterNot(context: CoroutineContext = Dispatchers.Unconfined, predicate: suspend (E) -> Boolean): ReceiveChannel<E> =
- filter(context) { !predicate(it) }
-
-/**
- * Returns a channel containing all elements that are not `null`.
- *
- * The operation is _intermediate_ and _stateless_.
- * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel].
- *
- * **Note: This API will become obsolete in future updates with introduction of lazy asynchronous streams.**
- * See [issue #254](https://github.com/Kotlin/kotlinx.coroutines/issues/254).
- */
-@Deprecated(
- message = "Channel operators are deprecated in favour of Flow and will be removed in 1.4.x",
- level = DeprecationLevel.ERROR
-)
-@Suppress("UNCHECKED_CAST")
-public fun <E : Any> ReceiveChannel<E?>.filterNotNull(): ReceiveChannel<E> =
- filter { it != null } as ReceiveChannel<E>
-
-/**
- * Appends all elements that are not `null` to the given [destination].
- *
- * The operation is _terminal_.
- * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel].
- *
- * **Note: This API will become obsolete in future updates with introduction of lazy asynchronous streams.**
- * See [issue #254](https://github.com/Kotlin/kotlinx.coroutines/issues/254).
- */
-@Deprecated(
- message = "Channel operators are deprecated in favour of Flow and will be removed in 1.4.x",
- level = DeprecationLevel.ERROR
-)
-public suspend fun <E : Any, C : MutableCollection<in E>> ReceiveChannel<E?>.filterNotNullTo(destination: C): C {
- consumeEach {
- if (it != null) destination.add(it)
- }
- return destination
-}
-
-/**
- * Appends all elements that are not `null` to the given [destination].
- *
- * The operation is _terminal_.
- * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel].
- *
- * **Note: This API will become obsolete in future updates with introduction of lazy asynchronous streams.**
- * See [issue #254](https://github.com/Kotlin/kotlinx.coroutines/issues/254).
- */
-@Deprecated(
- message = "Channel operators are deprecated in favour of Flow and will be removed in 1.4.x",
- level = DeprecationLevel.ERROR
-)
-public suspend fun <E : Any, C : SendChannel<E>> ReceiveChannel<E?>.filterNotNullTo(destination: C): C {
- consumeEach {
- if (it != null) destination.send(it)
- }
- return destination
-}
-
-/**
- * Appends all elements not matching the given [predicate] to the given [destination].
- *
- * The operation is _terminal_.
- * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel].
- *
- * **Note: This API will become obsolete in future updates with introduction of lazy asynchronous streams.**
- * See [issue #254](https://github.com/Kotlin/kotlinx.coroutines/issues/254).
- */
-@Deprecated(
- message = "Channel operators are deprecated in favour of Flow and will be removed in 1.4.x",
- level = DeprecationLevel.ERROR
-)
-public suspend inline fun <E, C : MutableCollection<in E>> ReceiveChannel<E>.filterNotTo(destination: C, predicate: (E) -> Boolean): C {
- consumeEach {
- if (!predicate(it)) destination.add(it)
- }
- return destination
-}
-
-/**
- * Appends all elements not matching the given [predicate] to the given [destination].
- *
- * The operation is _terminal_.
- * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel].
- *
- * **Note: This API will become obsolete in future updates with introduction of lazy asynchronous streams.**
- * See [issue #254](https://github.com/Kotlin/kotlinx.coroutines/issues/254).
- */
-@Deprecated(
- message = "Channel operators are deprecated in favour of Flow and will be removed in 1.4.x",
- level = DeprecationLevel.ERROR
-)
-public suspend inline fun <E, C : SendChannel<E>> ReceiveChannel<E>.filterNotTo(destination: C, predicate: (E) -> Boolean): C {
- consumeEach {
- if (!predicate(it)) destination.send(it)
- }
- return destination
-}
-
-/**
- * Appends all elements matching the given [predicate] to the given [destination].
- *
- * The operation is _terminal_.
- * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel].
- *
- * **Note: This API will become obsolete in future updates with introduction of lazy asynchronous streams.**
- * See [issue #254](https://github.com/Kotlin/kotlinx.coroutines/issues/254).
- */
-@Deprecated(
- message = "Channel operators are deprecated in favour of Flow and will be removed in 1.4.x",
- level = DeprecationLevel.ERROR
-)
-public suspend inline fun <E, C : MutableCollection<in E>> ReceiveChannel<E>.filterTo(destination: C, predicate: (E) -> Boolean): C {
- consumeEach {
- if (predicate(it)) destination.add(it)
- }
- return destination
-}
-
-/**
- * Appends all elements matching the given [predicate] to the given [destination].
- *
- * The operation is _terminal_.
- * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel].
- *
- * **Note: This API will become obsolete in future updates with introduction of lazy asynchronous streams.**
- * See [issue #254](https://github.com/Kotlin/kotlinx.coroutines/issues/254).
- */
-@Deprecated(
- message = "Channel operators are deprecated in favour of Flow and will be removed in 1.4.x",
- level = DeprecationLevel.ERROR
-)
-public suspend inline fun <E, C : SendChannel<E>> ReceiveChannel<E>.filterTo(destination: C, predicate: (E) -> Boolean): C {
- consumeEach {
- if (predicate(it)) destination.send(it)
- }
- return destination
-}
-
-/**
- * Returns a channel containing first [n] elements.
- *
- * The operation is _intermediate_ and _stateless_.
- * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel].
- *
- * **Note: This API will become obsolete in future updates with introduction of lazy asynchronous streams.**
- * See [issue #254](https://github.com/Kotlin/kotlinx.coroutines/issues/254).
- */
-@Deprecated(
- message = "Channel operators are deprecated in favour of Flow and will be removed in 1.4.x",
- level = DeprecationLevel.ERROR
-)
-public fun <E> ReceiveChannel<E>.take(n: Int, context: CoroutineContext = Dispatchers.Unconfined): ReceiveChannel<E> =
- GlobalScope.produce(context, onCompletion = consumes()) {
- if (n == 0) return@produce
- require(n >= 0) { "Requested element count $n is less than zero." }
- var remaining: Int = n
- for (e in this@take) {
- send(e)
- remaining--
- if (remaining == 0)
- return@produce
- }
- }
-
-/**
- * Returns a channel containing first elements satisfying the given [predicate].
- *
- * The operation is _intermediate_ and _stateless_.
- * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel].
- *
- * **Note: This API will become obsolete in future updates with introduction of lazy asynchronous streams.**
- * See [issue #254](https://github.com/Kotlin/kotlinx.coroutines/issues/254).
- */
-@Deprecated(
- message = "Channel operators are deprecated in favour of Flow and will be removed in 1.4.x",
- level = DeprecationLevel.ERROR
-)
-public fun <E> ReceiveChannel<E>.takeWhile(context: CoroutineContext = Dispatchers.Unconfined, predicate: suspend (E) -> Boolean): ReceiveChannel<E> =
- GlobalScope.produce(context, onCompletion = consumes()) {
- for (e in this@takeWhile) {
- if (!predicate(e)) return@produce
- send(e)
- }
- }
-
-/**
- * Returns a [Map] containing key-value pairs provided by [transform] function
- * applied to elements of the given channel.
- *
- * If any of two pairs would have the same key the last one gets added to the map.
- *
- * The returned map preserves the entry iteration order of the original channel.
- *
- * The operation is _terminal_.
- * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel].
- *
- * **Note: This API will become obsolete in future updates with introduction of lazy asynchronous streams.**
- * See [issue #254](https://github.com/Kotlin/kotlinx.coroutines/issues/254).
- */
-@Deprecated(
- message = "Channel operators are deprecated in favour of Flow and will be removed in 1.4.x",
- level = DeprecationLevel.ERROR
-)
-public suspend inline fun <E, K, V> ReceiveChannel<E>.associate(transform: (E) -> Pair<K, V>): Map<K, V> =
- associateTo(LinkedHashMap(), transform)
-
-/**
- * Returns a [Map] containing the elements from the given channel indexed by the key
- * returned from [keySelector] function applied to each element.
- *
- * If any two elements would have the same key returned by [keySelector] the last one gets added to the map.
- *
- * The returned map preserves the entry iteration order of the original channel.
- *
- * The operation is _terminal_.
- * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel].
- *
- * **Note: This API will become obsolete in future updates with introduction of lazy asynchronous streams.**
- * See [issue #254](https://github.com/Kotlin/kotlinx.coroutines/issues/254).
- */
-@Deprecated(
- message = "Channel operators are deprecated in favour of Flow and will be removed in 1.4.x",
- level = DeprecationLevel.ERROR
-)
-public suspend inline fun <E, K> ReceiveChannel<E>.associateBy(keySelector: (E) -> K): Map<K, E> =
- associateByTo(LinkedHashMap(), keySelector)
-
-/**
- * Returns a [Map] containing the values provided by [valueTransform] and indexed by [keySelector] functions applied to elements of the given channel.
- *
- * If any two elements would have the same key returned by [keySelector] the last one gets added to the map.
- *
- * The returned map preserves the entry iteration order of the original channel.
- *
- * The operation is _terminal_.
- * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel].
- *
- * **Note: This API will become obsolete in future updates with introduction of lazy asynchronous streams.**
- * See [issue #254](https://github.com/Kotlin/kotlinx.coroutines/issues/254).
- */
-@Deprecated(
- message = "Channel operators are deprecated in favour of Flow and will be removed in 1.4.x",
- level = DeprecationLevel.ERROR
-)
-public suspend inline fun <E, K, V> ReceiveChannel<E>.associateBy(keySelector: (E) -> K, valueTransform: (E) -> V): Map<K, V> =
- associateByTo(LinkedHashMap(), keySelector, valueTransform)
-
-/**
- * Populates and returns the [destination] mutable map with key-value pairs,
- * where key is provided by the [keySelector] function applied to each element of the given channel
- * and value is the element itself.
- *
- * If any two elements would have the same key returned by [keySelector] the last one gets added to the map.
- *
- * The operation is _terminal_.
- * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel].
- *
- * **Note: This API will become obsolete in future updates with introduction of lazy asynchronous streams.**
- * See [issue #254](https://github.com/Kotlin/kotlinx.coroutines/issues/254).
- */
-@Deprecated(
- message = "Channel operators are deprecated in favour of Flow and will be removed in 1.4.x",
- level = DeprecationLevel.ERROR
-)
-public suspend inline fun <E, K, M : MutableMap<in K, in E>> ReceiveChannel<E>.associateByTo(destination: M, keySelector: (E) -> K): M {
- consumeEach {
- destination.put(keySelector(it), it)
- }
- return destination
-}
-
-/**
- * Populates and returns the [destination] mutable map with key-value pairs,
- * where key is provided by the [keySelector] function and
- * and value is provided by the [valueTransform] function applied to elements of the given channel.
- *
- * If any two elements would have the same key returned by [keySelector] the last one gets added to the map.
- *
- * The operation is _terminal_.
- * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel].
- *
- * **Note: This API will become obsolete in future updates with introduction of lazy asynchronous streams.**
- * See [issue #254](https://github.com/Kotlin/kotlinx.coroutines/issues/254).
- */
-@Deprecated(
- message = "Channel operators are deprecated in favour of Flow and will be removed in 1.4.x",
- level = DeprecationLevel.ERROR
-)
-public suspend inline fun <E, K, V, M : MutableMap<in K, in V>> ReceiveChannel<E>.associateByTo(destination: M, keySelector: (E) -> K, valueTransform: (E) -> V): M {
- consumeEach {
- destination.put(keySelector(it), valueTransform(it))
- }
- return destination
-}
-
-/**
- * Populates and returns the [destination] mutable map with key-value pairs
- * provided by [transform] function applied to each element of the given channel.
- *
- * If any of two pairs would have the same key the last one gets added to the map.
- *
- * The operation is _terminal_.
- * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel].
- *
- * **Note: This API will become obsolete in future updates with introduction of lazy asynchronous streams.**
- * See [issue #254](https://github.com/Kotlin/kotlinx.coroutines/issues/254).
- */
-@Deprecated(
- message = "Channel operators are deprecated in favour of Flow and will be removed in 1.4.x",
- level = DeprecationLevel.ERROR
-)
-public suspend inline fun <E, K, V, M : MutableMap<in K, in V>> ReceiveChannel<E>.associateTo(destination: M, transform: (E) -> Pair<K, V>): M {
- consumeEach {
- destination += transform(it)
- }
- return destination
-}
-
-/**
- * Send each element of the original channel
- * and appends the results to the given [destination].
- *
- * The operation is _terminal_.
- * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel].
- *
- * **Note: This API will become obsolete in future updates with introduction of lazy asynchronous streams.**
- * See [issue #254](https://github.com/Kotlin/kotlinx.coroutines/issues/254).
- */
-@Deprecated(
- message = "Channel operators are deprecated in favour of Flow and will be removed in 1.4.x",
- level = DeprecationLevel.ERROR
-)
-public suspend fun <E, C : SendChannel<E>> ReceiveChannel<E>.toChannel(destination: C): C {
- consumeEach {
- destination.send(it)
- }
- return destination
-}
-
-/**
- * Appends all elements to the given [destination] collection.
- *
- * The operation is _terminal_.
- * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel].
- *
- * **Note: This API will become obsolete in future updates with introduction of lazy asynchronous streams.**
- * See [issue #254](https://github.com/Kotlin/kotlinx.coroutines/issues/254).
- */
-@Deprecated(
- message = "Channel operators are deprecated in favour of Flow and will be removed in 1.4.x",
- level = DeprecationLevel.ERROR
-)
-public suspend fun <E, C : MutableCollection<in E>> ReceiveChannel<E>.toCollection(destination: C): C {
- consumeEach {
- destination.add(it)
- }
- return destination
-}
-
-/**
* Returns a [List] containing all elements.
*
* The operation is _terminal_.
* This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel].
*/
-public suspend fun <E> ReceiveChannel<E>.toList(): List<E> =
- this.toMutableList()
-
-/**
- * Returns a [Map] filled with all elements of this channel.
- *
- * The operation is _terminal_.
- * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel].
- *
- * **Note: This API will become obsolete in future updates with introduction of lazy asynchronous streams.**
- * See [issue #254](https://github.com/Kotlin/kotlinx.coroutines/issues/254).
- */
-@Deprecated(
- message = "Channel operators are deprecated in favour of Flow and will be removed in 1.4.x",
- level = DeprecationLevel.ERROR
-)
-public suspend fun <K, V> ReceiveChannel<Pair<K, V>>.toMap(): Map<K, V> =
- toMap(LinkedHashMap())
-
-/**
- * Returns a [MutableMap] filled with all elements of this channel.
- *
- * The operation is _terminal_.
- * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel].
- *
- * **Note: This API will become obsolete in future updates with introduction of lazy asynchronous streams.**
- * See [issue #254](https://github.com/Kotlin/kotlinx.coroutines/issues/254).
- */
-@Deprecated(
- message = "Channel operators are deprecated in favour of Flow and will be removed in 1.4.x",
- level = DeprecationLevel.ERROR
-)
-public suspend fun <K, V, M : MutableMap<in K, in V>> ReceiveChannel<Pair<K, V>>.toMap(destination: M): M {
+@OptIn(ExperimentalStdlibApi::class)
+public suspend fun <E> ReceiveChannel<E>.toList(): List<E> = buildList {
consumeEach {
- destination += it
+ add(it)
}
- return destination
}
/**
- * Returns a [MutableList] filled with all elements of this channel.
- *
- * The operation is _terminal_.
- * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel].
- *
- * **Note: This API will become obsolete in future updates with introduction of lazy asynchronous streams.**
- * See [issue #254](https://github.com/Kotlin/kotlinx.coroutines/issues/254).
- */
-@Deprecated(
- message = "Channel operators are deprecated in favour of Flow and will be removed in 1.4.x",
- level = DeprecationLevel.ERROR
-)
-public suspend fun <E> ReceiveChannel<E>.toMutableList(): MutableList<E> =
- toCollection(ArrayList())
-
-/**
- * Returns a [Set] of all elements.
- *
- * The returned set preserves the element iteration order of the original channel.
- *
- * The operation is _terminal_.
- * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel].
- *
- * **Note: This API will become obsolete in future updates with introduction of lazy asynchronous streams.**
- * See [issue #254](https://github.com/Kotlin/kotlinx.coroutines/issues/254).
- */
-@Deprecated(
- message = "Channel operators are deprecated in favour of Flow and will be removed in 1.4.x",
- level = DeprecationLevel.ERROR
-)
-public suspend fun <E> ReceiveChannel<E>.toSet(): Set<E> =
- this.toMutableSet()
-
-/**
- * Returns a single channel of all elements from results of [transform] function being invoked on each element of original channel.
- *
- * The operation is _intermediate_ and _stateless_.
- * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel].
- *
- * **Note: This API will become obsolete in future updates with introduction of lazy asynchronous streams.**
- * See [issue #254](https://github.com/Kotlin/kotlinx.coroutines/issues/254).
- */
-@Deprecated(
- message = "Channel operators are deprecated in favour of Flow and will be removed in 1.4.x",
- level = DeprecationLevel.ERROR
-)
-public fun <E, R> ReceiveChannel<E>.flatMap(context: CoroutineContext = Dispatchers.Unconfined, transform: suspend (E) -> ReceiveChannel<R>): ReceiveChannel<R> =
- GlobalScope.produce(context, onCompletion = consumes()) {
- for (e in this@flatMap) {
- transform(e).toChannel(this)
- }
- }
-
-/**
- * Groups elements of the original channel by the key returned by the given [keySelector] function
- * applied to each element and returns a map where each group key is associated with a list of corresponding elements.
- *
- * The returned map preserves the entry iteration order of the keys produced from the original channel.
- *
- * The operation is _terminal_.
- * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel].
- *
- * **Note: This API will become obsolete in future updates with introduction of lazy asynchronous streams.**
- * See [issue #254](https://github.com/Kotlin/kotlinx.coroutines/issues/254).
- */
-@Deprecated(
- message = "Channel operators are deprecated in favour of Flow and will be removed in 1.4.x",
- level = DeprecationLevel.ERROR
-)
-public suspend inline fun <E, K> ReceiveChannel<E>.groupBy(keySelector: (E) -> K): Map<K, List<E>> =
- groupByTo(LinkedHashMap(), keySelector)
-
-/**
- * Groups values returned by the [valueTransform] function applied to each element of the original channel
- * by the key returned by the given [keySelector] function applied to the element
- * and returns a map where each group key is associated with a list of corresponding values.
- *
- * The returned map preserves the entry iteration order of the keys produced from the original channel.
- *
- * The operation is _terminal_.
- * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel].
- *
- * **Note: This API will become obsolete in future updates with introduction of lazy asynchronous streams.**
- * See [issue #254](https://github.com/Kotlin/kotlinx.coroutines/issues/254).
- */
-@Deprecated(
- message = "Channel operators are deprecated in favour of Flow and will be removed in 1.4.x",
- level = DeprecationLevel.ERROR
-)
-public suspend inline fun <E, K, V> ReceiveChannel<E>.groupBy(keySelector: (E) -> K, valueTransform: (E) -> V): Map<K, List<V>> =
- groupByTo(LinkedHashMap(), keySelector, valueTransform)
-
-/**
- * Groups elements of the original channel by the key returned by the given [keySelector] function
- * applied to each element and puts to the [destination] map each group key associated with a list of corresponding elements.
- *
- * @return The [destination] map.
- *
- * The operation is _terminal_.
- * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel].
- *
- * **Note: This API will become obsolete in future updates with introduction of lazy asynchronous streams.**
- * See [issue #254](https://github.com/Kotlin/kotlinx.coroutines/issues/254).
- */
-@Deprecated(
- message = "Channel operators are deprecated in favour of Flow and will be removed in 1.4.x",
- level = DeprecationLevel.ERROR
-)
-public suspend inline fun <E, K, M : MutableMap<in K, MutableList<E>>> ReceiveChannel<E>.groupByTo(destination: M, keySelector: (E) -> K): M {
- consumeEach {
- val key = keySelector(it)
- val list = destination.getOrPut(key) { ArrayList() }
- list.add(it)
- }
- return destination
-}
-
-/**
- * Groups values returned by the [valueTransform] function applied to each element of the original channel
- * by the key returned by the given [keySelector] function applied to the element
- * and puts to the [destination] map each group key associated with a list of corresponding values.
- *
- * @return The [destination] map.
- *
- * The operation is _terminal_.
- * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel].
- *
- * **Note: This API will become obsolete in future updates with introduction of lazy asynchronous streams.**
- * See [issue #254](https://github.com/Kotlin/kotlinx.coroutines/issues/254).
- */
-@Deprecated(
- message = "Channel operators are deprecated in favour of Flow and will be removed in 1.4.x",
- level = DeprecationLevel.ERROR
-)
-public suspend inline fun <E, K, V, M : MutableMap<in K, MutableList<V>>> ReceiveChannel<E>.groupByTo(destination: M, keySelector: (E) -> K, valueTransform: (E) -> V): M {
- consumeEach {
- val key = keySelector(it)
- val list = destination.getOrPut(key) { ArrayList() }
- list.add(valueTransform(it))
- }
- return destination
-}
-
-/**
- * Returns a channel containing the results of applying the given [transform] function
- * to each element in the original channel.
- *
- * The operation is _intermediate_ and _stateless_.
- * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel].
- */
-@Deprecated(
- message = "Channel operators are deprecated in favour of Flow and will be removed in 1.4.x",
- level = DeprecationLevel.ERROR
-)
-public fun <E, R> ReceiveChannel<E>.map(context: CoroutineContext = Dispatchers.Unconfined, transform: suspend (E) -> R): ReceiveChannel<R> =
- GlobalScope.produce(context, onCompletion = consumes()) {
- consumeEach {
- send(transform(it))
- }
- }
-
-/**
- * Returns a channel containing the results of applying the given [transform] function
- * to each element and its index in the original channel.
- * @param [transform] function that takes the index of an element and the element itself
- * and returns the result of the transform applied to the element.
- *
- * The operation is _intermediate_ and _stateless_.
- * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel].
- *
- * **Note: This API will become obsolete in future updates with introduction of lazy asynchronous streams.**
- * See [issue #254](https://github.com/Kotlin/kotlinx.coroutines/issues/254).
- */
-@Deprecated(
- message = "Channel operators are deprecated in favour of Flow and will be removed in 1.4.x",
- level = DeprecationLevel.ERROR
-)
-public fun <E, R> ReceiveChannel<E>.mapIndexed(context: CoroutineContext = Dispatchers.Unconfined, transform: suspend (index: Int, E) -> R): ReceiveChannel<R> =
- GlobalScope.produce(context, onCompletion = consumes()) {
- var index = 0
- for (e in this@mapIndexed) {
- send(transform(index++, e))
- }
- }
-
-/**
- * Returns a channel containing only the non-null results of applying the given [transform] function
- * to each element and its index in the original channel.
- * @param [transform] function that takes the index of an element and the element itself
- * and returns the result of the transform applied to the element.
- *
- * The operation is _intermediate_ and _stateless_.
- * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel].
- *
- * **Note: This API will become obsolete in future updates with introduction of lazy asynchronous streams.**
- * See [issue #254](https://github.com/Kotlin/kotlinx.coroutines/issues/254).
- */
-@Deprecated(
- message = "Channel operators are deprecated in favour of Flow and will be removed in 1.4.x",
- level = DeprecationLevel.ERROR
-)
-public fun <E, R : Any> ReceiveChannel<E>.mapIndexedNotNull(context: CoroutineContext = Dispatchers.Unconfined, transform: suspend (index: Int, E) -> R?): ReceiveChannel<R> =
- mapIndexed(context, transform).filterNotNull()
-
-/**
- * Applies the given [transform] function to each element and its index in the original channel
- * and appends only the non-null results to the given [destination].
- * @param [transform] function that takes the index of an element and the element itself
- * and returns the result of the transform applied to the element.
- *
- * The operation is _terminal_.
- * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel].
- *
- * **Note: This API will become obsolete in future updates with introduction of lazy asynchronous streams.**
- * See [issue #254](https://github.com/Kotlin/kotlinx.coroutines/issues/254).
- */
-@Deprecated(
- message = "Channel operators are deprecated in favour of Flow and will be removed in 1.4.x",
- level = DeprecationLevel.ERROR
-)
-public suspend inline fun <E, R : Any, C : MutableCollection<in R>> ReceiveChannel<E>.mapIndexedNotNullTo(destination: C, transform: (index: Int, E) -> R?): C {
- consumeEachIndexed { (index, element) ->
- transform(index, element)?.let { destination.add(it) }
- }
- return destination
-}
-
-/**
- * Applies the given [transform] function to each element and its index in the original channel
- * and appends only the non-null results to the given [destination].
- * @param [transform] function that takes the index of an element and the element itself
- * and returns the result of the transform applied to the element.
- *
- * The operation is _terminal_.
- * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel].
- *
- * **Note: This API will become obsolete in future updates with introduction of lazy asynchronous streams.**
- * See [issue #254](https://github.com/Kotlin/kotlinx.coroutines/issues/254).
- */
-@Deprecated(
- message = "Channel operators are deprecated in favour of Flow and will be removed in 1.4.x",
- level = DeprecationLevel.ERROR
-)
-public suspend inline fun <E, R : Any, C : SendChannel<R>> ReceiveChannel<E>.mapIndexedNotNullTo(destination: C, transform: (index: Int, E) -> R?): C {
- consumeEachIndexed { (index, element) ->
- transform(index, element)?.let { destination.send(it) }
- }
- return destination
-}
-
-/**
- * Applies the given [transform] function to each element and its index in the original channel
- * and appends the results to the given [destination].
- * @param [transform] function that takes the index of an element and the element itself
- * and returns the result of the transform applied to the element.
- *
- * The operation is _terminal_.
- * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel].
- *
- * **Note: This API will become obsolete in future updates with introduction of lazy asynchronous streams.**
- * See [issue #254](https://github.com/Kotlin/kotlinx.coroutines/issues/254).
- */
-@Deprecated(
- message = "Channel operators are deprecated in favour of Flow and will be removed in 1.4.x",
- level = DeprecationLevel.ERROR
-)
-public suspend inline fun <E, R, C : MutableCollection<in R>> ReceiveChannel<E>.mapIndexedTo(destination: C, transform: (index: Int, E) -> R): C {
- var index = 0
- consumeEach {
- destination.add(transform(index++, it))
- }
- return destination
-}
-
-/**
- * Applies the given [transform] function to each element and its index in the original channel
- * and appends the results to the given [destination].
- * @param [transform] function that takes the index of an element and the element itself
- * and returns the result of the transform applied to the element.
- *
- * The operation is _terminal_.
- * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel].
- *
- * **Note: This API will become obsolete in future updates with introduction of lazy asynchronous streams.**
- * See [issue #254](https://github.com/Kotlin/kotlinx.coroutines/issues/254).
- */
-@Deprecated(
- message = "Channel operators are deprecated in favour of Flow and will be removed in 1.4.x",
- level = DeprecationLevel.ERROR
-)
-public suspend inline fun <E, R, C : SendChannel<R>> ReceiveChannel<E>.mapIndexedTo(destination: C, transform: (index: Int, E) -> R): C {
- var index = 0
- consumeEach {
- destination.send(transform(index++, it))
- }
- return destination
-}
-
-/**
- * Returns a channel containing only the non-null results of applying the given [transform] function
- * to each element in the original channel.
- *
- * The operation is _intermediate_ and _stateless_.
- * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel].
- *
- * **Note: This API will become obsolete in future updates with introduction of lazy asynchronous streams.**
- * See [issue #254](https://github.com/Kotlin/kotlinx.coroutines/issues/254).
- */
-@Deprecated(
- message = "Channel operators are deprecated in favour of Flow and will be removed in 1.4.x",
- level = DeprecationLevel.ERROR
-)
-public fun <E, R : Any> ReceiveChannel<E>.mapNotNull(context: CoroutineContext = Dispatchers.Unconfined, transform: suspend (E) -> R?): ReceiveChannel<R> =
- map(context, transform).filterNotNull()
-
-/**
- * Applies the given [transform] function to each element in the original channel
- * and appends only the non-null results to the given [destination].
- *
- * The operation is _terminal_.
- * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel].
- *
- * **Note: This API will become obsolete in future updates with introduction of lazy asynchronous streams.**
- * See [issue #254](https://github.com/Kotlin/kotlinx.coroutines/issues/254).
- */
-@Deprecated(
- message = "Channel operators are deprecated in favour of Flow and will be removed in 1.4.x",
- level = DeprecationLevel.ERROR
-)
-public suspend inline fun <E, R : Any, C : MutableCollection<in R>> ReceiveChannel<E>.mapNotNullTo(destination: C, transform: (E) -> R?): C {
- consumeEach {
- transform(it)?.let { destination.add(it) }
- }
- return destination
-}
-
-/**
- * Applies the given [transform] function to each element in the original channel
- * and appends only the non-null results to the given [destination].
- *
- * The operation is _terminal_.
- * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel].
- *
- * **Note: This API will become obsolete in future updates with introduction of lazy asynchronous streams.**
- * See [issue #254](https://github.com/Kotlin/kotlinx.coroutines/issues/254).
- */
-@Deprecated(
- message = "Channel operators are deprecated in favour of Flow and will be removed in 1.4.x",
- level = DeprecationLevel.ERROR
-)
-public suspend inline fun <E, R : Any, C : SendChannel<R>> ReceiveChannel<E>.mapNotNullTo(destination: C, transform: (E) -> R?): C {
- consumeEach {
- transform(it)?.let { destination.send(it) }
- }
- return destination
-}
-
-/**
- * Applies the given [transform] function to each element of the original channel
- * and appends the results to the given [destination].
- *
- * The operation is _terminal_.
- * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel].
- *
- * **Note: This API will become obsolete in future updates with introduction of lazy asynchronous streams.**
- * See [issue #254](https://github.com/Kotlin/kotlinx.coroutines/issues/254).
- */
-@Deprecated(
- message = "Channel operators are deprecated in favour of Flow and will be removed in 1.4.x",
- level = DeprecationLevel.ERROR
-)
-public suspend inline fun <E, R, C : MutableCollection<in R>> ReceiveChannel<E>.mapTo(destination: C, transform: (E) -> R): C {
- consumeEach {
- destination.add(transform(it))
- }
- return destination
-}
-
-/**
- * Applies the given [transform] function to each element of the original channel
- * and appends the results to the given [destination].
- *
- * The operation is _terminal_.
- * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel].
- *
- * **Note: This API will become obsolete in future updates with introduction of lazy asynchronous streams.**
- * See [issue #254](https://github.com/Kotlin/kotlinx.coroutines/issues/254).
- */
-@Deprecated(
- message = "Channel operators are deprecated in favour of Flow and will be removed in 1.4.x",
- level = DeprecationLevel.ERROR
-)
-public suspend inline fun <E, R, C : SendChannel<R>> ReceiveChannel<E>.mapTo(destination: C, transform: (E) -> R): C {
- consumeEach {
- destination.send(transform(it))
- }
- return destination
-}
-
-/**
- * Returns a channel of [IndexedValue] for each element of the original channel.
- *
- * The operation is _intermediate_ and _stateless_.
- * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel].
- *
- * **Note: This API will become obsolete in future updates with introduction of lazy asynchronous streams.**
- * See [issue #254](https://github.com/Kotlin/kotlinx.coroutines/issues/254).
- */
-@Deprecated(
- message = "Channel operators are deprecated in favour of Flow and will be removed in 1.4.x",
- level = DeprecationLevel.ERROR
-)
-public fun <E> ReceiveChannel<E>.withIndex(context: CoroutineContext = Dispatchers.Unconfined): ReceiveChannel<IndexedValue<E>> =
- GlobalScope.produce(context, onCompletion = consumes()) {
- var index = 0
- for (e in this@withIndex) {
- send(IndexedValue(index++, e))
- }
- }
-
-/**
- * Returns a channel containing only distinct elements from the given channel.
- *
- * The elements in the resulting channel are in the same order as they were in the source channel.
- *
- * The operation is _intermediate_ and _stateful_.
- * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel].
- *
- * **Note: This API will become obsolete in future updates with introduction of lazy asynchronous streams.**
- * See [issue #254](https://github.com/Kotlin/kotlinx.coroutines/issues/254).
- */
-@Deprecated(
- message = "Channel operators are deprecated in favour of Flow and will be removed in 1.4.x",
- level = DeprecationLevel.ERROR
-)
-public fun <E> ReceiveChannel<E>.distinct(): ReceiveChannel<E> =
- this.distinctBy { it }
-
-/**
- * Returns a channel containing only elements from the given channel
- * having distinct keys returned by the given [selector] function.
- *
- * The elements in the resulting channel are in the same order as they were in the source channel.
- *
- * The operation is _intermediate_ and _stateful_.
- * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel].
- *
- * **Note: This API will become obsolete in future updates with introduction of lazy asynchronous streams.**
- * See [issue #254](https://github.com/Kotlin/kotlinx.coroutines/issues/254).
- */
-@Deprecated(
- message = "Channel operators are deprecated in favour of Flow and will be removed in 1.4.x",
- level = DeprecationLevel.ERROR
-)
-public fun <E, K> ReceiveChannel<E>.distinctBy(context: CoroutineContext = Dispatchers.Unconfined, selector: suspend (E) -> K): ReceiveChannel<E> =
- GlobalScope.produce(context, onCompletion = consumes()) {
- val keys = HashSet<K>()
- for (e in this@distinctBy) {
- val k = selector(e)
- if (k !in keys) {
- send(e)
- keys += k
- }
- }
- }
-
-/**
- * Returns a mutable set containing all distinct elements from the given channel.
- *
- * The returned set preserves the element iteration order of the original channel.
- *
- * The operation is _terminal_.
- * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel].
- *
- * **Note: This API will become obsolete in future updates with introduction of lazy asynchronous streams.**
- * See [issue #254](https://github.com/Kotlin/kotlinx.coroutines/issues/254).
- */
-@Deprecated(
- message = "Channel operators are deprecated in favour of Flow and will be removed in 1.4.x",
- level = DeprecationLevel.ERROR
-)
-public suspend fun <E> ReceiveChannel<E>.toMutableSet(): MutableSet<E> =
- toCollection(LinkedHashSet())
-
-/**
- * Returns `true` if all elements match the given [predicate].
- *
- * The operation is _terminal_.
- * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel].
- *
- * **Note: This API will become obsolete in future updates with introduction of lazy asynchronous streams.**
- * See [issue #254](https://github.com/Kotlin/kotlinx.coroutines/issues/254).
- */
-@Deprecated(
- message = "Channel operators are deprecated in favour of Flow and will be removed in 1.4.x",
- level = DeprecationLevel.ERROR
-)
-public suspend inline fun <E> ReceiveChannel<E>.all(predicate: (E) -> Boolean): Boolean {
- consumeEach {
- if (!predicate(it)) return false
- }
- return true
-}
-
-/**
- * Returns `true` if channel has at least one element.
- *
- * The operation is _terminal_.
- * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel].
- *
- * **Note: This API will become obsolete in future updates with introduction of lazy asynchronous streams.**
- * See [issue #254](https://github.com/Kotlin/kotlinx.coroutines/issues/254).
- */
-@Deprecated(
- message = "Channel operators are deprecated in favour of Flow and will be removed in 1.4.x",
- level = DeprecationLevel.ERROR
-)
-public suspend fun <E> ReceiveChannel<E>.any(): Boolean =
- consume {
- return iterator().hasNext()
- }
-
-/**
- * Returns `true` if at least one element matches the given [predicate].
- *
- * The operation is _terminal_.
- * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel].
- *
- * **Note: This API will become obsolete in future updates with introduction of lazy asynchronous streams.**
- * See [issue #254](https://github.com/Kotlin/kotlinx.coroutines/issues/254).
- */
-@Deprecated(
- message = "Channel operators are deprecated in favour of Flow and will be removed in 1.4.x",
- level = DeprecationLevel.ERROR
-)
-public suspend inline fun <E> ReceiveChannel<E>.any(predicate: (E) -> Boolean): Boolean {
- consumeEach {
- if (predicate(it)) return true
- }
- return false
-}
-
-/**
- * Returns the number of elements in this channel.
- *
- * The operation is _terminal_.
- * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel].
- *
- * **Note: This API will become obsolete in future updates with introduction of lazy asynchronous streams.**
- * See [issue #254](https://github.com/Kotlin/kotlinx.coroutines/issues/254).
- */
-@Deprecated(
- message = "Channel operators are deprecated in favour of Flow and will be removed in 1.4.x",
- level = DeprecationLevel.ERROR
-)
-public suspend fun <E> ReceiveChannel<E>.count(): Int {
- var count = 0
- consumeEach { count++ }
- return count
-}
-
-/**
- * Returns the number of elements matching the given [predicate].
- *
- * The operation is _terminal_.
- * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel].
- *
- * **Note: This API will become obsolete in future updates with introduction of lazy asynchronous streams.**
- * See [issue #254](https://github.com/Kotlin/kotlinx.coroutines/issues/254).
- */
-@Deprecated(
- message = "Channel operators are deprecated in favour of Flow and will be removed in 1.4.x",
- level = DeprecationLevel.ERROR
-)
-public suspend inline fun <E> ReceiveChannel<E>.count(predicate: (E) -> Boolean): Int {
- var count = 0
- consumeEach {
- if (predicate(it)) count++
- }
- return count
-}
-
-/**
- * Accumulates value starting with [initial] value and applying [operation] from left to right to current accumulator value and each element.
- *
- * The operation is _terminal_.
- * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel].
- *
- * **Note: This API will become obsolete in future updates with introduction of lazy asynchronous streams.**
- * See [issue #254](https://github.com/Kotlin/kotlinx.coroutines/issues/254).
- */
-@Deprecated(
- message = "Channel operators are deprecated in favour of Flow and will be removed in 1.4.x",
- level = DeprecationLevel.ERROR
-)
-public suspend inline fun <E, R> ReceiveChannel<E>.fold(initial: R, operation: (acc: R, E) -> R): R {
- var accumulator = initial
- consumeEach {
- accumulator = operation(accumulator, it)
- }
- return accumulator
-}
-
-/**
- * Accumulates value starting with [initial] value and applying [operation] from left to right
- * to current accumulator value and each element with its index in the original channel.
- * @param [operation] function that takes the index of an element, current accumulator value
- * and the element itself, and calculates the next accumulator value.
- *
- * The operation is _terminal_.
- * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel].
- *
- * **Note: This API will become obsolete in future updates with introduction of lazy asynchronous streams.**
- * See [issue #254](https://github.com/Kotlin/kotlinx.coroutines/issues/254).
- */
-@Deprecated(
- message = "Channel operators are deprecated in favour of Flow and will be removed in 1.4.x",
- level = DeprecationLevel.ERROR
-)
-public suspend inline fun <E, R> ReceiveChannel<E>.foldIndexed(initial: R, operation: (index: Int, acc: R, E) -> R): R {
- var index = 0
- var accumulator = initial
- consumeEach {
- accumulator = operation(index++, accumulator, it)
- }
- return accumulator
-}
-
-/**
- * Returns the first element yielding the largest value of the given function or `null` if there are no elements.
- *
- * The operation is _terminal_.
- * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel].
- *
- * **Note: This API will become obsolete in future updates with introduction of lazy asynchronous streams.**
- * See [issue #254](https://github.com/Kotlin/kotlinx.coroutines/issues/254).
- */
-@Deprecated(
- message = "Channel operators are deprecated in favour of Flow and will be removed in 1.4.x",
- level = DeprecationLevel.ERROR
-)
-public suspend inline fun <E, R : Comparable<R>> ReceiveChannel<E>.maxBy(selector: (E) -> R): E? =
- consume {
- val iterator = iterator()
- if (!iterator.hasNext()) return null
- var maxElem = iterator.next()
- var maxValue = selector(maxElem)
- while (iterator.hasNext()) {
- val e = iterator.next()
- val v = selector(e)
- if (maxValue < v) {
- maxElem = e
- maxValue = v
- }
- }
- return maxElem
- }
-
-/**
- * Returns the first element having the largest value according to the provided [comparator] or `null` if there are no elements.
- *
- * The operation is _terminal_.
- * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel].
- *
- * **Note: This API will become obsolete in future updates with introduction of lazy asynchronous streams.**
- * See [issue #254](https://github.com/Kotlin/kotlinx.coroutines/issues/254).
- */
-@Deprecated(
- message = "Channel operators are deprecated in favour of Flow and will be removed in 1.4.x",
- level = DeprecationLevel.ERROR
-)
-public suspend fun <E> ReceiveChannel<E>.maxWith(comparator: Comparator<in E>): E? =
- consume {
- val iterator = iterator()
- if (!iterator.hasNext()) return null
- var max = iterator.next()
- while (iterator.hasNext()) {
- val e = iterator.next()
- if (comparator.compare(max, e) < 0) max = e
- }
- return max
- }
-
-/**
- * Returns the first element yielding the smallest value of the given function or `null` if there are no elements.
- *
- * The operation is _terminal_.
- * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel].
- *
- * **Note: This API will become obsolete in future updates with introduction of lazy asynchronous streams.**
- * See [issue #254](https://github.com/Kotlin/kotlinx.coroutines/issues/254).
- */
-@Deprecated(
- message = "Channel operators are deprecated in favour of Flow and will be removed in 1.4.x",
- level = DeprecationLevel.ERROR
-)
-public suspend inline fun <E, R : Comparable<R>> ReceiveChannel<E>.minBy(selector: (E) -> R): E? =
- consume {
- val iterator = iterator()
- if (!iterator.hasNext()) return null
- var minElem = iterator.next()
- var minValue = selector(minElem)
- while (iterator.hasNext()) {
- val e = iterator.next()
- val v = selector(e)
- if (minValue > v) {
- minElem = e
- minValue = v
- }
- }
- return minElem
- }
-
-/**
- * Returns the first element having the smallest value according to the provided [comparator] or `null` if there are no elements.
- *
- * The operation is _terminal_.
- * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel].
- *
- * **Note: This API will become obsolete in future updates with introduction of lazy asynchronous streams.**
- * See [issue #254](https://github.com/Kotlin/kotlinx.coroutines/issues/254).
- */
-@Deprecated(
- message = "Channel operators are deprecated in favour of Flow and will be removed in 1.4.x",
- level = DeprecationLevel.ERROR
-)
-public suspend fun <E> ReceiveChannel<E>.minWith(comparator: Comparator<in E>): E? =
- consume {
- val iterator = iterator()
- if (!iterator.hasNext()) return null
- var min = iterator.next()
- while (iterator.hasNext()) {
- val e = iterator.next()
- if (comparator.compare(min, e) > 0) min = e
- }
- return min
- }
-
-/**
- * Returns `true` if the channel has no elements.
- *
- * The operation is _terminal_.
- * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel].
- *
- * **Note: This API will become obsolete in future updates with introduction of lazy asynchronous streams.**
- * See [issue #254](https://github.com/Kotlin/kotlinx.coroutines/issues/254).
- */
-@Deprecated(
- message = "Channel operators are deprecated in favour of Flow and will be removed in 1.4.x",
- level = DeprecationLevel.ERROR
-)
-public suspend fun <E> ReceiveChannel<E>.none(): Boolean =
- consume {
- return !iterator().hasNext()
- }
-
-/**
- * Returns `true` if no elements match the given [predicate].
- *
- * The operation is _terminal_.
- * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel].
- *
- * **Note: This API will become obsolete in future updates with introduction of lazy asynchronous streams.**
- * See [issue #254](https://github.com/Kotlin/kotlinx.coroutines/issues/254).
- */
-@Deprecated(
- message = "Channel operators are deprecated in favour of Flow and will be removed in 1.4.x",
- level = DeprecationLevel.ERROR
-)
-public suspend inline fun <E> ReceiveChannel<E>.none(predicate: (E) -> Boolean): Boolean {
- consumeEach {
- if (predicate(it)) return false
- }
- return true
-}
-
-/**
- * Accumulates value starting with the first element and applying [operation] from left to right to current accumulator value and each element.
- *
- * The operation is _terminal_.
- * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel].
- *
- * **Note: This API will become obsolete in future updates with introduction of lazy asynchronous streams.**
- * See [issue #254](https://github.com/Kotlin/kotlinx.coroutines/issues/254).
- */
-@Deprecated(
- message = "Channel operators are deprecated in favour of Flow and will be removed in 1.4.x",
- level = DeprecationLevel.ERROR
-)
-public suspend inline fun <S, E : S> ReceiveChannel<E>.reduce(operation: (acc: S, E) -> S): S =
- consume {
- val iterator = this.iterator()
- if (!iterator.hasNext()) throw UnsupportedOperationException("Empty channel can't be reduced.")
- var accumulator: S = iterator.next()
- while (iterator.hasNext()) {
- accumulator = operation(accumulator, iterator.next())
- }
- return accumulator
- }
-
-/**
- * Accumulates value starting with the first element and applying [operation] from left to right
- * to current accumulator value and each element with its index in the original channel.
- * @param [operation] function that takes the index of an element, current accumulator value
- * and the element itself and calculates the next accumulator value.
- *
- * The operation is _terminal_.
- * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel].
+ * Subscribes to this [BroadcastChannel] and performs the specified action for each received element.
*
* **Note: This API will become obsolete in future updates with introduction of lazy asynchronous streams.**
* See [issue #254](https://github.com/Kotlin/kotlinx.coroutines/issues/254).
*/
-@Deprecated(
- message = "Channel operators are deprecated in favour of Flow and will be removed in 1.4.x",
- level = DeprecationLevel.ERROR
-)
-public suspend inline fun <S, E : S> ReceiveChannel<E>.reduceIndexed(operation: (index: Int, acc: S, E) -> S): S =
+@ObsoleteCoroutinesApi
+public suspend inline fun <E> BroadcastChannel<E>.consumeEach(action: (E) -> Unit): Unit =
consume {
- val iterator = this.iterator()
- if (!iterator.hasNext()) throw UnsupportedOperationException("Empty channel can't be reduced.")
- var index = 1
- var accumulator: S = iterator.next()
- while (iterator.hasNext()) {
- accumulator = operation(index++, accumulator, iterator.next())
- }
- return accumulator
- }
-
-/**
- * Returns the sum of all values produced by [selector] function applied to each element in the channel.
- *
- * The operation is _terminal_.
- * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel].
- *
- * **Note: This API will become obsolete in future updates with introduction of lazy asynchronous streams.**
- * See [issue #254](https://github.com/Kotlin/kotlinx.coroutines/issues/254).
- */
-@Deprecated(
- message = "Channel operators are deprecated in favour of Flow and will be removed in 1.4.x",
- level = DeprecationLevel.ERROR
-)
-public suspend inline fun <E> ReceiveChannel<E>.sumBy(selector: (E) -> Int): Int {
- var sum = 0
- consumeEach {
- sum += selector(it)
- }
- return sum
-}
-
-/**
- * Returns the sum of all values produced by [selector] function applied to each element in the channel.
- *
- * The operation is _terminal_.
- * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel].
- *
- * **Note: This API will become obsolete in future updates with introduction of lazy asynchronous streams.**
- * See [issue #254](https://github.com/Kotlin/kotlinx.coroutines/issues/254).
- */
-@Deprecated(
- message = "Channel operators are deprecated in favour of Flow and will be removed in 1.4.x",
- level = DeprecationLevel.ERROR
-)
-public suspend inline fun <E> ReceiveChannel<E>.sumByDouble(selector: (E) -> Double): Double {
- var sum = 0.0
- consumeEach {
- sum += selector(it)
+ for (element in this) action(element)
}
- return sum
-}
-/**
- * Returns an original collection containing all the non-`null` elements, throwing an [IllegalArgumentException] if there are any `null` elements.
- *
- * The operation is _intermediate_ and _stateless_.
- * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel].
- *
- * **Note: This API will become obsolete in future updates with introduction of lazy asynchronous streams.**
- * See [issue #254](https://github.com/Kotlin/kotlinx.coroutines/issues/254).
- */
-@Deprecated(
- message = "Channel operators are deprecated in favour of Flow and will be removed in 1.4.x",
- level = DeprecationLevel.ERROR
-)
-public fun <E : Any> ReceiveChannel<E?>.requireNoNulls(): ReceiveChannel<E> =
- map { it ?: throw IllegalArgumentException("null element found in $this.") }
-/**
- * Splits the original channel into pair of lists,
- * where *first* list contains elements for which [predicate] yielded `true`,
- * while *second* list contains elements for which [predicate] yielded `false`.
- *
- * The operation is _terminal_.
- * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel].
- *
- * **Note: This API will become obsolete in future updates with introduction of lazy asynchronous streams.**
- * See [issue #254](https://github.com/Kotlin/kotlinx.coroutines/issues/254).
- */
-@Deprecated(
- message = "Channel operators are deprecated in favour of Flow and will be removed in 1.4.x",
- level = DeprecationLevel.ERROR
-)
-public suspend inline fun <E> ReceiveChannel<E>.partition(predicate: (E) -> Boolean): Pair<List<E>, List<E>> {
- val first = ArrayList<E>()
- val second = ArrayList<E>()
- consumeEach {
- if (predicate(it)) {
- first.add(it)
- } else {
- second.add(it)
- }
- }
- return Pair(first, second)
+@PublishedApi
+internal fun ReceiveChannel<*>.cancelConsumed(cause: Throwable?) {
+ cancel(cause?.let {
+ it as? CancellationException ?: CancellationException("Channel was consumed, consumer had failed", it)
+ })
}
-/**
- * Returns a channel of pairs built from elements of both channels with same indexes.
- * Resulting channel has length of shortest input channel.
- *
- * The operation is _intermediate_ and _stateless_.
- * This function [consumes][ReceiveChannel.consume] all elements of both the original [ReceiveChannel] and the `other` one.
- *
- * **Note: This API will become obsolete in future updates with introduction of lazy asynchronous streams.**
- * See [issue #254](https://github.com/Kotlin/kotlinx.coroutines/issues/254).
- */
-@Deprecated(
- message = "Channel operators are deprecated in favour of Flow and will be removed in 1.4.x",
- level = DeprecationLevel.ERROR
-)
-public infix fun <E, R> ReceiveChannel<E>.zip(other: ReceiveChannel<R>): ReceiveChannel<Pair<E, R>> =
- zip(other) { t1, t2 -> t1 to t2 }
-
-/**
- * Returns a channel of values built from elements of both collections with same indexes using provided [transform]. Resulting channel has length of shortest input channels.
- *
- * The operation is _intermediate_ and _stateless_.
- * This function [consumes][consume] all elements of both the original [ReceiveChannel] and the `other` one.
- *
- * **Note: This API will become obsolete in future updates with introduction of lazy asynchronous streams.**
- * See [issue #254](https://github.com/Kotlin/kotlinx.coroutines/issues/254).
- */
-@Deprecated(
- message = "Channel operators are deprecated in favour of Flow and will be removed in 1.4.x",
- level = DeprecationLevel.ERROR
-)
-public fun <E, R, V> ReceiveChannel<E>.zip(other: ReceiveChannel<R>, context: CoroutineContext = Dispatchers.Unconfined, transform: (a: E, b: R) -> V): ReceiveChannel<V> =
- GlobalScope.produce(context, onCompletion = consumesAll(this, other)) {
- val otherIterator = other.iterator()
- this@zip.consumeEach { element1 ->
- if (!otherIterator.hasNext()) return@consumeEach
- val element2 = otherIterator.next()
- send(transform(element1, element2))
- }
- }
diff --git a/kotlinx-coroutines-core/common/src/channels/ConflatedBroadcastChannel.kt b/kotlinx-coroutines-core/common/src/channels/ConflatedBroadcastChannel.kt
index 5986dae3..b768d7c3 100644
--- a/kotlinx-coroutines-core/common/src/channels/ConflatedBroadcastChannel.kt
+++ b/kotlinx-coroutines-core/common/src/channels/ConflatedBroadcastChannel.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.channels
@@ -17,7 +17,7 @@ import kotlin.jvm.*
* Back-to-send sent elements are _conflated_ -- only the the most recently sent value is received,
* while previously sent elements **are lost**.
* Every subscriber immediately receives the most recently sent element.
- * Sender to this broadcast channel never suspends and [offer] always returns `true`.
+ * Sender to this broadcast channel never suspends and [trySend] always succeeds.
*
* A secondary constructor can be used to create an instance of this class that already holds a value.
* This channel is also created by `BroadcastChannel(Channel.CONFLATED)` factory function invocation.
@@ -26,10 +26,10 @@ import kotlin.jvm.*
* [opening][openSubscription] and [closing][ReceiveChannel.cancel] subscription takes O(N) time, where N is the
* number of subscribers.
*
- * **Note: This API is obsolete.** It will be deprecated and replaced by [StateFlow][kotlinx.coroutines.flow.StateFlow]
- * when it becomes stable.
+ * **Note: This API is obsolete since 1.5.0.** It will be deprecated with warning in 1.6.0
+ * and with error in 1.7.0. It is replaced with [StateFlow][kotlinx.coroutines.flow.StateFlow].
*/
-@ExperimentalCoroutinesApi // not @ObsoleteCoroutinesApi to reduce burden for people who are still using it
+@ObsoleteCoroutinesApi
public class ConflatedBroadcastChannel<E>() : BroadcastChannel<E> {
/**
* Creates an instance of this class that already holds a value.
@@ -94,7 +94,6 @@ public class ConflatedBroadcastChannel<E>() : BroadcastChannel<E> {
}
public override val isClosedForSend: Boolean get() = _state.value is Closed
- public override val isFull: Boolean get() = false
@Suppress("UNCHECKED_CAST")
public override fun openSubscription(): ReceiveChannel<E> {
@@ -229,12 +228,12 @@ public class ConflatedBroadcastChannel<E>() : BroadcastChannel<E> {
/**
* Sends the value to all subscribed receives and stores this value as the most recent state for
- * future subscribers. This implementation always returns `true`.
- * It throws exception if the channel [isClosedForSend] (see [close] for details).
+ * future subscribers. This implementation always returns either successful result
+ * or closed with an exception.
*/
- public override fun offer(element: E): Boolean {
- offerInternal(element)?.let { throw it.sendException }
- return true
+ public override fun trySend(element: E): ChannelResult<Unit> {
+ offerInternal(element)?.let { return ChannelResult.closed(it.sendException) }
+ return ChannelResult.success(Unit)
}
@Suppress("UNCHECKED_CAST")
diff --git a/kotlinx-coroutines-core/common/src/channels/ConflatedChannel.kt b/kotlinx-coroutines-core/common/src/channels/ConflatedChannel.kt
index 75e421c6..f7f60cf9 100644
--- a/kotlinx-coroutines-core/common/src/channels/ConflatedChannel.kt
+++ b/kotlinx-coroutines-core/common/src/channels/ConflatedChannel.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.channels
@@ -9,11 +9,11 @@ import kotlinx.coroutines.internal.*
import kotlinx.coroutines.selects.*
/**
- * Channel that buffers at most one element and conflates all subsequent `send` and `offer` invocations,
+ * Channel that buffers at most one element and conflates all subsequent `send` and `trySend` invocations,
* so that the receiver always gets the most recently sent element.
* Back-to-send sent elements are _conflated_ -- only the most recently sent element is received,
* while previously sent elements **are lost**.
- * Sender to this channel never suspends and [offer] always returns `true`.
+ * Sender to this channel never suspends and [trySend] always succeeds.
*
* This channel is created by `Channel(Channel.CONFLATED)` factory function invocation.
*/
@@ -120,9 +120,10 @@ internal open class ConflatedChannel<E>(onUndeliveredElement: OnUndeliveredEleme
undeliveredElementException = updateValueLocked(EMPTY)
}
super.onCancelIdempotent(wasClosed)
- undeliveredElementException?.let { throw it } // throw exception at the end if there was one
+ undeliveredElementException?.let { throw it } // throw UndeliveredElementException at the end if there was one
}
+ @Suppress("UNCHECKED_CAST")
private fun updateValueLocked(element: Any?): UndeliveredElementException? {
val old = value
val undeliveredElementException = if (old === EMPTY) null else
diff --git a/kotlinx-coroutines-core/common/src/channels/Deprecated.kt b/kotlinx-coroutines-core/common/src/channels/Deprecated.kt
new file mode 100644
index 00000000..2b9ed42d
--- /dev/null
+++ b/kotlinx-coroutines-core/common/src/channels/Deprecated.kt
@@ -0,0 +1,478 @@
+/*
+ * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+@file:JvmMultifileClass
+@file:JvmName("ChannelsKt")
+@file:Suppress("unused")
+
+package kotlinx.coroutines.channels
+
+import kotlinx.coroutines.*
+import kotlin.coroutines.*
+import kotlin.jvm.*
+
+/** @suppress **/
+@PublishedApi // Binary compatibility
+internal fun consumesAll(vararg channels: ReceiveChannel<*>): CompletionHandler =
+ { cause: Throwable? ->
+ var exception: Throwable? = null
+ for (channel in channels)
+ try {
+ channel.cancelConsumed(cause)
+ } catch (e: Throwable) {
+ if (exception == null) {
+ exception = e
+ } else {
+ exception.addSuppressedThrowable(e)
+ }
+ }
+ exception?.let { throw it }
+ }
+
+/** @suppress **/
+@Deprecated(message = "Binary compatibility", level = DeprecationLevel.HIDDEN)
+public suspend fun <E> ReceiveChannel<E>.elementAt(index: Int): E = consume {
+ if (index < 0)
+ throw IndexOutOfBoundsException("ReceiveChannel doesn't contain element at index $index.")
+ var count = 0
+ for (element in this) {
+ @Suppress("UNUSED_CHANGED_VALUE") // KT-47628
+ if (index == count++)
+ return element
+ }
+ throw IndexOutOfBoundsException("ReceiveChannel doesn't contain element at index $index.")
+}
+
+/** @suppress **/
+@Deprecated(message = "Binary compatibility", level = DeprecationLevel.HIDDEN)
+public suspend fun <E> ReceiveChannel<E>.elementAtOrNull(index: Int): E? =
+ consume {
+ if (index < 0)
+ return null
+ var count = 0
+ for (element in this) {
+ if (index == count++)
+ return element
+ }
+ return null
+ }
+
+/** @suppress **/
+@Deprecated(message = "Binary compatibility", level = DeprecationLevel.HIDDEN)
+public suspend fun <E> ReceiveChannel<E>.first(): E =
+ consume {
+ val iterator = iterator()
+ if (!iterator.hasNext())
+ throw NoSuchElementException("ReceiveChannel is empty.")
+ return iterator.next()
+ }
+
+/** @suppress **/
+@Deprecated(message = "Binary compatibility", level = DeprecationLevel.HIDDEN)
+public suspend fun <E> ReceiveChannel<E>.firstOrNull(): E? =
+ consume {
+ val iterator = iterator()
+ if (!iterator.hasNext())
+ return null
+ return iterator.next()
+ }
+
+/** @suppress **/
+@Deprecated(message = "Binary compatibility", level = DeprecationLevel.HIDDEN)
+public suspend fun <E> ReceiveChannel<E>.indexOf(element: E): Int {
+ var index = 0
+ consumeEach {
+ if (element == it)
+ return index
+ index++
+ }
+ return -1
+}
+
+/** @suppress **/
+@Deprecated(message = "Binary compatibility", level = DeprecationLevel.HIDDEN)
+public suspend fun <E> ReceiveChannel<E>.last(): E =
+ consume {
+ val iterator = iterator()
+ if (!iterator.hasNext())
+ throw NoSuchElementException("ReceiveChannel is empty.")
+ var last = iterator.next()
+ while (iterator.hasNext())
+ last = iterator.next()
+ return last
+ }
+
+/** @suppress **/
+@Deprecated(message = "Binary compatibility", level = DeprecationLevel.HIDDEN)
+public suspend fun <E> ReceiveChannel<E>.lastIndexOf(element: E): Int {
+ var lastIndex = -1
+ var index = 0
+ consumeEach {
+ if (element == it)
+ lastIndex = index
+ index++
+ }
+ return lastIndex
+}
+
+/** @suppress **/
+@Deprecated(message = "Binary compatibility", level = DeprecationLevel.HIDDEN)
+public suspend fun <E> ReceiveChannel<E>.lastOrNull(): E? =
+ consume {
+ val iterator = iterator()
+ if (!iterator.hasNext())
+ return null
+ var last = iterator.next()
+ while (iterator.hasNext())
+ last = iterator.next()
+ return last
+ }
+
+/** @suppress **/
+@Deprecated(message = "Binary compatibility", level = DeprecationLevel.HIDDEN)
+public suspend fun <E> ReceiveChannel<E>.single(): E =
+ consume {
+ val iterator = iterator()
+ if (!iterator.hasNext())
+ throw NoSuchElementException("ReceiveChannel is empty.")
+ val single = iterator.next()
+ if (iterator.hasNext())
+ throw IllegalArgumentException("ReceiveChannel has more than one element.")
+ return single
+ }
+
+/** @suppress **/
+@Deprecated(message = "Binary compatibility", level = DeprecationLevel.HIDDEN)
+public suspend fun <E> ReceiveChannel<E>.singleOrNull(): E? =
+ consume {
+ val iterator = iterator()
+ if (!iterator.hasNext())
+ return null
+ val single = iterator.next()
+ if (iterator.hasNext())
+ return null
+ return single
+ }
+
+/** @suppress **/
+@Deprecated(message = "Binary compatibility", level = DeprecationLevel.HIDDEN)
+public fun <E> ReceiveChannel<E>.drop(n: Int, context: CoroutineContext = Dispatchers.Unconfined): ReceiveChannel<E> =
+ GlobalScope.produce(context, onCompletion = consumes()) {
+ require(n >= 0) { "Requested element count $n is less than zero." }
+ var remaining: Int = n
+ if (remaining > 0)
+ for (e in this@drop) {
+ remaining--
+ if (remaining == 0)
+ break
+ }
+ for (e in this@drop) {
+ send(e)
+ }
+ }
+
+/** @suppress **/
+@Deprecated(message = "Binary compatibility", level = DeprecationLevel.HIDDEN)
+public fun <E> ReceiveChannel<E>.dropWhile(
+ context: CoroutineContext = Dispatchers.Unconfined,
+ predicate: suspend (E) -> Boolean
+): ReceiveChannel<E> =
+ GlobalScope.produce(context, onCompletion = consumes()) {
+ for (e in this@dropWhile) {
+ if (!predicate(e)) {
+ send(e)
+ break
+ }
+ }
+ for (e in this@dropWhile) {
+ send(e)
+ }
+ }
+
+@PublishedApi
+internal fun <E> ReceiveChannel<E>.filter(
+ context: CoroutineContext = Dispatchers.Unconfined,
+ predicate: suspend (E) -> Boolean
+): ReceiveChannel<E> =
+ GlobalScope.produce(context, onCompletion = consumes()) {
+ for (e in this@filter) {
+ if (predicate(e)) send(e)
+ }
+ }
+
+/** @suppress **/
+@Deprecated(message = "Binary compatibility", level = DeprecationLevel.HIDDEN)
+public fun <E> ReceiveChannel<E>.filterIndexed(
+ context: CoroutineContext = Dispatchers.Unconfined,
+ predicate: suspend (index: Int, E) -> Boolean
+): ReceiveChannel<E> =
+ GlobalScope.produce(context, onCompletion = consumes()) {
+ var index = 0
+ for (e in this@filterIndexed) {
+ if (predicate(index++, e)) send(e)
+ }
+ }
+
+/** @suppress **/
+@Deprecated(message = "Binary compatibility", level = DeprecationLevel.HIDDEN)
+public fun <E> ReceiveChannel<E>.filterNot(
+ context: CoroutineContext = Dispatchers.Unconfined,
+ predicate: suspend (E) -> Boolean
+): ReceiveChannel<E> =
+ filter(context) { !predicate(it) }
+
+@PublishedApi
+@Suppress("UNCHECKED_CAST")
+internal fun <E : Any> ReceiveChannel<E?>.filterNotNull(): ReceiveChannel<E> =
+ filter { it != null } as ReceiveChannel<E>
+
+/** @suppress **/
+@Deprecated(message = "Binary compatibility", level = DeprecationLevel.HIDDEN)
+public suspend fun <E : Any, C : MutableCollection<in E>> ReceiveChannel<E?>.filterNotNullTo(destination: C): C {
+ consumeEach {
+ if (it != null) destination.add(it)
+ }
+ return destination
+}
+
+/** @suppress **/
+@Deprecated(message = "Binary compatibility", level = DeprecationLevel.HIDDEN)
+public suspend fun <E : Any, C : SendChannel<E>> ReceiveChannel<E?>.filterNotNullTo(destination: C): C {
+ consumeEach {
+ if (it != null) destination.send(it)
+ }
+ return destination
+}
+
+/** @suppress **/
+@Deprecated(message = "Binary compatibility", level = DeprecationLevel.HIDDEN)
+public fun <E> ReceiveChannel<E>.take(n: Int, context: CoroutineContext = Dispatchers.Unconfined): ReceiveChannel<E> =
+ GlobalScope.produce(context, onCompletion = consumes()) {
+ if (n == 0) return@produce
+ require(n >= 0) { "Requested element count $n is less than zero." }
+ var remaining: Int = n
+ for (e in this@take) {
+ send(e)
+ remaining--
+ if (remaining == 0)
+ return@produce
+ }
+ }
+
+/** @suppress **/
+@Deprecated(message = "Binary compatibility", level = DeprecationLevel.HIDDEN)
+public fun <E> ReceiveChannel<E>.takeWhile(
+ context: CoroutineContext = Dispatchers.Unconfined,
+ predicate: suspend (E) -> Boolean
+): ReceiveChannel<E> =
+ GlobalScope.produce(context, onCompletion = consumes()) {
+ for (e in this@takeWhile) {
+ if (!predicate(e)) return@produce
+ send(e)
+ }
+ }
+
+@PublishedApi
+internal suspend fun <E, C : SendChannel<E>> ReceiveChannel<E>.toChannel(destination: C): C {
+ consumeEach {
+ destination.send(it)
+ }
+ return destination
+}
+
+@PublishedApi
+internal suspend fun <E, C : MutableCollection<in E>> ReceiveChannel<E>.toCollection(destination: C): C {
+ consumeEach {
+ destination.add(it)
+ }
+ return destination
+}
+
+/** @suppress **/
+@Deprecated(message = "Binary compatibility", level = DeprecationLevel.HIDDEN)
+public suspend fun <K, V> ReceiveChannel<Pair<K, V>>.toMap(): Map<K, V> =
+ toMap(LinkedHashMap())
+
+@PublishedApi
+internal suspend fun <K, V, M : MutableMap<in K, in V>> ReceiveChannel<Pair<K, V>>.toMap(destination: M): M {
+ consumeEach {
+ destination += it
+ }
+ return destination
+}
+
+/** @suppress **/
+@Deprecated(message = "Binary compatibility", level = DeprecationLevel.HIDDEN)
+public suspend fun <E> ReceiveChannel<E>.toMutableList(): MutableList<E> =
+ toCollection(ArrayList())
+
+/** @suppress **/
+@Deprecated(message = "Binary compatibility", level = DeprecationLevel.HIDDEN)
+public suspend fun <E> ReceiveChannel<E>.toSet(): Set<E> =
+ this.toMutableSet()
+
+/** @suppress **/
+@Deprecated(message = "Binary compatibility", level = DeprecationLevel.HIDDEN)
+public fun <E, R> ReceiveChannel<E>.flatMap(
+ context: CoroutineContext = Dispatchers.Unconfined,
+ transform: suspend (E) -> ReceiveChannel<R>
+): ReceiveChannel<R> =
+ GlobalScope.produce(context, onCompletion = consumes()) {
+ for (e in this@flatMap) {
+ transform(e).toChannel(this)
+ }
+ }
+
+@PublishedApi
+internal fun <E, R> ReceiveChannel<E>.map(
+ context: CoroutineContext = Dispatchers.Unconfined,
+ transform: suspend (E) -> R
+): ReceiveChannel<R> =
+ GlobalScope.produce(context, onCompletion = consumes()) {
+ consumeEach {
+ send(transform(it))
+ }
+ }
+
+@PublishedApi
+internal fun <E, R> ReceiveChannel<E>.mapIndexed(
+ context: CoroutineContext = Dispatchers.Unconfined,
+ transform: suspend (index: Int, E) -> R
+): ReceiveChannel<R> =
+ GlobalScope.produce(context, onCompletion = consumes()) {
+ var index = 0
+ for (e in this@mapIndexed) {
+ send(transform(index++, e))
+ }
+ }
+
+/** @suppress **/
+@Deprecated(message = "Binary compatibility", level = DeprecationLevel.HIDDEN)
+public fun <E, R : Any> ReceiveChannel<E>.mapIndexedNotNull(
+ context: CoroutineContext = Dispatchers.Unconfined,
+ transform: suspend (index: Int, E) -> R?
+): ReceiveChannel<R> =
+ mapIndexed(context, transform).filterNotNull()
+
+/** @suppress **/
+@Deprecated(message = "Binary compatibility", level = DeprecationLevel.HIDDEN)
+public fun <E, R : Any> ReceiveChannel<E>.mapNotNull(
+ context: CoroutineContext = Dispatchers.Unconfined,
+ transform: suspend (E) -> R?
+): ReceiveChannel<R> =
+ map(context, transform).filterNotNull()
+
+/** @suppress **/
+@Deprecated(message = "Binary compatibility", level = DeprecationLevel.HIDDEN)
+public fun <E> ReceiveChannel<E>.withIndex(context: CoroutineContext = Dispatchers.Unconfined): ReceiveChannel<IndexedValue<E>> =
+ GlobalScope.produce(context, onCompletion = consumes()) {
+ var index = 0
+ for (e in this@withIndex) {
+ send(IndexedValue(index++, e))
+ }
+ }
+
+/** @suppress **/
+@Deprecated(message = "Binary compatibility", level = DeprecationLevel.HIDDEN)
+public fun <E> ReceiveChannel<E>.distinct(): ReceiveChannel<E> =
+ this.distinctBy { it }
+
+@PublishedApi
+internal fun <E, K> ReceiveChannel<E>.distinctBy(
+ context: CoroutineContext = Dispatchers.Unconfined,
+ selector: suspend (E) -> K
+): ReceiveChannel<E> =
+ GlobalScope.produce(context, onCompletion = consumes()) {
+ val keys = HashSet<K>()
+ for (e in this@distinctBy) {
+ val k = selector(e)
+ if (k !in keys) {
+ send(e)
+ keys += k
+ }
+ }
+ }
+
+@PublishedApi
+internal suspend fun <E> ReceiveChannel<E>.toMutableSet(): MutableSet<E> =
+ toCollection(LinkedHashSet())
+
+/** @suppress **/
+@Deprecated(message = "Binary compatibility", level = DeprecationLevel.HIDDEN)
+public suspend fun <E> ReceiveChannel<E>.any(): Boolean =
+ consume {
+ return iterator().hasNext()
+ }
+
+/** @suppress **/
+@Deprecated(message = "Binary compatibility", level = DeprecationLevel.HIDDEN)
+public suspend fun <E> ReceiveChannel<E>.count(): Int {
+ var count = 0
+ consumeEach { count++ }
+ return count
+}
+
+/** @suppress **/
+@Deprecated(message = "Binary compatibility", level = DeprecationLevel.HIDDEN)
+public suspend fun <E> ReceiveChannel<E>.maxWith(comparator: Comparator<in E>): E? =
+ consume {
+ val iterator = iterator()
+ if (!iterator.hasNext()) return null
+ var max = iterator.next()
+ while (iterator.hasNext()) {
+ val e = iterator.next()
+ if (comparator.compare(max, e) < 0) max = e
+ }
+ return max
+ }
+
+/** @suppress **/
+@Deprecated(message = "Binary compatibility", level = DeprecationLevel.HIDDEN)
+public suspend fun <E> ReceiveChannel<E>.minWith(comparator: Comparator<in E>): E? =
+ consume {
+ val iterator = iterator()
+ if (!iterator.hasNext()) return null
+ var min = iterator.next()
+ while (iterator.hasNext()) {
+ val e = iterator.next()
+ if (comparator.compare(min, e) > 0) min = e
+ }
+ return min
+ }
+
+/** @suppress **/
+@Deprecated(message = "Binary compatibility", level = DeprecationLevel.HIDDEN)
+public suspend fun <E> ReceiveChannel<E>.none(): Boolean =
+ consume {
+ return !iterator().hasNext()
+ }
+
+/** @suppress **/
+@Deprecated(message = "Left for binary compatibility", level = DeprecationLevel.HIDDEN)
+public fun <E : Any> ReceiveChannel<E?>.requireNoNulls(): ReceiveChannel<E> =
+ map { it ?: throw IllegalArgumentException("null element found in $this.") }
+
+/** @suppress **/
+@Deprecated(message = "Binary compatibility", level = DeprecationLevel.HIDDEN)
+public infix fun <E, R> ReceiveChannel<E>.zip(other: ReceiveChannel<R>): ReceiveChannel<Pair<E, R>> =
+ zip(other) { t1, t2 -> t1 to t2 }
+
+@PublishedApi // Binary compatibility
+internal fun <E, R, V> ReceiveChannel<E>.zip(
+ other: ReceiveChannel<R>,
+ context: CoroutineContext = Dispatchers.Unconfined,
+ transform: (a: E, b: R) -> V
+): ReceiveChannel<V> =
+ GlobalScope.produce(context, onCompletion = consumesAll(this, other)) {
+ val otherIterator = other.iterator()
+ this@zip.consumeEach { element1 ->
+ if (!otherIterator.hasNext()) return@consumeEach
+ val element2 = otherIterator.next()
+ send(transform(element1, element2))
+ }
+ }
+
+@PublishedApi // Binary compatibility
+internal fun ReceiveChannel<*>.consumes(): CompletionHandler = { cause: Throwable? ->
+ cancelConsumed(cause)
+}
diff --git a/kotlinx-coroutines-core/common/src/channels/LinkedListChannel.kt b/kotlinx-coroutines-core/common/src/channels/LinkedListChannel.kt
index 2f464213..b5f607b2 100644
--- a/kotlinx-coroutines-core/common/src/channels/LinkedListChannel.kt
+++ b/kotlinx-coroutines-core/common/src/channels/LinkedListChannel.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.channels
@@ -9,7 +9,7 @@ import kotlinx.coroutines.selects.*
/**
* Channel with linked-list buffer of a unlimited capacity (limited only by available memory).
- * Sender to this channel never suspends and [offer] always returns `true`.
+ * Sender to this channel never suspends and [trySend] always succeeds.
*
* This channel is created by `Channel(Channel.UNLIMITED)` factory function invocation.
*
@@ -58,5 +58,19 @@ internal open class LinkedListChannel<E>(onUndeliveredElement: OnUndeliveredElem
}
}
}
+
+ override fun onCancelIdempotentList(list: InlineList<Send>, closed: Closed<*>) {
+ var undeliveredElementException: UndeliveredElementException? = null
+ list.forEachReversed {
+ when (it) {
+ is SendBuffered<*> -> {
+ @Suppress("UNCHECKED_CAST")
+ undeliveredElementException = onUndeliveredElement?.callUndeliveredElementCatchingException(it.element as E, undeliveredElementException)
+ }
+ else -> it.resumeSendClosed(closed)
+ }
+ }
+ undeliveredElementException?.let { throw it } // throw UndeliveredElementException at the end if there was one
+ }
}
diff --git a/kotlinx-coroutines-core/common/src/channels/Produce.kt b/kotlinx-coroutines-core/common/src/channels/Produce.kt
index 10a15e2a..3342fb6e 100644
--- a/kotlinx-coroutines-core/common/src/channels/Produce.kt
+++ b/kotlinx-coroutines-core/common/src/channels/Produce.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.channels
@@ -139,7 +139,7 @@ internal fun <E> CoroutineScope.produce(
internal open class ProducerCoroutine<E>(
parentContext: CoroutineContext, channel: Channel<E>
-) : ChannelCoroutine<E>(parentContext, channel, active = true), ProducerScope<E> {
+) : ChannelCoroutine<E>(parentContext, channel, true, active = true), ProducerScope<E> {
override val isActive: Boolean
get() = super.isActive
diff --git a/kotlinx-coroutines-core/common/src/channels/RendezvousChannel.kt b/kotlinx-coroutines-core/common/src/channels/RendezvousChannel.kt
index 857a9793..e8ade513 100644
--- a/kotlinx-coroutines-core/common/src/channels/RendezvousChannel.kt
+++ b/kotlinx-coroutines-core/common/src/channels/RendezvousChannel.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.channels
diff --git a/kotlinx-coroutines-core/common/src/flow/Builders.kt b/kotlinx-coroutines-core/common/src/flow/Builders.kt
index 7d84cd21..66b55a90 100644
--- a/kotlinx-coroutines-core/common/src/flow/Builders.kt
+++ b/kotlinx-coroutines-core/common/src/flow/Builders.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
@file:JvmMultifileClass
@@ -234,7 +234,7 @@ public fun <T> flowViaChannel(
* resulting flow to specify a user-defined value and to control what happens when data is produced faster
* than consumed, i.e. to control the back-pressure behavior.
*
- * Adjacent applications of [channelFlow], [flowOn], [buffer], [produceIn], and [broadcastIn] are
+ * Adjacent applications of [channelFlow], [flowOn], [buffer], and [produceIn] are
* always fused so that only one properly configured channel is used for execution.
*
* Examples of usage:
@@ -261,7 +261,6 @@ public fun <T> flowViaChannel(
* }
* ```
*/
-@ExperimentalCoroutinesApi
public fun <T> channelFlow(@BuilderInference block: suspend ProducerScope<T>.() -> Unit): Flow<T> =
ChannelFlowBuilder(block)
@@ -290,10 +289,10 @@ public fun <T> channelFlow(@BuilderInference block: suspend ProducerScope<T>.()
* resulting flow to specify a user-defined value and to control what happens when data is produced faster
* than consumed, i.e. to control the back-pressure behavior.
*
- * Adjacent applications of [callbackFlow], [flowOn], [buffer], [produceIn], and [broadcastIn] are
+ * Adjacent applications of [callbackFlow], [flowOn], [buffer], and [produceIn] are
* always fused so that only one properly configured channel is used for execution.
*
- * Example of usage that converts a multi-short callback API to a flow.
+ * Example of usage that converts a multi-shot callback API to a flow.
* For single-shot callbacks use [suspendCancellableCoroutine].
*
* ```
@@ -302,11 +301,10 @@ public fun <T> channelFlow(@BuilderInference block: suspend ProducerScope<T>.()
* override fun onNextValue(value: T) {
* // To avoid blocking you can configure channel capacity using
* // either buffer(Channel.CONFLATED) or buffer(Channel.UNLIMITED) to avoid overfill
- * try {
- * sendBlocking(value)
- * } catch (e: Exception) {
- * // Handle exception from the channel: failure in flow or premature closing
- * }
+ * trySendBlocking(value)
+ * .onFailure { throwable ->
+ * // Downstream has been cancelled or failed, can log here
+ * }
* }
* override fun onApiError(cause: Throwable) {
* cancel(CancellationException("API Error", cause))
@@ -327,7 +325,6 @@ public fun <T> channelFlow(@BuilderInference block: suspend ProducerScope<T>.()
* > `awaitClose` block can be called at any time due to asynchronous nature of cancellation, even
* > concurrently with the call of the callback.
*/
-@ExperimentalCoroutinesApi
public fun <T> callbackFlow(@BuilderInference block: suspend ProducerScope<T>.() -> Unit): Flow<T> = CallbackFlowBuilder(block)
// ChannelFlow implementation that is the first in the chain of flow operations and introduces (builds) a flow
diff --git a/kotlinx-coroutines-core/common/src/flow/Channels.kt b/kotlinx-coroutines-core/common/src/flow/Channels.kt
index 762cdcad..382953ef 100644
--- a/kotlinx-coroutines-core/common/src/flow/Channels.kt
+++ b/kotlinx-coroutines-core/common/src/flow/Channels.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
@file:JvmMultifileClass
@@ -30,7 +30,8 @@ public suspend fun <T> FlowCollector<T>.emitAll(channel: ReceiveChannel<T>): Uni
emitAllImpl(channel, consume = true)
private suspend fun <T> FlowCollector<T>.emitAllImpl(channel: ReceiveChannel<T>, consume: Boolean) {
- // Manually inlined "consumeEach" implementation that does not use iterator but works via "receiveOrClosed".
+ ensureActive()
+ // Manually inlined "consumeEach" implementation that does not use iterator but works via "receiveCatching".
// It has smaller and more efficient spilled state which also allows to implement a manual kludge to
// fix retention of the last emitted value.
// See https://youtrack.jetbrains.com/issue/KT-16222
@@ -47,9 +48,9 @@ private suspend fun <T> FlowCollector<T>.emitAllImpl(channel: ReceiveChannel<T>,
// L$1 <- channel
// L$2 <- cause
// L$3 <- this$run (actually equal to this)
- val result = run { channel.receiveOrClosed() }
+ val result = run { channel.receiveCatching() }
if (result.isClosed) {
- result.closeCause?.let { throw it }
+ result.exceptionOrNull()?.let { throw it }
break // returns normally when result.closeCause == null
}
// result is spilled here to the coroutine state and retained after the call, even though
@@ -58,7 +59,7 @@ private suspend fun <T> FlowCollector<T>.emitAllImpl(channel: ReceiveChannel<T>,
// L$1 <- channel
// L$2 <- cause
// L$3 <- result
- emit(result.value)
+ emit(result.getOrThrow())
}
} catch (e: Throwable) {
cause = e
@@ -133,17 +134,12 @@ private class ChannelAsFlow<T>(
override fun create(context: CoroutineContext, capacity: Int, onBufferOverflow: BufferOverflow): ChannelFlow<T> =
ChannelAsFlow(channel, consume, context, capacity, onBufferOverflow)
- override fun dropChannelOperators(): Flow<T>? =
+ override fun dropChannelOperators(): Flow<T> =
ChannelAsFlow(channel, consume)
override suspend fun collectTo(scope: ProducerScope<T>) =
SendingCollector(scope).emitAllImpl(channel, consume) // use efficient channel receiving code from emitAll
- override fun broadcastImpl(scope: CoroutineScope, start: CoroutineStart): BroadcastChannel<T> {
- markConsumed() // fail fast on repeated attempt to collect it
- return super.broadcastImpl(scope, start)
- }
-
override fun produceImpl(scope: CoroutineScope): ReceiveChannel<T> {
markConsumed() // fail fast on repeated attempt to collect it
return if (capacity == Channel.OPTIONAL_CHANNEL) {
@@ -173,26 +169,20 @@ private class ChannelAsFlow<T>(
* 2) Flow consumer completes normally when the original channel completes (~is closed) normally.
* 3) If the flow consumer fails with an exception, subscription is cancelled.
*/
-@FlowPreview
+@Deprecated(
+ level = DeprecationLevel.WARNING,
+ message = "'BroadcastChannel' is obsolete and all corresponding operators are deprecated " +
+ "in the favour of StateFlow and SharedFlow"
+) // Since 1.5.0, was @FlowPreview, safe to remove in 1.7.0
public fun <T> BroadcastChannel<T>.asFlow(): Flow<T> = flow {
emitAll(openSubscription())
}
/**
- * Creates a [broadcast] coroutine that collects the given flow.
- *
- * This transformation is **stateful**, it launches a [broadcast] coroutine
- * that collects the given flow and thus resulting channel should be properly closed or cancelled.
- *
- * A channel with [default][Channel.Factory.BUFFERED] buffer size is created.
- * Use [buffer] operator on the flow before calling `broadcastIn` to specify a value other than
- * default and to control what happens when data is produced faster than it is consumed,
- * that is to control backpressure behavior.
- *
* ### Deprecated
*
* **This API is deprecated.** The [BroadcastChannel] provides a complex channel-like API for hot flows.
- * [SharedFlow] is a easier-to-use and more flow-centric API for the same purposes, so using
+ * [SharedFlow] is an easier-to-use and more flow-centric API for the same purposes, so using
* [shareIn] operator is preferred. It is not a direct replacement, so please
* study [shareIn] documentation to see what kind of shared flow fits your use-case. As a rule of thumb:
*
@@ -201,14 +191,27 @@ public fun <T> BroadcastChannel<T>.asFlow(): Flow<T> = flow {
*/
@Deprecated(
message = "Use shareIn operator and the resulting SharedFlow as a replacement for BroadcastChannel",
- replaceWith = ReplaceWith("shareIn(scope, 0, SharingStarted.Lazily)"),
- level = DeprecationLevel.WARNING
-)
+ replaceWith = ReplaceWith("this.shareIn(scope, SharingStarted.Lazily, 0)"),
+ level = DeprecationLevel.ERROR
+) // WARNING in 1.4.0, error in 1.5.0, removed in 1.6.0 (was @FlowPreview)
public fun <T> Flow<T>.broadcastIn(
scope: CoroutineScope,
start: CoroutineStart = CoroutineStart.LAZY
-): BroadcastChannel<T> =
- asChannelFlow().broadcastImpl(scope, start)
+): BroadcastChannel<T> {
+ // Backwards compatibility with operator fusing
+ val channelFlow = asChannelFlow()
+ val capacity = when (channelFlow.onBufferOverflow) {
+ BufferOverflow.SUSPEND -> channelFlow.produceCapacity
+ BufferOverflow.DROP_OLDEST -> Channel.CONFLATED
+ BufferOverflow.DROP_LATEST ->
+ throw IllegalArgumentException("Broadcast channel does not support BufferOverflow.DROP_LATEST")
+ }
+ return scope.broadcast(channelFlow.context, capacity = capacity, start = start) {
+ collect { value ->
+ send(value)
+ }
+ }
+}
/**
* Creates a [produce] coroutine that collects the given flow.
diff --git a/kotlinx-coroutines-core/common/src/flow/Flow.kt b/kotlinx-coroutines-core/common/src/flow/Flow.kt
index 19a5b43f..0ccd343e 100644
--- a/kotlinx-coroutines-core/common/src/flow/Flow.kt
+++ b/kotlinx-coroutines-core/common/src/flow/Flow.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.flow
@@ -101,7 +101,7 @@ import kotlin.coroutines.*
* From the implementation point of view, it means that all flow implementations should
* only emit from the same coroutine.
* This constraint is efficiently enforced by the default [flow] builder.
- * The [flow] builder should be used if flow implementation does not start any coroutines.
+ * The [flow] builder should be used if the flow implementation does not start any coroutines.
* Its implementation prevents most of the development mistakes:
*
* ```
diff --git a/kotlinx-coroutines-core/common/src/flow/FlowCollector.kt b/kotlinx-coroutines-core/common/src/flow/FlowCollector.kt
index 8c6208bf..d1c1565c 100644
--- a/kotlinx-coroutines-core/common/src/flow/FlowCollector.kt
+++ b/kotlinx-coroutines-core/common/src/flow/FlowCollector.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.flow
diff --git a/kotlinx-coroutines-core/common/src/flow/Migration.kt b/kotlinx-coroutines-core/common/src/flow/Migration.kt
index 11969a48..6278081a 100644
--- a/kotlinx-coroutines-core/common/src/flow/Migration.kt
+++ b/kotlinx-coroutines-core/common/src/flow/Migration.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
@file:JvmMultifileClass
@@ -344,13 +344,13 @@ public fun <T> Flow<T>.concatWith(value: T): Flow<T> = noImpl()
/**
* Flow analogue of `concatWith` is [onCompletion].
- * Use `onCompletion { emitAll(other) }`.
+ * Use `onCompletion { if (it == null) emitAll(other) }`.
* @suppress
*/
@Deprecated(
level = DeprecationLevel.ERROR,
- message = "Flow analogue of 'concatWith' is 'onCompletion'. Use 'onCompletion { emitAll(other) }'",
- replaceWith = ReplaceWith("onCompletion { emitAll(other) }")
+ message = "Flow analogue of 'concatWith' is 'onCompletion'. Use 'onCompletion { if (it == null) emitAll(other) }'",
+ replaceWith = ReplaceWith("onCompletion { if (it == null) emitAll(other) }")
)
public fun <T> Flow<T>.concatWith(other: Flow<T>): Flow<T> = noImpl()
@@ -404,7 +404,7 @@ public fun <T1, T2, T3, T4, T5, R> Flow<T1>.combineLatest(
* @suppress
*/
@Deprecated(
- level = DeprecationLevel.WARNING, // since 1.3.0, error in 1.4.0
+ level = DeprecationLevel.ERROR, // since 1.3.0, error in 1.5.0
message = "Use 'onStart { delay(timeMillis) }'",
replaceWith = ReplaceWith("onStart { delay(timeMillis) }")
)
@@ -416,7 +416,7 @@ public fun <T> Flow<T>.delayFlow(timeMillis: Long): Flow<T> = onStart { delay(ti
* @suppress
*/
@Deprecated(
- level = DeprecationLevel.WARNING, // since 1.3.0, error in 1.4.0
+ level = DeprecationLevel.ERROR, // since 1.3.0, error in 1.5.0
message = "Use 'onEach { delay(timeMillis) }'",
replaceWith = ReplaceWith("onEach { delay(timeMillis) }")
)
@@ -430,7 +430,7 @@ public fun <T> Flow<T>.delayEach(timeMillis: Long): Flow<T> = onEach { delay(tim
public fun <T, R> Flow<T>.switchMap(transform: suspend (value: T) -> Flow<R>): Flow<R> = flatMapLatest(transform)
@Deprecated(
- level = DeprecationLevel.WARNING, // Since 1.3.8, was experimental when deprecated
+ level = DeprecationLevel.ERROR, // Warning since 1.3.8, was experimental when deprecated, ERROR since 1.5.0
message = "'scanReduce' was renamed to 'runningReduce' to be consistent with Kotlin standard library",
replaceWith = ReplaceWith("runningReduce(operation)")
)
diff --git a/kotlinx-coroutines-core/common/src/flow/SharedFlow.kt b/kotlinx-coroutines-core/common/src/flow/SharedFlow.kt
index feb27495..d79e2034 100644
--- a/kotlinx-coroutines-core/common/src/flow/SharedFlow.kt
+++ b/kotlinx-coroutines-core/common/src/flow/SharedFlow.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.flow
@@ -33,7 +33,7 @@ import kotlin.native.concurrent.*
*
* [SharedFlow] is useful for broadcasting events that happen inside an application to subscribers that can come and go.
* For example, the following class encapsulates an event bus that distributes events to all subscribers
- * in a _rendezvous_ manner, suspending until all subscribers process each event:
+ * in a _rendezvous_ manner, suspending until all subscribers receive emitted event:
*
* ```
* class EventBus {
@@ -68,10 +68,26 @@ import kotlin.native.concurrent.*
* the `onBufferOverflow` parameter, which is equal to one of the entries of the [BufferOverflow] enum. When a strategy other
* than [SUSPENDED][BufferOverflow.SUSPEND] is configured, emissions to the shared flow never suspend.
*
+ * **Buffer overflow condition can happen only when there is at least one subscriber that is not ready to accept
+ * the new value.** In the absence of subscribers only the most recent `replay` values are stored and the buffer
+ * overflow behavior is never triggered and has no effect. In particular, in the absence of subscribers emitter never
+ * suspends despite [BufferOverflow.SUSPEND] option and [BufferOverflow.DROP_LATEST] option does not have effect either.
+ * Essentially, the behavior in the absence of subscribers is always similar to [BufferOverflow.DROP_OLDEST],
+ * but the buffer is just of `replay` size (without any `extraBufferCapacity`).
+ *
+ * ### Unbuffered shared flow
+ *
+ * A default implementation of a shared flow that is created with `MutableSharedFlow()` constructor function
+ * without parameters has no replay cache nor additional buffer.
+ * [emit][MutableSharedFlow.emit] call to such a shared flow suspends until all subscribers receive the emitted value
+ * and returns immediately if there are no subscribers.
+ * Thus, [tryEmit][MutableSharedFlow.tryEmit] call succeeds and returns `true` only if
+ * there are no subscribers (in which case the emitted value is immediately lost).
+ *
* ### SharedFlow vs BroadcastChannel
*
* Conceptually shared flow is similar to [BroadcastChannel][BroadcastChannel]
- * and is designed to completely replace `BroadcastChannel` in the future.
+ * and is designed to completely replace it.
* It has the following important differences:
*
* * `SharedFlow` is simpler, because it does not have to implement all the [Channel] APIs, which allows
@@ -83,7 +99,7 @@ import kotlin.native.concurrent.*
*
* To migrate [BroadcastChannel] usage to [SharedFlow], start by replacing usages of the `BroadcastChannel(capacity)`
* constructor with `MutableSharedFlow(0, extraBufferCapacity=capacity)` (broadcast channel does not replay
- * values to new subscribers). Replace [send][BroadcastChannel.send] and [offer][BroadcastChannel.offer] calls
+ * values to new subscribers). Replace [send][BroadcastChannel.send] and [trySend][BroadcastChannel.trySend] calls
* with [emit][MutableStateFlow.emit] and [tryEmit][MutableStateFlow.tryEmit], and convert subscribers' code to flow operators.
*
* ### Concurrency
@@ -139,6 +155,17 @@ public interface SharedFlow<out T> : Flow<T> {
*/
public interface MutableSharedFlow<T> : SharedFlow<T>, FlowCollector<T> {
/**
+ * Emits a [value] to this shared flow, suspending on buffer overflow if the shared flow was created
+ * with the default [BufferOverflow.SUSPEND] strategy.
+ *
+ * See [tryEmit] for a non-suspending variant of this function.
+ *
+ * This method is **thread-safe** and can be safely invoked from concurrent coroutines without
+ * external synchronization.
+ */
+ override suspend fun emit(value: T)
+
+ /**
* Tries to emit a [value] to this shared flow without suspending. It returns `true` if the value was
* emitted successfully. When this function returns `false`, it means that the call to a plain [emit]
* function will suspend until there is a buffer space available.
@@ -146,6 +173,9 @@ public interface MutableSharedFlow<T> : SharedFlow<T>, FlowCollector<T> {
* A shared flow configured with a [BufferOverflow] strategy other than [SUSPEND][BufferOverflow.SUSPEND]
* (either [DROP_OLDEST][BufferOverflow.DROP_OLDEST] or [DROP_LATEST][BufferOverflow.DROP_LATEST]) never
* suspends on [emit], and thus `tryEmit` to such a shared flow always returns `true`.
+ *
+ * This method is **thread-safe** and can be safely invoked from concurrent coroutines without
+ * external synchronization.
*/
public fun tryEmit(value: T): Boolean
@@ -181,6 +211,9 @@ public interface MutableSharedFlow<T> : SharedFlow<T>, FlowCollector<T> {
* supported, and throws an [UnsupportedOperationException]. To reset a [MutableStateFlow]
* to an initial value, just update its [value][MutableStateFlow.value].
*
+ * This method is **thread-safe** and can be safely invoked from concurrent coroutines without
+ * external synchronization.
+ *
* **Note: This is an experimental api.** This function may be removed or renamed in the future.
*/
@ExperimentalCoroutinesApi
@@ -195,9 +228,12 @@ public interface MutableSharedFlow<T> : SharedFlow<T>, FlowCollector<T> {
* @param replay the number of values replayed to new subscribers (cannot be negative, defaults to zero).
* @param extraBufferCapacity the number of values buffered in addition to `replay`.
* [emit][MutableSharedFlow.emit] does not suspend while there is a buffer space remaining (optional, cannot be negative, defaults to zero).
- * @param onBufferOverflow configures an action on buffer overflow (optional, defaults to
- * [suspending][BufferOverflow.SUSPEND] attempts to [emit][MutableSharedFlow.emit] a value,
- * supported only when `replay > 0` or `extraBufferCapacity > 0`).
+ * @param onBufferOverflow configures an [emit][MutableSharedFlow.emit] action on buffer overflow. Optional, defaults to
+ * [suspending][BufferOverflow.SUSPEND] attempts to emit a value.
+ * Values other than [BufferOverflow.SUSPEND] are supported only when `replay > 0` or `extraBufferCapacity > 0`.
+ * **Buffer overflow can happen only when there is at least one subscriber that is not ready to accept
+ * the new value.** In the absence of subscribers only the most recent [replay] values are stored and
+ * the buffer overflow behavior is never triggered and has no effect.
*/
@Suppress("FunctionName", "UNCHECKED_CAST")
public fun <T> MutableSharedFlow(
@@ -433,7 +469,7 @@ private class SharedFlowImpl<T>(
// outside of the lock: register dispose on cancellation
emitter?.let { cont.disposeOnCancellation(it) }
// outside of the lock: resume slots if needed
- for (cont in resumes) cont?.resume(Unit)
+ for (r in resumes) r?.resume(Unit)
}
private fun cancelEmitter(emitter: Emitter) = synchronized(this) {
diff --git a/kotlinx-coroutines-core/common/src/flow/SharingStarted.kt b/kotlinx-coroutines-core/common/src/flow/SharingStarted.kt
index 19e5fa36..f4c6f2ee 100644
--- a/kotlinx-coroutines-core/common/src/flow/SharingStarted.kt
+++ b/kotlinx-coroutines-core/common/src/flow/SharingStarted.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.flow
@@ -38,7 +38,7 @@ public enum class SharingCommand {
/**
* A strategy for starting and stopping the sharing coroutine in [shareIn] and [stateIn] operators.
*
- * This interface provides a set of built-in strategies: [Eagerly], [Lazily], [WhileSubscribed], and
+ * This functional interface provides a set of built-in strategies: [Eagerly], [Lazily], [WhileSubscribed], and
* supports custom strategies by implementing this interface's [command] function.
*
* For example, it is possible to define a custom strategy that starts the upstream only when the number
@@ -46,11 +46,9 @@ public enum class SharingCommand {
* that it looks like a built-in strategy on the use-site:
*
* ```
- * fun SharingStarted.Companion.WhileSubscribedAtLeast(threshold: Int): SharingStarted =
- * object : SharingStarted {
- * override fun command(subscriptionCount: StateFlow<Int>): Flow<SharingCommand> =
- * subscriptionCount
- * .map { if (it >= threshold) SharingCommand.START else SharingCommand.STOP }
+ * fun SharingStarted.Companion.WhileSubscribedAtLeast(threshold: Int) =
+ * SharingStarted { subscriptionCount: StateFlow<Int> ->
+ * subscriptionCount.map { if (it >= threshold) SharingCommand.START else SharingCommand.STOP }
* }
* ```
*
@@ -60,7 +58,7 @@ public enum class SharingCommand {
* [`command`][command] flow implementation function. Back-to-back emissions of the same command have no effect.
* Only emission of a different command has effect:
*
- * * [START][SharingCommand.START] &mdash; the upstream flow is stared.
+ * * [START][SharingCommand.START] &mdash; the upstream flow is started.
* * [STOP][SharingCommand.STOP] &mdash; the upstream flow is stopped.
* * [STOP_AND_RESET_REPLAY_CACHE][SharingCommand.STOP_AND_RESET_REPLAY_CACHE] &mdash;
* the upstream flow is stopped and the [SharedFlow.replayCache] is reset to its initial state.
@@ -74,7 +72,7 @@ public enum class SharingCommand {
* The completion of the `command` flow normally has no effect (the upstream flow keeps running if it was running).
* The failure of the `command` flow cancels the sharing coroutine and the upstream flow.
*/
-public interface SharingStarted {
+public fun interface SharingStarted {
public companion object {
/**
* Sharing is started immediately and never stops.
@@ -142,7 +140,7 @@ public fun SharingStarted.Companion.WhileSubscribed(
stopTimeout: Duration = Duration.ZERO,
replayExpiration: Duration = Duration.INFINITE
): SharingStarted =
- StartedWhileSubscribed(stopTimeout.toLongMilliseconds(), replayExpiration.toLongMilliseconds())
+ StartedWhileSubscribed(stopTimeout.inWholeMilliseconds, replayExpiration.inWholeMilliseconds)
// -------------------------------- implementation --------------------------------
diff --git a/kotlinx-coroutines-core/common/src/flow/StateFlow.kt b/kotlinx-coroutines-core/common/src/flow/StateFlow.kt
index a9a4ed3d..9e82e787 100644
--- a/kotlinx-coroutines-core/common/src/flow/StateFlow.kt
+++ b/kotlinx-coroutines-core/common/src/flow/StateFlow.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.flow
@@ -21,7 +21,7 @@ import kotlin.native.concurrent.*
* neither does a coroutine started by the [Flow.launchIn] function. An active collector of a state flow is called a _subscriber_.
*
* A [mutable state flow][MutableStateFlow] is created using `MutableStateFlow(value)` constructor function with
- * the initial value. The value of mutable state flow can be updated by setting its [value] property.
+ * the initial value. The value of mutable state flow can be updated by setting its [value] property.
* Updates to the [value] are always [conflated][Flow.conflate]. So a slow collector skips fast updates,
* but always collects the most recently emitted value.
*
@@ -37,7 +37,7 @@ import kotlin.native.concurrent.*
* val counter = _counter.asStateFlow() // publicly exposed as read-only state flow
*
* fun inc() {
- * _counter.value++
+ * _counter.update { count -> count + 1 } // atomic, safe for concurrent use
* }
* }
* ```
@@ -88,7 +88,7 @@ import kotlin.native.concurrent.*
* ### StateFlow vs ConflatedBroadcastChannel
*
* Conceptually, state flow is similar to [ConflatedBroadcastChannel]
- * and is designed to completely replace `ConflatedBroadcastChannel` in the future.
+ * and is designed to completely replace it.
* It has the following important differences:
*
* * `StateFlow` is simpler, because it does not have to implement all the [Channel] APIs, which allows
@@ -107,7 +107,7 @@ import kotlin.native.concurrent.*
*
* To migrate [ConflatedBroadcastChannel] usage to [StateFlow], start by replacing usages of the `ConflatedBroadcastChannel()`
* constructor with `MutableStateFlow(initialValue)`, using `null` as an initial value if you don't have one.
- * Replace [send][ConflatedBroadcastChannel.send] and [offer][ConflatedBroadcastChannel.offer] calls
+ * Replace [send][ConflatedBroadcastChannel.send] and [trySend][ConflatedBroadcastChannel.trySend] calls
* with updates to the state flow's [MutableStateFlow.value], and convert subscribers' code to flow operators.
* You can use the [filterNotNull] operator to mimic behavior of a `ConflatedBroadcastChannel` without initial value.
*
@@ -160,6 +160,9 @@ public interface MutableStateFlow<T> : StateFlow<T>, MutableSharedFlow<T> {
* The current value of this state flow.
*
* Setting a value that is [equal][Any.equals] to the previous one does nothing.
+ *
+ * This property is **thread-safe** and can be safely updated from concurrent coroutines without
+ * external synchronization.
*/
public override var value: T
@@ -170,6 +173,9 @@ public interface MutableStateFlow<T> : StateFlow<T>, MutableSharedFlow<T> {
* This function use a regular comparison using [Any.equals]. If both [expect] and [update] are equal to the
* current [value], this function returns `true`, but it does not actually change the reference that is
* stored in the [value].
+ *
+ * This method is **thread-safe** and can be safely invoked from concurrent coroutines without
+ * external synchronization.
*/
public fun compareAndSet(expect: T, update: T): Boolean
}
@@ -180,6 +186,56 @@ public interface MutableStateFlow<T> : StateFlow<T>, MutableSharedFlow<T> {
@Suppress("FunctionName")
public fun <T> MutableStateFlow(value: T): MutableStateFlow<T> = StateFlowImpl(value ?: NULL)
+// ------------------------------------ Update methods ------------------------------------
+
+/**
+ * Updates the [MutableStateFlow.value] atomically using the specified [function] of its value, and returns the new
+ * value.
+ *
+ * [function] may be evaluated multiple times, if [value] is being concurrently updated.
+ */
+public inline fun <T> MutableStateFlow<T>.updateAndGet(function: (T) -> T): T {
+ while (true) {
+ val prevValue = value
+ val nextValue = function(prevValue)
+ if (compareAndSet(prevValue, nextValue)) {
+ return nextValue
+ }
+ }
+}
+
+/**
+ * Updates the [MutableStateFlow.value] atomically using the specified [function] of its value, and returns its
+ * prior value.
+ *
+ * [function] may be evaluated multiple times, if [value] is being concurrently updated.
+ */
+public inline fun <T> MutableStateFlow<T>.getAndUpdate(function: (T) -> T): T {
+ while (true) {
+ val prevValue = value
+ val nextValue = function(prevValue)
+ if (compareAndSet(prevValue, nextValue)) {
+ return prevValue
+ }
+ }
+}
+
+
+/**
+ * Updates the [MutableStateFlow.value] atomically using the specified [function] of its value.
+ *
+ * [function] may be evaluated multiple times, if [value] is being concurrently updated.
+ */
+public inline fun <T> MutableStateFlow<T>.update(function: (T) -> T) {
+ while (true) {
+ val prevValue = value
+ val nextValue = function(prevValue)
+ if (compareAndSet(prevValue, nextValue)) {
+ return
+ }
+ }
+}
+
// ------------------------------------ Implementation ------------------------------------
@SharedImmutable
@@ -360,10 +416,7 @@ private class StateFlowImpl<T>(
}
internal fun MutableStateFlow<Int>.increment(delta: Int) {
- while (true) { // CAS loop
- val current = value
- if (compareAndSet(current, current + delta)) return
- }
+ update { it + delta }
}
internal fun <T> StateFlow<T>.fuseStateFlow(
diff --git a/kotlinx-coroutines-core/common/src/flow/internal/AbstractSharedFlow.kt b/kotlinx-coroutines-core/common/src/flow/internal/AbstractSharedFlow.kt
index ccb53430..7114cc08 100644
--- a/kotlinx-coroutines-core/common/src/flow/internal/AbstractSharedFlow.kt
+++ b/kotlinx-coroutines-core/common/src/flow/internal/AbstractSharedFlow.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.flow.internal
@@ -98,4 +98,4 @@ internal abstract class AbstractSharedFlow<S : AbstractSharedFlowSlot<*>> : Sync
if (slot != null) block(slot)
}
}
-} \ No newline at end of file
+}
diff --git a/kotlinx-coroutines-core/common/src/flow/internal/ChannelFlow.kt b/kotlinx-coroutines-core/common/src/flow/internal/ChannelFlow.kt
index f3730cc7..0efe5f86 100644
--- a/kotlinx-coroutines-core/common/src/flow/internal/ChannelFlow.kt
+++ b/kotlinx-coroutines-core/common/src/flow/internal/ChannelFlow.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.flow.internal
@@ -37,7 +37,7 @@ public interface FusibleFlow<T> : Flow<T> {
/**
* Operators that use channels as their "output" extend this `ChannelFlow` and are always fused with each other.
* This class servers as a skeleton implementation of [FusibleFlow] and provides other cross-cutting
- * methods like ability to [produceIn] and [broadcastIn] the corresponding flow, thus making it
+ * methods like ability to [produceIn] the corresponding flow, thus making it
* possible to directly use the backing channel if it exists (hence the `ChannelFlow` name).
*
* @suppress **This an internal API and should not be used from general code.**
@@ -59,7 +59,7 @@ public abstract class ChannelFlow<T>(
internal val collectToFun: suspend (ProducerScope<T>) -> Unit
get() = { collectTo(it) }
- private val produceCapacity: Int
+ internal val produceCapacity: Int
get() = if (capacity == Channel.OPTIONAL_CHANNEL) Channel.BUFFERED else capacity
/**
@@ -107,18 +107,6 @@ public abstract class ChannelFlow<T>(
protected abstract suspend fun collectTo(scope: ProducerScope<T>)
- // broadcastImpl is used in broadcastIn operator which is obsolete and replaced by SharedFlow.
- // BroadcastChannel does not support onBufferOverflow beyond simple conflation
- public open fun broadcastImpl(scope: CoroutineScope, start: CoroutineStart): BroadcastChannel<T> {
- val broadcastCapacity = when (onBufferOverflow) {
- BufferOverflow.SUSPEND -> produceCapacity
- BufferOverflow.DROP_OLDEST -> Channel.CONFLATED
- BufferOverflow.DROP_LATEST ->
- throw IllegalArgumentException("Broadcast channel does not support BufferOverflow.DROP_LATEST")
- }
- return scope.broadcast(context, broadcastCapacity, start, block = collectToFun)
- }
-
/**
* Here we use ATOMIC start for a reason (#1825).
* NB: [produceImpl] is used for [flowOn].
@@ -201,7 +189,7 @@ internal class ChannelFlowOperatorImpl<T>(
override fun create(context: CoroutineContext, capacity: Int, onBufferOverflow: BufferOverflow): ChannelFlow<T> =
ChannelFlowOperatorImpl(flow, context, capacity, onBufferOverflow)
- override fun dropChannelOperators(): Flow<T>? = flow
+ override fun dropChannelOperators(): Flow<T> = flow
override suspend fun flowCollect(collector: FlowCollector<T>) =
flow.collect(collector)
diff --git a/kotlinx-coroutines-core/common/src/flow/internal/Combine.kt b/kotlinx-coroutines-core/common/src/flow/internal/Combine.kt
index bbdebd08..c924c090 100644
--- a/kotlinx-coroutines-core/common/src/flow/internal/Combine.kt
+++ b/kotlinx-coroutines-core/common/src/flow/internal/Combine.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
@file:Suppress("UNCHECKED_CAST", "NON_APPLICABLE_CALL_FOR_BUILDER_INFERENCE") // KT-32203
@@ -23,7 +23,7 @@ internal suspend fun <R, T> FlowCollector<R>.combineInternal(
val size = flows.size
if (size == 0) return@flowScope // bail-out for empty input
val latestValues = arrayOfNulls<Any?>(size)
- latestValues.fill(UNINITIALIZED) // Smaller bytecode & faster that Array(size) { UNINITIALIZED }
+ latestValues.fill(UNINITIALIZED) // Smaller bytecode & faster than Array(size) { UNINITIALIZED }
val resultChannel = Channel<Update>(size)
val nonClosed = LocalAtomicInt(size)
var remainingAbsentValues = size
@@ -54,7 +54,7 @@ internal suspend fun <R, T> FlowCollector<R>.combineInternal(
++currentEpoch
// Start batch
// The very first receive in epoch should be suspending
- var element = resultChannel.receiveOrNull() ?: break // Channel is closed, nothing to do here
+ var element = resultChannel.receiveCatching().getOrNull() ?: break // Channel is closed, nothing to do here
while (true) {
val index = element.index
// Update values
@@ -65,7 +65,7 @@ internal suspend fun <R, T> FlowCollector<R>.combineInternal(
// Received the second value from the same flow in the same epoch -- bail out
if (lastReceivedEpoch[index] == currentEpoch) break
lastReceivedEpoch[index] = currentEpoch
- element = resultChannel.poll() ?: break
+ element = resultChannel.tryReceive().getOrNull() ?: break
}
// Process batch result if there is enough data
@@ -129,7 +129,9 @@ internal fun <T1, T2, R> zipImpl(flow: Flow<T1>, flow2: Flow<T2>, transform: sus
withContextUndispatched(coroutineContext + collectJob, Unit) {
flow.collect { value ->
withContextUndispatched(scopeContext, Unit, cnt) {
- val otherValue = second.receiveOrNull() ?: throw AbortFlowException(this@unsafeFlow)
+ val otherValue = second.receiveCatching().getOrElse {
+ throw it ?:AbortFlowException(this@unsafeFlow)
+ }
emit(transform(value, NULL.unbox(otherValue)))
}
}
@@ -137,7 +139,7 @@ internal fun <T1, T2, R> zipImpl(flow: Flow<T1>, flow2: Flow<T2>, transform: sus
} catch (e: AbortFlowException) {
e.checkOwnership(owner = this@unsafeFlow)
} finally {
- if (!second.isClosedForReceive) second.cancel()
+ second.cancel()
}
}
}
diff --git a/kotlinx-coroutines-core/common/src/flow/internal/FlowCoroutine.kt b/kotlinx-coroutines-core/common/src/flow/internal/FlowCoroutine.kt
index acc6ca04..b3955256 100644
--- a/kotlinx-coroutines-core/common/src/flow/internal/FlowCoroutine.kt
+++ b/kotlinx-coroutines-core/common/src/flow/internal/FlowCoroutine.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.flow.internal
diff --git a/kotlinx-coroutines-core/common/src/flow/internal/FlowExceptions.common.kt b/kotlinx-coroutines-core/common/src/flow/internal/FlowExceptions.common.kt
index 3064ed26..6a6c369c 100644
--- a/kotlinx-coroutines-core/common/src/flow/internal/FlowExceptions.common.kt
+++ b/kotlinx-coroutines-core/common/src/flow/internal/FlowExceptions.common.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.flow.internal
diff --git a/kotlinx-coroutines-core/common/src/flow/internal/Merge.kt b/kotlinx-coroutines-core/common/src/flow/internal/Merge.kt
index 530bcc1e..9eca8aa0 100644
--- a/kotlinx-coroutines-core/common/src/flow/internal/Merge.kt
+++ b/kotlinx-coroutines-core/common/src/flow/internal/Merge.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.flow.internal
diff --git a/kotlinx-coroutines-core/common/src/flow/internal/NopCollector.kt b/kotlinx-coroutines-core/common/src/flow/internal/NopCollector.kt
index 62a20a07..a2d7f04f 100644
--- a/kotlinx-coroutines-core/common/src/flow/internal/NopCollector.kt
+++ b/kotlinx-coroutines-core/common/src/flow/internal/NopCollector.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.flow.internal
diff --git a/kotlinx-coroutines-core/common/src/flow/internal/NullSurrogate.kt b/kotlinx-coroutines-core/common/src/flow/internal/NullSurrogate.kt
index f20deb2d..c7327bd3 100644
--- a/kotlinx-coroutines-core/common/src/flow/internal/NullSurrogate.kt
+++ b/kotlinx-coroutines-core/common/src/flow/internal/NullSurrogate.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.flow.internal
diff --git a/kotlinx-coroutines-core/common/src/flow/internal/SafeCollector.common.kt b/kotlinx-coroutines-core/common/src/flow/internal/SafeCollector.common.kt
index c89e94f5..006da41f 100644
--- a/kotlinx-coroutines-core/common/src/flow/internal/SafeCollector.common.kt
+++ b/kotlinx-coroutines-core/common/src/flow/internal/SafeCollector.common.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.flow.internal
diff --git a/kotlinx-coroutines-core/common/src/flow/internal/SendingCollector.kt b/kotlinx-coroutines-core/common/src/flow/internal/SendingCollector.kt
index c2abafd2..5188a969 100644
--- a/kotlinx-coroutines-core/common/src/flow/internal/SendingCollector.kt
+++ b/kotlinx-coroutines-core/common/src/flow/internal/SendingCollector.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.flow.internal
diff --git a/kotlinx-coroutines-core/common/src/flow/operators/Context.kt b/kotlinx-coroutines-core/common/src/flow/operators/Context.kt
index a6d6b76d..04342ed0 100644
--- a/kotlinx-coroutines-core/common/src/flow/operators/Context.kt
+++ b/kotlinx-coroutines-core/common/src/flow/operators/Context.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
@file:JvmMultifileClass
@@ -79,7 +79,7 @@ import kotlin.jvm.*
*
* ### Operator fusion
*
- * Adjacent applications of [channelFlow], [flowOn], [buffer], [produceIn], and [broadcastIn] are
+ * Adjacent applications of [channelFlow], [flowOn], [buffer], and [produceIn] are
* always fused so that only one properly configured channel is used for execution.
*
* Explicitly specified buffer capacity takes precedence over `buffer()` or `buffer(Channel.BUFFERED)` calls,
@@ -176,12 +176,12 @@ public fun <T> Flow<T>.buffer(capacity: Int = BUFFERED): Flow<T> = buffer(capaci
*
* ### Operator fusion
*
- * Adjacent applications of `conflate`/[buffer], [channelFlow], [flowOn], [produceIn], and [broadcastIn] are
+ * Adjacent applications of `conflate`/[buffer], [channelFlow], [flowOn] and [produceIn] are
* always fused so that only one properly configured channel is used for execution.
* **Conflation takes precedence over `buffer()` calls with any other capacity.**
*
* Note that any instance of [StateFlow] already behaves as if `conflate` operator is
- * applied to it, so applying `conflate` to a `StateFlow` has not effect.
+ * applied to it, so applying `conflate` to a `StateFlow` has no effect.
* See [StateFlow] documentation on Operator Fusion.
*/
public fun <T> Flow<T>.conflate(): Flow<T> = buffer(CONFLATED)
@@ -219,7 +219,7 @@ public fun <T> Flow<T>.conflate(): Flow<T> = buffer(CONFLATED)
*
* ### Operator fusion
*
- * Adjacent applications of [channelFlow], [flowOn], [buffer], [produceIn], and [broadcastIn] are
+ * Adjacent applications of [channelFlow], [flowOn], [buffer], and [produceIn] are
* always fused so that only one properly configured channel is used for execution.
*
* Multiple `flowOn` operators fuse to a single `flowOn` with a combined context. The elements of the context of
@@ -309,6 +309,8 @@ private class CancellableFlowImpl<T>(private val flow: Flow<T>) : CancellableFlo
* 3) It defers the execution of declarative [builder] until the moment of [collection][Flow.collect] similarly
* to `Observable.defer`. But it is unexpected because nothing in the name `flowWith` reflects this fact.
* 4) It can be confused with [flowOn] operator, though [flowWith] is much rarer.
+ *
+ * @suppress
*/
@FlowPreview
@Deprecated(message = "flowWith is deprecated without replacement, please refer to its KDoc for an explanation", level = DeprecationLevel.ERROR) // Error in beta release, removal in 1.4
diff --git a/kotlinx-coroutines-core/common/src/flow/operators/Delay.kt b/kotlinx-coroutines-core/common/src/flow/operators/Delay.kt
index c95b4be9..fed5962b 100644
--- a/kotlinx-coroutines-core/common/src/flow/operators/Delay.kt
+++ b/kotlinx-coroutines-core/common/src/flow/operators/Delay.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
@file:JvmMultifileClass
@@ -209,8 +209,7 @@ public fun <T> Flow<T>.debounce(timeout: (T) -> Duration): Flow<T> =
private fun <T> Flow<T>.debounceInternal(timeoutMillisSelector: (T) -> Long) : Flow<T> =
scopedFlow { downstream ->
// Produce the values using the default (rendezvous) channel
- // Note: the actual type is Any, KT-30796
- val values = produce<Any?> {
+ val values = produce {
collect { value -> send(value ?: NULL) }
}
// Now consume the values
@@ -237,14 +236,15 @@ private fun <T> Flow<T>.debounceInternal(timeoutMillisSelector: (T) -> Long) : F
lastValue = null // Consume the value
}
}
- // Should be receiveOrClosed when boxing issues are fixed
- values.onReceiveOrNull { value ->
- if (value == null) {
- if (lastValue != null) downstream.emit(NULL.unbox(lastValue))
- lastValue = DONE
- } else {
- lastValue = value
- }
+ values.onReceiveCatching { value ->
+ value
+ .onSuccess { lastValue = it }
+ .onFailure {
+ it?.let { throw it }
+ // If closed normally, emit the latest value
+ if (lastValue != null) downstream.emit(NULL.unbox(lastValue))
+ lastValue = DONE
+ }
}
}
}
@@ -278,21 +278,21 @@ private fun <T> Flow<T>.debounceInternal(timeoutMillisSelector: (T) -> Long) : F
public fun <T> Flow<T>.sample(periodMillis: Long): Flow<T> {
require(periodMillis > 0) { "Sample period should be positive" }
return scopedFlow { downstream ->
- val values = produce<Any?>(capacity = Channel.CONFLATED) {
- // Actually Any, KT-30796
+ val values = produce(capacity = Channel.CONFLATED) {
collect { value -> send(value ?: NULL) }
}
var lastValue: Any? = null
val ticker = fixedPeriodTicker(periodMillis)
while (lastValue !== DONE) {
select<Unit> {
- values.onReceiveOrNull {
- if (it == null) {
- ticker.cancel(ChildCancelledException())
- lastValue = DONE
- } else {
- lastValue = it
- }
+ values.onReceiveCatching { result ->
+ result
+ .onSuccess { lastValue = it }
+ .onFailure {
+ it?.let { throw it }
+ ticker.cancel(ChildCancelledException())
+ lastValue = DONE
+ }
}
// todo: shall be start sampling only when an element arrives or sample aways as here?
diff --git a/kotlinx-coroutines-core/common/src/flow/operators/Distinct.kt b/kotlinx-coroutines-core/common/src/flow/operators/Distinct.kt
index 1a34af77..0d67f889 100644
--- a/kotlinx-coroutines-core/common/src/flow/operators/Distinct.kt
+++ b/kotlinx-coroutines-core/common/src/flow/operators/Distinct.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
@file:JvmMultifileClass
@@ -83,4 +83,4 @@ private class DistinctFlowImpl<T>(
}
}
}
-} \ No newline at end of file
+}
diff --git a/kotlinx-coroutines-core/common/src/flow/operators/Emitters.kt b/kotlinx-coroutines-core/common/src/flow/operators/Emitters.kt
index 244af9a7..90879a97 100644
--- a/kotlinx-coroutines-core/common/src/flow/operators/Emitters.kt
+++ b/kotlinx-coroutines-core/common/src/flow/operators/Emitters.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
@file:JvmMultifileClass
@@ -194,7 +194,15 @@ public fun <T> Flow<T>.onEmpty(
}
}
-private class ThrowingCollector(private val e: Throwable) : FlowCollector<Any?> {
+/*
+ * 'emitAll' methods call this to fail-fast before starting to collect
+ * their sources (that may not have any elements for a long time).
+ */
+internal fun FlowCollector<*>.ensureActive() {
+ if (this is ThrowingCollector) throw e
+}
+
+internal class ThrowingCollector(@JvmField val e: Throwable) : FlowCollector<Any?> {
override suspend fun emit(value: Any?) {
throw e
}
diff --git a/kotlinx-coroutines-core/common/src/flow/operators/Errors.kt b/kotlinx-coroutines-core/common/src/flow/operators/Errors.kt
index c73ded9e..608221e0 100644
--- a/kotlinx-coroutines-core/common/src/flow/operators/Errors.kt
+++ b/kotlinx-coroutines-core/common/src/flow/operators/Errors.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
@file:JvmMultifileClass
diff --git a/kotlinx-coroutines-core/common/src/flow/operators/Limit.kt b/kotlinx-coroutines-core/common/src/flow/operators/Limit.kt
index 1d7ffd1d..8fbf1a2b 100644
--- a/kotlinx-coroutines-core/common/src/flow/operators/Limit.kt
+++ b/kotlinx-coroutines-core/common/src/flow/operators/Limit.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
@file:JvmMultifileClass
diff --git a/kotlinx-coroutines-core/common/src/flow/operators/Lint.kt b/kotlinx-coroutines-core/common/src/flow/operators/Lint.kt
index 7a70fbf7..83f83e1e 100644
--- a/kotlinx-coroutines-core/common/src/flow/operators/Lint.kt
+++ b/kotlinx-coroutines-core/common/src/flow/operators/Lint.kt
@@ -1,13 +1,14 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
-@file:Suppress("unused")
+@file:Suppress("unused", "INVISIBLE_REFERENCE", "INVISIBLE_MEMBER", "UNUSED_PARAMETER")
package kotlinx.coroutines.flow
import kotlinx.coroutines.*
import kotlin.coroutines.*
+import kotlin.internal.InlineOnly
/**
* Applying [cancellable][Flow.cancellable] to a [SharedFlow] has no effect.
@@ -79,4 +80,62 @@ public fun FlowCollector<*>.cancel(cause: CancellationException? = null): Unit =
replaceWith = ReplaceWith("currentCoroutineContext()")
)
public val FlowCollector<*>.coroutineContext: CoroutineContext
- get() = noImpl() \ No newline at end of file
+ get() = noImpl()
+
+@Deprecated(
+ message = "SharedFlow never completes, so this operator typically has not effect, it can only " +
+ "catch exceptions from 'onSubscribe' operator",
+ level = DeprecationLevel.WARNING,
+ replaceWith = ReplaceWith("this")
+)
+@InlineOnly
+public inline fun <T> SharedFlow<T>.catch(noinline action: suspend FlowCollector<T>.(cause: Throwable) -> Unit): Flow<T> =
+ (this as Flow<T>).catch(action)
+
+@Deprecated(
+ message = "SharedFlow never completes, so this operator has no effect.",
+ level = DeprecationLevel.WARNING,
+ replaceWith = ReplaceWith("this")
+)
+@InlineOnly
+public inline fun <T> SharedFlow<T>.retry(
+ retries: Long = Long.MAX_VALUE,
+ noinline predicate: suspend (cause: Throwable) -> Boolean = { true }
+): Flow<T> =
+ (this as Flow<T>).retry(retries, predicate)
+
+@Deprecated(
+ message = "SharedFlow never completes, so this operator has no effect.",
+ level = DeprecationLevel.WARNING,
+ replaceWith = ReplaceWith("this")
+)
+@InlineOnly
+public inline fun <T> SharedFlow<T>.retryWhen(noinline predicate: suspend FlowCollector<T>.(cause: Throwable, attempt: Long) -> Boolean): Flow<T> =
+ (this as Flow<T>).retryWhen(predicate)
+
+@Suppress("DeprecatedCallableAddReplaceWith")
+@Deprecated(
+ message = "SharedFlow never completes, so this terminal operation never completes.",
+ level = DeprecationLevel.WARNING
+)
+@InlineOnly
+public suspend inline fun <T> SharedFlow<T>.toList(): List<T> =
+ (this as Flow<T>).toList()
+
+@Suppress("DeprecatedCallableAddReplaceWith")
+@Deprecated(
+ message = "SharedFlow never completes, so this terminal operation never completes.",
+ level = DeprecationLevel.WARNING
+)
+@InlineOnly
+public suspend inline fun <T> SharedFlow<T>.toSet(): Set<T> =
+ (this as Flow<T>).toSet()
+
+@Suppress("DeprecatedCallableAddReplaceWith")
+@Deprecated(
+ message = "SharedFlow never completes, so this terminal operation never completes.",
+ level = DeprecationLevel.WARNING
+)
+@InlineOnly
+public suspend inline fun <T> SharedFlow<T>.count(): Int =
+ (this as Flow<T>).count()
diff --git a/kotlinx-coroutines-core/common/src/flow/operators/Merge.kt b/kotlinx-coroutines-core/common/src/flow/operators/Merge.kt
index 340d8e31..432160f3 100644
--- a/kotlinx-coroutines-core/common/src/flow/operators/Merge.kt
+++ b/kotlinx-coroutines-core/common/src/flow/operators/Merge.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
@file:JvmMultifileClass
@@ -34,7 +34,7 @@ public val DEFAULT_CONCURRENCY: Int = systemProp(DEFAULT_CONCURRENCY_PROPERTY_NA
* Transforms elements emitted by the original flow by applying [transform], that returns another flow,
* and then concatenating and flattening these flows.
*
- * This method is is a shortcut for `map(transform).flattenConcat()`. See [flattenConcat].
+ * This method is a shortcut for `map(transform).flattenConcat()`. See [flattenConcat].
*
* Note that even though this operator looks very familiar, we discourage its usage in a regular application-specific flows.
* Most likely, suspending operation in [map] operator will be sufficient and linear transformations are much easier to reason about.
@@ -57,7 +57,7 @@ public fun <T, R> Flow<T>.flatMapConcat(transform: suspend (value: T) -> Flow<R>
*
* ### Operator fusion
*
- * Applications of [flowOn], [buffer], [produceIn], and [broadcastIn] _after_ this operator are fused with
+ * Applications of [flowOn], [buffer], and [produceIn] _after_ this operator are fused with
* its concurrent merging so that only one properly configured channel is used for execution of merging logic.
*
* @param concurrency controls the number of in-flight flows, at most [concurrency] flows are collected
@@ -87,7 +87,7 @@ public fun <T> Flow<Flow<T>>.flattenConcat(): Flow<T> = flow {
*
* ### Operator fusion
*
- * Applications of [flowOn], [buffer], [produceIn], and [broadcastIn] _after_ this operator are fused with
+ * Applications of [flowOn], [buffer], and [produceIn] _after_ this operator are fused with
* its concurrent merging so that only one properly configured channel is used for execution of merging logic.
*/
@ExperimentalCoroutinesApi
@@ -111,7 +111,7 @@ public fun <T> Iterable<Flow<T>>.merge(): Flow<T> {
*
* ### Operator fusion
*
- * Applications of [flowOn], [buffer], [produceIn], and [broadcastIn] _after_ this operator are fused with
+ * Applications of [flowOn], [buffer], and [produceIn] _after_ this operator are fused with
* its concurrent merging so that only one properly configured channel is used for execution of merging logic.
*/
@ExperimentalCoroutinesApi
@@ -126,9 +126,12 @@ public fun <T> merge(vararg flows: Flow<T>): Flow<T> = flows.asIterable().merge(
*
* ### Operator fusion
*
- * Applications of [flowOn], [buffer], [produceIn], and [broadcastIn] _after_ this operator are fused with
+ * Applications of [flowOn], [buffer], and [produceIn] _after_ this operator are fused with
* its concurrent merging so that only one properly configured channel is used for execution of merging logic.
*
+ * When [concurrency] is greater than 1, this operator is [buffered][buffer] by default
+ * and size of its output buffer can be changed by applying subsequent [buffer] operator.
+ *
* @param concurrency controls the number of in-flight flows, at most [concurrency] flows are collected
* at the same time. By default it is equal to [DEFAULT_CONCURRENCY].
*/
diff --git a/kotlinx-coroutines-core/common/src/flow/operators/Share.kt b/kotlinx-coroutines-core/common/src/flow/operators/Share.kt
index fe737a5b..4fa74d8e 100644
--- a/kotlinx-coroutines-core/common/src/flow/operators/Share.kt
+++ b/kotlinx-coroutines-core/common/src/flow/operators/Share.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
@file:JvmMultifileClass
@@ -68,7 +68,7 @@ import kotlin.jvm.*
* ### Upstream completion and error handling
*
* **Normal completion of the upstream flow has no effect on subscribers**, and the sharing coroutine continues to run. If a
- * a strategy like [SharingStarted.WhileSubscribed] is used, then the upstream can get restarted again. If a special
+ * strategy like [SharingStarted.WhileSubscribed] is used, then the upstream can get restarted again. If a special
* action on upstream completion is needed, then an [onCompletion] operator can be used before the
* `shareIn` operator to emit a special value in this case, like this:
*
@@ -144,8 +144,8 @@ public fun <T> Flow<T>.shareIn(
onBufferOverflow = config.onBufferOverflow
)
@Suppress("UNCHECKED_CAST")
- scope.launchSharing(config.context, config.upstream, shared, started, NO_VALUE as T)
- return shared.asSharedFlow()
+ val job = scope.launchSharing(config.context, config.upstream, shared, started, NO_VALUE as T)
+ return ReadonlySharedFlow(shared, job)
}
private class SharingConfig<T>(
@@ -197,7 +197,7 @@ private fun <T> CoroutineScope.launchSharing(
shared: MutableSharedFlow<T>,
started: SharingStarted,
initialValue: T
-) {
+): Job =
launch(context) { // the single coroutine to rule the sharing
// Optimize common built-in started strategies
when {
@@ -230,7 +230,6 @@ private fun <T> CoroutineScope.launchSharing(
}
}
}
-}
// -------------------------------- stateIn --------------------------------
@@ -303,8 +302,8 @@ public fun <T> Flow<T>.stateIn(
): StateFlow<T> {
val config = configureSharing(1)
val state = MutableStateFlow(initialValue)
- scope.launchSharing(config.context, config.upstream, state, started, initialValue)
- return state.asStateFlow()
+ val job = scope.launchSharing(config.context, config.upstream, state, started, initialValue)
+ return ReadonlyStateFlow(state, job)
}
/**
@@ -332,7 +331,7 @@ private fun <T> CoroutineScope.launchSharingDeferred(
upstream.collect { value ->
state?.let { it.value = value } ?: run {
state = MutableStateFlow(value).also {
- result.complete(it.asStateFlow())
+ result.complete(ReadonlyStateFlow(it, coroutineContext.job))
}
}
}
@@ -351,23 +350,27 @@ private fun <T> CoroutineScope.launchSharingDeferred(
* Represents this mutable shared flow as a read-only shared flow.
*/
public fun <T> MutableSharedFlow<T>.asSharedFlow(): SharedFlow<T> =
- ReadonlySharedFlow(this)
+ ReadonlySharedFlow(this, null)
/**
* Represents this mutable state flow as a read-only state flow.
*/
public fun <T> MutableStateFlow<T>.asStateFlow(): StateFlow<T> =
- ReadonlyStateFlow(this)
+ ReadonlyStateFlow(this, null)
private class ReadonlySharedFlow<T>(
- flow: SharedFlow<T>
+ flow: SharedFlow<T>,
+ @Suppress("unused")
+ private val job: Job? // keeps a strong reference to the job (if present)
) : SharedFlow<T> by flow, CancellableFlow<T>, FusibleFlow<T> {
override fun fuse(context: CoroutineContext, capacity: Int, onBufferOverflow: BufferOverflow) =
fuseSharedFlow(context, capacity, onBufferOverflow)
}
private class ReadonlyStateFlow<T>(
- flow: StateFlow<T>
+ flow: StateFlow<T>,
+ @Suppress("unused")
+ private val job: Job? // keeps a strong reference to the job (if present)
) : StateFlow<T> by flow, CancellableFlow<T>, FusibleFlow<T> {
override fun fuse(context: CoroutineContext, capacity: Int, onBufferOverflow: BufferOverflow) =
fuseStateFlow(context, capacity, onBufferOverflow)
diff --git a/kotlinx-coroutines-core/common/src/flow/operators/Transform.kt b/kotlinx-coroutines-core/common/src/flow/operators/Transform.kt
index e3552d28..a47ae776 100644
--- a/kotlinx-coroutines-core/common/src/flow/operators/Transform.kt
+++ b/kotlinx-coroutines-core/common/src/flow/operators/Transform.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
@file:JvmMultifileClass
@@ -15,7 +15,7 @@ import kotlinx.coroutines.flow.internal.unsafeFlow as flow
import kotlinx.coroutines.flow.unsafeTransform as transform
/**
- * Returns a flow containing only values of the original flow that matches the given [predicate].
+ * Returns a flow containing only values of the original flow that match the given [predicate].
*/
public inline fun <T> Flow<T>.filter(crossinline predicate: suspend (T) -> Boolean): Flow<T> = transform { value ->
if (predicate(value)) return@transform emit(value)
@@ -82,9 +82,23 @@ public fun <T> Flow<T>.onEach(action: suspend (T) -> Unit): Flow<T> = transform
* flowOf(1, 2, 3).scan(emptyList<Int>()) { acc, value -> acc + value }.toList()
* ```
* will produce `[], [1], [1, 2], [1, 2, 3]]`.
+ *
+ * This function is an alias to [runningFold] operator.
+ */
+@ExperimentalCoroutinesApi
+public fun <T, R> Flow<T>.scan(initial: R, @BuilderInference operation: suspend (accumulator: R, value: T) -> R): Flow<R> = runningFold(initial, operation)
+
+/**
+ * Folds the given flow with [operation], emitting every intermediate result, including [initial] value.
+ * Note that initial value should be immutable (or should not be mutated) as it is shared between different collectors.
+ * For example:
+ * ```
+ * flowOf(1, 2, 3).runningFold(emptyList<Int>()) { acc, value -> acc + value }.toList()
+ * ```
+ * will produce `[], [1], [1, 2], [1, 2, 3]]`.
*/
@ExperimentalCoroutinesApi
-public fun <T, R> Flow<T>.scan(initial: R, @BuilderInference operation: suspend (accumulator: R, value: T) -> R): Flow<R> = flow {
+public fun <T, R> Flow<T>.runningFold(initial: R, @BuilderInference operation: suspend (accumulator: R, value: T) -> R): Flow<R> = flow {
var accumulator: R = initial
emit(accumulator)
collect { value ->
@@ -100,7 +114,7 @@ public fun <T, R> Flow<T>.scan(initial: R, @BuilderInference operation: suspend
*
* For example:
* ```
- * flowOf(1, 2, 3, 4).runningReduce { (v1, v2) -> v1 + v2 }.toList()
+ * flowOf(1, 2, 3, 4).runningReduce { acc, value -> acc + value }.toList()
* ```
* will produce `[1, 3, 6, 10]`
*/
diff --git a/kotlinx-coroutines-core/common/src/flow/operators/Zip.kt b/kotlinx-coroutines-core/common/src/flow/operators/Zip.kt
index 790c0895..ed6f5722 100644
--- a/kotlinx-coroutines-core/common/src/flow/operators/Zip.kt
+++ b/kotlinx-coroutines-core/common/src/flow/operators/Zip.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
@file:JvmMultifileClass
@@ -153,7 +153,7 @@ public fun <T1, T2, T3, T4, R> combine(
flow3: Flow<T3>,
flow4: Flow<T4>,
transform: suspend (T1, T2, T3, T4) -> R
-): Flow<R> = combine(flow, flow2, flow3, flow4) { args: Array<*> ->
+): Flow<R> = combineUnsafe(flow, flow2, flow3, flow4) { args: Array<*> ->
transform(
args[0] as T1,
args[1] as T2,
diff --git a/kotlinx-coroutines-core/common/src/flow/terminal/Collect.kt b/kotlinx-coroutines-core/common/src/flow/terminal/Collect.kt
index e8f2a9a3..771f8332 100644
--- a/kotlinx-coroutines-core/common/src/flow/terminal/Collect.kt
+++ b/kotlinx-coroutines-core/common/src/flow/terminal/Collect.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
@file:JvmMultifileClass
@@ -44,7 +44,7 @@ public suspend fun Flow<*>.collect(): Unit = collect(NopCollector)
* .launchIn(uiScope)
* ```
*
- * Note that resulting value of [launchIn] is not used the provided scope takes care of cancellation.
+ * Note that the resulting value of [launchIn] is not used and the provided scope takes care of cancellation.
*/
public fun <T> Flow<T>.launchIn(scope: CoroutineScope): Job = scope.launch {
collect() // tail-call
@@ -87,8 +87,8 @@ public suspend inline fun <T> Flow<T>.collectIndexed(crossinline action: suspend
/**
* Terminal flow operator that collects the given flow with a provided [action].
- * The crucial difference from [collect] is that when the original flow emits a new value, [action] block for previous
- * value is cancelled.
+ * The crucial difference from [collect] is that when the original flow emits a new value
+ * then the [action] block for the previous value is cancelled.
*
* It can be demonstrated by the following example:
*
@@ -127,5 +127,7 @@ public suspend fun <T> Flow<T>.collectLatest(action: suspend (value: T) -> Unit)
* Collects all the values from the given [flow] and emits them to the collector.
* It is a shorthand for `flow.collect { value -> emit(value) }`.
*/
-@BuilderInference
-public suspend inline fun <T> FlowCollector<T>.emitAll(flow: Flow<T>): Unit = flow.collect(this)
+public suspend fun <T> FlowCollector<T>.emitAll(flow: Flow<T>) {
+ ensureActive()
+ flow.collect(this)
+}
diff --git a/kotlinx-coroutines-core/common/src/flow/terminal/Collection.kt b/kotlinx-coroutines-core/common/src/flow/terminal/Collection.kt
index 6b05ca18..c973da89 100644
--- a/kotlinx-coroutines-core/common/src/flow/terminal/Collection.kt
+++ b/kotlinx-coroutines-core/common/src/flow/terminal/Collection.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
@file:JvmMultifileClass
diff --git a/kotlinx-coroutines-core/common/src/flow/terminal/Count.kt b/kotlinx-coroutines-core/common/src/flow/terminal/Count.kt
index d50c0272..5eb99fc8 100644
--- a/kotlinx-coroutines-core/common/src/flow/terminal/Count.kt
+++ b/kotlinx-coroutines-core/common/src/flow/terminal/Count.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
@file:JvmMultifileClass
diff --git a/kotlinx-coroutines-core/common/src/flow/terminal/Reduce.kt b/kotlinx-coroutines-core/common/src/flow/terminal/Reduce.kt
index 83f5498e..1794c9f4 100644
--- a/kotlinx-coroutines-core/common/src/flow/terminal/Reduce.kt
+++ b/kotlinx-coroutines-core/common/src/flow/terminal/Reduce.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
@file:JvmMultifileClass
@@ -144,3 +144,28 @@ public suspend fun <T> Flow<T>.firstOrNull(predicate: suspend (T) -> Boolean): T
}
return result
}
+
+/**
+ * The terminal operator that returns the last element emitted by the flow.
+ *
+ * Throws [NoSuchElementException] if the flow was empty.
+ */
+public suspend fun <T> Flow<T>.last(): T {
+ var result: Any? = NULL
+ collect {
+ result = it
+ }
+ if (result === NULL) throw NoSuchElementException("Expected at least one element")
+ return result as T
+}
+
+/**
+ * The terminal operator that returns the last element emitted by the flow or `null` if the flow was empty.
+ */
+public suspend fun <T> Flow<T>.lastOrNull(): T? {
+ var result: T? = null
+ collect {
+ result = it
+ }
+ return result
+}
diff --git a/kotlinx-coroutines-core/common/src/internal/ArrayQueue.kt b/kotlinx-coroutines-core/common/src/internal/ArrayQueue.kt
index 09806cd2..6b994b68 100644
--- a/kotlinx-coroutines-core/common/src/internal/ArrayQueue.kt
+++ b/kotlinx-coroutines-core/common/src/internal/ArrayQueue.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.internal
diff --git a/kotlinx-coroutines-core/common/src/internal/Atomic.kt b/kotlinx-coroutines-core/common/src/internal/Atomic.kt
index a27d5491..cf43764c 100644
--- a/kotlinx-coroutines-core/common/src/internal/Atomic.kt
+++ b/kotlinx-coroutines-core/common/src/internal/Atomic.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
@file:Suppress("NO_EXPLICIT_VISIBILITY_IN_API_MODE")
diff --git a/kotlinx-coroutines-core/common/src/internal/Concurrent.common.kt b/kotlinx-coroutines-core/common/src/internal/Concurrent.common.kt
index 1836a528..9f2699ae 100644
--- a/kotlinx-coroutines-core/common/src/internal/Concurrent.common.kt
+++ b/kotlinx-coroutines-core/common/src/internal/Concurrent.common.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.internal
diff --git a/kotlinx-coroutines-core/common/src/internal/ConcurrentLinkedList.kt b/kotlinx-coroutines-core/common/src/internal/ConcurrentLinkedList.kt
index 128a1998..638ec432 100644
--- a/kotlinx-coroutines-core/common/src/internal/ConcurrentLinkedList.kt
+++ b/kotlinx-coroutines-core/common/src/internal/ConcurrentLinkedList.kt
@@ -1,11 +1,12 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.internal
import kotlinx.atomicfu.*
import kotlinx.coroutines.*
+import kotlin.jvm.*
import kotlin.native.concurrent.SharedImmutable
/**
@@ -227,8 +228,8 @@ private inline fun AtomicInt.addConditionally(delta: Int, condition: (cur: Int)
}
}
-@Suppress("EXPERIMENTAL_FEATURE_WARNING") // We are using inline class only internally, so it is Ok
-internal inline class SegmentOrClosed<S : Segment<S>>(private val value: Any?) {
+@JvmInline
+internal value class SegmentOrClosed<S : Segment<S>>(private val value: Any?) {
val isClosed: Boolean get() = value === CLOSED
@Suppress("UNCHECKED_CAST")
val segment: S get() = if (value === CLOSED) error("Does not contain segment") else value as S
@@ -237,4 +238,4 @@ internal inline class SegmentOrClosed<S : Segment<S>>(private val value: Any?) {
private const val POINTERS_SHIFT = 16
@SharedImmutable
-private val CLOSED = Symbol("CLOSED") \ No newline at end of file
+private val CLOSED = Symbol("CLOSED")
diff --git a/kotlinx-coroutines-core/common/src/internal/DispatchedContinuation.kt b/kotlinx-coroutines-core/common/src/internal/DispatchedContinuation.kt
index b7b2954f..c689a381 100644
--- a/kotlinx-coroutines-core/common/src/internal/DispatchedContinuation.kt
+++ b/kotlinx-coroutines-core/common/src/internal/DispatchedContinuation.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.internal
@@ -23,7 +23,7 @@ internal class DispatchedContinuation<in T>(
@JvmField
@Suppress("PropertyName")
internal var _state: Any? = UNDEFINED
- override val callerFrame: CoroutineStackFrame? = continuation as? CoroutineStackFrame
+ override val callerFrame: CoroutineStackFrame? get() = continuation as? CoroutineStackFrame
override fun getStackTraceElement(): StackTraceElement? = null
@JvmField // pre-cached value to avoid ctx.fold on every resumption
internal val countOrElement = threadContextElements(context)
@@ -46,40 +46,49 @@ internal class DispatchedContinuation<in T>(
* 4) [Throwable] continuation was cancelled with this cause while being in [suspendCancellableCoroutineReusable],
* [CancellableContinuationImpl.getResult] will check for cancellation later.
*
- * [REUSABLE_CLAIMED] state is required to prevent the lost resume in the channel.
- * AbstractChannel.receive method relies on the fact that the following pattern
+ * [REUSABLE_CLAIMED] state is required to prevent double-use of the reused continuation.
+ * In the `getResult`, we have the following code:
* ```
- * suspendCancellableCoroutineReusable { cont ->
- * val result = pollFastPath()
- * if (result != null) cont.resume(result)
+ * if (trySuspend()) {
+ * // <- at this moment current continuation can be redispatched and claimed again.
+ * attachChildToParent()
+ * releaseClaimedContinuation()
* }
* ```
- * always succeeds.
- * To make it always successful, we actually postpone "reusable" cancellation
- * to this phase and set cancellation only at the moment of instantiation.
*/
private val _reusableCancellableContinuation = atomic<Any?>(null)
- public val reusableCancellableContinuation: CancellableContinuationImpl<*>?
+ private val reusableCancellableContinuation: CancellableContinuationImpl<*>?
get() = _reusableCancellableContinuation.value as? CancellableContinuationImpl<*>
- public fun isReusable(requester: CancellableContinuationImpl<*>): Boolean {
+ fun isReusable(): Boolean {
/*
+ Invariant: caller.resumeMode.isReusableMode
* Reusability control:
- * `null` -> no reusability at all, false
- * If current state is not CCI, then we are within `suspendCancellableCoroutineReusable`, true
- * Else, if result is CCI === requester.
- * Identity check my fail for the following pattern:
- * ```
- * loop:
- * suspendCancellableCoroutineReusable { } // Reusable, outer coroutine stores the child handle
- * suspendCancellableCoroutine { } // **Not reusable**, handle should be disposed after {}, otherwise
- * it will leak because it won't be freed by `releaseInterceptedContinuation`
- * ```
+ * `null` -> no reusability at all, `false`
+ * anything else -> reusable.
*/
- val value = _reusableCancellableContinuation.value ?: return false
- if (value is CancellableContinuationImpl<*>) return value === requester
- return true
+ return _reusableCancellableContinuation.value != null
+ }
+
+ /**
+ * Awaits until previous call to `suspendCancellableCoroutineReusable` will
+ * stop mutating cached instance
+ */
+ fun awaitReusability() {
+ _reusableCancellableContinuation.loop {
+ if (it !== REUSABLE_CLAIMED) return
+ }
+ }
+
+ fun release() {
+ /*
+ * Called from `releaseInterceptedContinuation`, can be concurrent with
+ * the code in `getResult` right after `trySuspend` returned `true`, so we have
+ * to wait for a release here.
+ */
+ awaitReusability()
+ reusableCancellableContinuation?.detachChild()
}
/**
@@ -103,11 +112,20 @@ internal class DispatchedContinuation<in T>(
_reusableCancellableContinuation.value = REUSABLE_CLAIMED
return null
}
+ // potentially competing with cancel
state is CancellableContinuationImpl<*> -> {
if (_reusableCancellableContinuation.compareAndSet(state, REUSABLE_CLAIMED)) {
return state as CancellableContinuationImpl<T>
}
}
+ state === REUSABLE_CLAIMED -> {
+ // Do nothing, wait until reusable instance will be returned from
+ // getResult() of a previous `suspendCancellableCoroutineReusable`
+ }
+ state is Throwable -> {
+ // Also do nothing, Throwable can only indicate that the CC
+ // is in REUSABLE_CLAIMED state, but with postponed cancellation
+ }
else -> error("Inconsistent state $state")
}
}
@@ -127,14 +145,13 @@ internal class DispatchedContinuation<in T>(
*
* See [CancellableContinuationImpl.getResult].
*/
- fun checkPostponedCancellation(continuation: CancellableContinuation<*>): Throwable? {
+ fun tryReleaseClaimedContinuation(continuation: CancellableContinuation<*>): Throwable? {
_reusableCancellableContinuation.loop { state ->
// not when(state) to avoid Intrinsics.equals call
when {
state === REUSABLE_CLAIMED -> {
if (_reusableCancellableContinuation.compareAndSet(REUSABLE_CLAIMED, continuation)) return null
}
- state === null -> return null
state is Throwable -> {
require(_reusableCancellableContinuation.compareAndSet(state, null))
return state
@@ -235,7 +252,7 @@ internal class DispatchedContinuation<in T>(
@Suppress("NOTHING_TO_INLINE") // we need it inline to save us an entry on the stack
inline fun resumeUndispatchedWith(result: Result<T>) {
- withCoroutineContext(context, countOrElement) {
+ withContinuationContext(continuation, countOrElement) {
continuation.resumeWith(result)
}
}
diff --git a/kotlinx-coroutines-core/common/src/internal/DispatchedTask.kt b/kotlinx-coroutines-core/common/src/internal/DispatchedTask.kt
index 1f4942a3..d982f95b 100644
--- a/kotlinx-coroutines-core/common/src/internal/DispatchedTask.kt
+++ b/kotlinx-coroutines-core/common/src/internal/DispatchedTask.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines
@@ -23,7 +23,7 @@ internal const val MODE_ATOMIC = 0
* **DO NOT CHANGE THE CONSTANT VALUE**. It is being into the user code from [suspendCancellableCoroutine].
*/
@PublishedApi
-internal const val MODE_CANCELLABLE = 1
+internal const val MODE_CANCELLABLE: Int = 1
/**
* Cancellable dispatch mode for [suspendCancellableCoroutineReusable].
@@ -85,9 +85,9 @@ internal abstract class DispatchedTask<in T>(
try {
val delegate = delegate as DispatchedContinuation<T>
val continuation = delegate.continuation
- val context = continuation.context
- val state = takeState() // NOTE: Must take state in any case, even if cancelled
- withCoroutineContext(context, delegate.countOrElement) {
+ withContinuationContext(continuation, delegate.countOrElement) {
+ val context = continuation.context
+ val state = takeState() // NOTE: Must take state in any case, even if cancelled
val exception = getExceptionalResult(state)
/*
* Check whether continuation was originally resumed with an exception.
@@ -134,7 +134,7 @@ internal abstract class DispatchedTask<in T>(
* Fatal exception handling can be intercepted with [CoroutineExceptionHandler] element in the context of
* a failed coroutine, but such exceptions should be reported anyway.
*/
- internal fun handleFatalException(exception: Throwable?, finallyException: Throwable?) {
+ public fun handleFatalException(exception: Throwable?, finallyException: Throwable?) {
if (exception === null && finallyException === null) return
if (exception !== null && finallyException !== null) {
exception.addSuppressedThrowable(finallyException)
diff --git a/kotlinx-coroutines-core/common/src/internal/InlineList.kt b/kotlinx-coroutines-core/common/src/internal/InlineList.kt
index bac8610c..61bf6d01 100644
--- a/kotlinx-coroutines-core/common/src/internal/InlineList.kt
+++ b/kotlinx-coroutines-core/common/src/internal/InlineList.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
@file:Suppress("UNCHECKED_CAST")
@@ -7,14 +7,16 @@
package kotlinx.coroutines.internal
import kotlinx.coroutines.assert
+import kotlin.jvm.*
/*
* Inline class that represents a mutable list, but does not allocate an underlying storage
* for zero and one elements.
* Cannot be parametrized with `List<*>`.
*/
-internal inline class InlineList<E>(private val holder: Any? = null) {
- public operator fun plus(element: E): InlineList<E> {
+@JvmInline
+internal value class InlineList<E>(private val holder: Any? = null) {
+ operator fun plus(element: E): InlineList<E> {
assert { element !is List<*> } // Lists are prohibited
return when (holder) {
null -> InlineList(element)
@@ -31,7 +33,7 @@ internal inline class InlineList<E>(private val holder: Any? = null) {
}
}
- public inline fun forEachReversed(action: (E) -> Unit) {
+ inline fun forEachReversed(action: (E) -> Unit) {
when (holder) {
null -> return
!is ArrayList<*> -> action(holder as E)
diff --git a/kotlinx-coroutines-core/common/src/internal/LocalAtomics.common.kt b/kotlinx-coroutines-core/common/src/internal/LocalAtomics.common.kt
index bcfb932d..74848b2d 100644
--- a/kotlinx-coroutines-core/common/src/internal/LocalAtomics.common.kt
+++ b/kotlinx-coroutines-core/common/src/internal/LocalAtomics.common.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.internal
diff --git a/kotlinx-coroutines-core/common/src/internal/LockFreeLinkedList.common.kt b/kotlinx-coroutines-core/common/src/internal/LockFreeLinkedList.common.kt
index 8508e392..0e1d1b47 100644
--- a/kotlinx-coroutines-core/common/src/internal/LockFreeLinkedList.common.kt
+++ b/kotlinx-coroutines-core/common/src/internal/LockFreeLinkedList.common.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
@file:Suppress("NO_EXPLICIT_VISIBILITY_IN_API_MODE")
diff --git a/kotlinx-coroutines-core/common/src/internal/LockFreeTaskQueue.kt b/kotlinx-coroutines-core/common/src/internal/LockFreeTaskQueue.kt
index dfee8e9f..10c5c830 100644
--- a/kotlinx-coroutines-core/common/src/internal/LockFreeTaskQueue.kt
+++ b/kotlinx-coroutines-core/common/src/internal/LockFreeTaskQueue.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.internal
diff --git a/kotlinx-coroutines-core/common/src/internal/MainDispatcherFactory.kt b/kotlinx-coroutines-core/common/src/internal/MainDispatcherFactory.kt
index c3587af6..0b863868 100644
--- a/kotlinx-coroutines-core/common/src/internal/MainDispatcherFactory.kt
+++ b/kotlinx-coroutines-core/common/src/internal/MainDispatcherFactory.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.internal
diff --git a/kotlinx-coroutines-core/common/src/internal/OnUndeliveredElement.kt b/kotlinx-coroutines-core/common/src/internal/OnUndeliveredElement.kt
index 1744359e..0701d686 100644
--- a/kotlinx-coroutines-core/common/src/internal/OnUndeliveredElement.kt
+++ b/kotlinx-coroutines-core/common/src/internal/OnUndeliveredElement.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.internal
diff --git a/kotlinx-coroutines-core/common/src/internal/ProbesSupport.common.kt b/kotlinx-coroutines-core/common/src/internal/ProbesSupport.common.kt
index 763c1ca3..a6d81361 100644
--- a/kotlinx-coroutines-core/common/src/internal/ProbesSupport.common.kt
+++ b/kotlinx-coroutines-core/common/src/internal/ProbesSupport.common.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.internal
diff --git a/kotlinx-coroutines-core/common/src/internal/Scopes.kt b/kotlinx-coroutines-core/common/src/internal/Scopes.kt
index 9bb2ce3d..ad8d86ed 100644
--- a/kotlinx-coroutines-core/common/src/internal/Scopes.kt
+++ b/kotlinx-coroutines-core/common/src/internal/Scopes.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.internal
@@ -15,12 +15,13 @@ import kotlin.jvm.*
internal open class ScopeCoroutine<in T>(
context: CoroutineContext,
@JvmField val uCont: Continuation<T> // unintercepted continuation
-) : AbstractCoroutine<T>(context, true), CoroutineStackFrame {
- final override val callerFrame: CoroutineStackFrame? get() = uCont as CoroutineStackFrame?
+) : AbstractCoroutine<T>(context, true, true), CoroutineStackFrame {
+
+ final override val callerFrame: CoroutineStackFrame? get() = uCont as? CoroutineStackFrame
final override fun getStackTraceElement(): StackTraceElement? = null
- final override val isScopedCoroutine: Boolean get() = true
- internal val parent: Job? get() = parentContext[Job]
+ final override val isScopedCoroutine: Boolean get() = true
+ internal val parent: Job? get() = parentHandle?.parent
override fun afterCompletion(state: Any?) {
// Resume in a cancellable way by default when resuming from another context
diff --git a/kotlinx-coroutines-core/common/src/internal/StackTraceRecovery.common.kt b/kotlinx-coroutines-core/common/src/internal/StackTraceRecovery.common.kt
index 15ba40f2..2d00768d 100644
--- a/kotlinx-coroutines-core/common/src/internal/StackTraceRecovery.common.kt
+++ b/kotlinx-coroutines-core/common/src/internal/StackTraceRecovery.common.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.internal
@@ -20,6 +20,7 @@ internal expect fun <E: Throwable> recoverStackTrace(exception: E, continuation:
/**
* initCause on JVM, nop on other platforms
*/
+@Suppress("EXTENSION_SHADOWED_BY_MEMBER")
internal expect fun Throwable.initCause(cause: Throwable)
/**
diff --git a/kotlinx-coroutines-core/common/src/internal/Symbol.kt b/kotlinx-coroutines-core/common/src/internal/Symbol.kt
index 4fa8f540..84db2ef6 100644
--- a/kotlinx-coroutines-core/common/src/internal/Symbol.kt
+++ b/kotlinx-coroutines-core/common/src/internal/Symbol.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.internal
@@ -10,7 +10,7 @@ package kotlinx.coroutines.internal
* @suppress **This is unstable API and it is subject to change.**
*/
internal class Symbol(val symbol: String) {
- override fun toString(): String = symbol
+ override fun toString(): String = "<$symbol>"
@Suppress("UNCHECKED_CAST", "NOTHING_TO_INLINE")
inline fun <T> unbox(value: Any?): T = if (value === this) null as T else value as T
diff --git a/kotlinx-coroutines-core/common/src/internal/Synchronized.common.kt b/kotlinx-coroutines-core/common/src/internal/Synchronized.common.kt
index 3afc7e18..059b234d 100644
--- a/kotlinx-coroutines-core/common/src/internal/Synchronized.common.kt
+++ b/kotlinx-coroutines-core/common/src/internal/Synchronized.common.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.internal
diff --git a/kotlinx-coroutines-core/common/src/internal/SystemProps.common.kt b/kotlinx-coroutines-core/common/src/internal/SystemProps.common.kt
index 4cb629e9..ca84809b 100644
--- a/kotlinx-coroutines-core/common/src/internal/SystemProps.common.kt
+++ b/kotlinx-coroutines-core/common/src/internal/SystemProps.common.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
@file:JvmName("SystemPropsKt")
diff --git a/kotlinx-coroutines-core/common/src/internal/ThreadContext.common.kt b/kotlinx-coroutines-core/common/src/internal/ThreadContext.common.kt
index 94695e8a..6d14e592 100644
--- a/kotlinx-coroutines-core/common/src/internal/ThreadContext.common.kt
+++ b/kotlinx-coroutines-core/common/src/internal/ThreadContext.common.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.internal
diff --git a/kotlinx-coroutines-core/common/src/internal/ThreadLocal.common.kt b/kotlinx-coroutines-core/common/src/internal/ThreadLocal.common.kt
index 0f4ec342..890ae4e3 100644
--- a/kotlinx-coroutines-core/common/src/internal/ThreadLocal.common.kt
+++ b/kotlinx-coroutines-core/common/src/internal/ThreadLocal.common.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.internal
diff --git a/kotlinx-coroutines-core/common/src/internal/ThreadSafeHeap.kt b/kotlinx-coroutines-core/common/src/internal/ThreadSafeHeap.kt
index df55c28f..43b7e9de 100644
--- a/kotlinx-coroutines-core/common/src/internal/ThreadSafeHeap.kt
+++ b/kotlinx-coroutines-core/common/src/internal/ThreadSafeHeap.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.internal
diff --git a/kotlinx-coroutines-core/common/src/intrinsics/Cancellable.kt b/kotlinx-coroutines-core/common/src/intrinsics/Cancellable.kt
index f814b152..f5b96a8d 100644
--- a/kotlinx-coroutines-core/common/src/intrinsics/Cancellable.kt
+++ b/kotlinx-coroutines-core/common/src/intrinsics/Cancellable.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.intrinsics
@@ -49,6 +49,19 @@ private inline fun runSafely(completion: Continuation<*>, block: () -> Unit) {
try {
block()
} catch (e: Throwable) {
- completion.resumeWith(Result.failure(e))
+ dispatcherFailure(completion, e)
}
}
+
+private fun dispatcherFailure(completion: Continuation<*>, e: Throwable) {
+ /*
+ * This method is invoked when we failed to start a coroutine due to the throwing
+ * dispatcher implementation or missing Dispatchers.Main.
+ * This situation is not recoverable, so we are trying to deliver the exception by all means:
+ * 1) Resume the coroutine with an exception, so it won't prevent its parent from completion
+ * 2) Rethrow the exception immediately, so it will crash the caller (e.g. when the coroutine had
+ * no parent or it was async/produce over MainScope).
+ */
+ completion.resumeWith(Result.failure(e))
+ throw e
+}
diff --git a/kotlinx-coroutines-core/common/src/intrinsics/Undispatched.kt b/kotlinx-coroutines-core/common/src/intrinsics/Undispatched.kt
index 525e322f..38e870ef 100644
--- a/kotlinx-coroutines-core/common/src/intrinsics/Undispatched.kt
+++ b/kotlinx-coroutines-core/common/src/intrinsics/Undispatched.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.intrinsics
@@ -82,11 +82,9 @@ private inline fun <T> startDirect(completion: Continuation<T>, block: (Continua
* This function shall be invoked at most once on this coroutine.
* This function checks cancellation of the outer [Job] on fast-path.
*
- * First, this function initializes the parent job from the `parentContext` of this coroutine that was passed to it
- * during construction. Second, it starts the coroutine using [startCoroutineUninterceptedOrReturn].
+ * It starts the coroutine using [startCoroutineUninterceptedOrReturn].
*/
internal fun <T, R> ScopeCoroutine<T>.startUndispatchedOrReturn(receiver: R, block: suspend R.() -> T): Any? {
- initParentJob()
return undispatchedResult({ true }) {
block.startCoroutineUninterceptedOrReturn(receiver, this)
}
@@ -96,8 +94,8 @@ internal fun <T, R> ScopeCoroutine<T>.startUndispatchedOrReturn(receiver: R, blo
* Same as [startUndispatchedOrReturn], but ignores [TimeoutCancellationException] on fast-path.
*/
internal fun <T, R> ScopeCoroutine<T>.startUndispatchedOrReturnIgnoreTimeout(
- receiver: R, block: suspend R.() -> T): Any? {
- initParentJob()
+ receiver: R, block: suspend R.() -> T
+): Any? {
return undispatchedResult({ e -> !(e is TimeoutCancellationException && e.coroutine === this) }) {
block.startCoroutineUninterceptedOrReturn(receiver, this)
}
diff --git a/kotlinx-coroutines-core/common/src/selects/Select.kt b/kotlinx-coroutines-core/common/src/selects/Select.kt
index 99c54f84..a7172707 100644
--- a/kotlinx-coroutines-core/common/src/selects/Select.kt
+++ b/kotlinx-coroutines-core/common/src/selects/Select.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
@file:OptIn(ExperimentalContracts::class)
@@ -177,15 +177,17 @@ public interface SelectInstance<in R> {
* corresponding non-suspending version that can be used with a regular `when` expression to select one
* of the alternatives or to perform the default (`else`) action if none of them can be immediately selected.
*
- * | **Receiver** | **Suspending function** | **Select clause** | **Non-suspending version**
- * | ---------------- | --------------------------------------------- | ------------------------------------------------ | --------------------------
- * | [Job] | [join][Job.join] | [onJoin][Job.onJoin] | [isCompleted][Job.isCompleted]
- * | [Deferred] | [await][Deferred.await] | [onAwait][Deferred.onAwait] | [isCompleted][Job.isCompleted]
- * | [SendChannel] | [send][SendChannel.send] | [onSend][SendChannel.onSend] | [offer][SendChannel.offer]
- * | [ReceiveChannel] | [receive][ReceiveChannel.receive] | [onReceive][ReceiveChannel.onReceive] | [poll][ReceiveChannel.poll]
- * | [ReceiveChannel] | [receiveOrNull][ReceiveChannel.receiveOrNull] | [onReceiveOrNull][ReceiveChannel.onReceiveOrNull]| [poll][ReceiveChannel.poll]
- * | [Mutex] | [lock][Mutex.lock] | [onLock][Mutex.onLock] | [tryLock][Mutex.tryLock]
- * | none | [delay] | [onTimeout][SelectBuilder.onTimeout] | none
+ * ### List of supported select methods
+ *
+ * | **Receiver** | **Suspending function** | **Select clause**
+ * | ---------------- | --------------------------------------------- | -----------------------------------------------------
+ * | [Job] | [join][Job.join] | [onJoin][Job.onJoin]
+ * | [Deferred] | [await][Deferred.await] | [onAwait][Deferred.onAwait]
+ * | [SendChannel] | [send][SendChannel.send] | [onSend][SendChannel.onSend]
+ * | [ReceiveChannel] | [receive][ReceiveChannel.receive] | [onReceive][ReceiveChannel.onReceive]
+ * | [ReceiveChannel] | [receiveCatching][ReceiveChannel.receiveCatching] | [onReceiveCatching][ReceiveChannel.onReceiveCatching]
+ * | [Mutex] | [lock][Mutex.lock] | [onLock][Mutex.onLock]
+ * | none | [delay] | [onTimeout][SelectBuilder.onTimeout]
*
* This suspending function is cancellable. If the [Job] of the current coroutine is cancelled or completed while this
* function is suspended, this function immediately resumes with [CancellationException].
@@ -327,19 +329,18 @@ internal class SelectBuilderImpl<in R>(
private fun initCancellability() {
val parent = context[Job] ?: return
val newRegistration = parent.invokeOnCompletion(
- onCancelling = true, handler = SelectOnCancelling(parent).asHandler)
+ onCancelling = true, handler = SelectOnCancelling().asHandler)
parentHandle = newRegistration
// now check our state _after_ registering
if (isSelected) newRegistration.dispose()
}
- private inner class SelectOnCancelling(job: Job) : JobCancellingNode<Job>(job) {
+ private inner class SelectOnCancelling : JobCancellingNode() {
// Note: may be invoked multiple times, but only the first trySelect succeeds anyway
override fun invoke(cause: Throwable?) {
if (trySelect())
resumeSelectWithException(job.getCancellationException())
}
- override fun toString(): String = "SelectOnCancelling[${this@SelectBuilderImpl}]"
}
@PublishedApi
@@ -553,7 +554,7 @@ internal class SelectBuilderImpl<in R>(
return decision
}
- override val atomicOp: AtomicOp<*>?
+ override val atomicOp: AtomicOp<*>
get() = otherOp.atomicOp
}
diff --git a/kotlinx-coroutines-core/common/src/selects/SelectUnbiased.kt b/kotlinx-coroutines-core/common/src/selects/SelectUnbiased.kt
index edcf123b..c33c5b1f 100644
--- a/kotlinx-coroutines-core/common/src/selects/SelectUnbiased.kt
+++ b/kotlinx-coroutines-core/common/src/selects/SelectUnbiased.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.selects
@@ -36,7 +36,7 @@ internal class UnbiasedSelectBuilderImpl<in R>(uCont: Continuation<R>) :
val clauses = arrayListOf<() -> Unit>()
@PublishedApi
- internal fun handleBuilderException(e: Throwable) = instance.handleBuilderException(e)
+ internal fun handleBuilderException(e: Throwable): Unit = instance.handleBuilderException(e)
@PublishedApi
internal fun initSelectResult(): Any? {
diff --git a/kotlinx-coroutines-core/common/src/selects/WhileSelect.kt b/kotlinx-coroutines-core/common/src/selects/WhileSelect.kt
index 33d4d7ec..98a9c672 100644
--- a/kotlinx-coroutines-core/common/src/selects/WhileSelect.kt
+++ b/kotlinx-coroutines-core/common/src/selects/WhileSelect.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.selects
diff --git a/kotlinx-coroutines-core/common/src/sync/Mutex.kt b/kotlinx-coroutines-core/common/src/sync/Mutex.kt
index 73aaab5f..19584e09 100644
--- a/kotlinx-coroutines-core/common/src/sync/Mutex.kt
+++ b/kotlinx-coroutines-core/common/src/sync/Mutex.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.sync
@@ -10,7 +10,6 @@ import kotlinx.coroutines.internal.*
import kotlinx.coroutines.intrinsics.*
import kotlinx.coroutines.selects.*
import kotlin.contracts.*
-import kotlin.coroutines.*
import kotlin.jvm.*
import kotlin.native.concurrent.*
@@ -124,8 +123,6 @@ private val LOCK_FAIL = Symbol("LOCK_FAIL")
@SharedImmutable
private val UNLOCK_FAIL = Symbol("UNLOCK_FAIL")
@SharedImmutable
-private val SELECT_SUCCESS = Symbol("SELECT_SUCCESS")
-@SharedImmutable
private val LOCKED = Symbol("LOCKED")
@SharedImmutable
private val UNLOCKED = Symbol("UNLOCKED")
@@ -191,7 +188,7 @@ internal class MutexImpl(locked: Boolean) : Mutex, SelectClause2<Any?, Mutex> {
}
private suspend fun lockSuspend(owner: Any?) = suspendCancellableCoroutineReusable<Unit> sc@ { cont ->
- val waiter = LockCont(owner, cont)
+ var waiter = LockCont(owner, cont)
_state.loop { state ->
when (state) {
is Empty -> {
@@ -201,7 +198,8 @@ internal class MutexImpl(locked: Boolean) : Mutex, SelectClause2<Any?, Mutex> {
// try lock
val update = if (owner == null) EMPTY_LOCKED else Empty(owner)
if (_state.compareAndSet(state, update)) { // locked
- cont.resume(Unit)
+ // TODO implement functional type in LockCont as soon as we get rid of legacy JS
+ cont.resume(Unit) { unlock(owner) }
return@sc
}
}
@@ -209,11 +207,24 @@ internal class MutexImpl(locked: Boolean) : Mutex, SelectClause2<Any?, Mutex> {
is LockedQueue -> {
val curOwner = state.owner
check(curOwner !== owner) { "Already locked by $owner" }
- if (state.addLastIf(waiter) { _state.value === state }) {
- // added to waiter list!
+
+ state.addLast(waiter)
+ /*
+ * If the state has been changed while we were adding the waiter,
+ * it means that 'unlock' has taken it and _either_ resumed it successfully or just overwritten.
+ * To rendezvous that, we try to "invalidate" our node and go for retry.
+ *
+ * Node has to be re-instantiated as we do not support node re-adding, even to
+ * another list
+ */
+ if (_state.value === state || !waiter.take()) {
+ // added to waiter list
cont.removeOnCancellation(waiter)
return@sc
}
+
+ waiter = LockCont(owner, cont)
+ return@loop
}
is OpDescriptor -> state.perform(this) // help
else -> error("Illegal state $state")
@@ -251,8 +262,17 @@ internal class MutexImpl(locked: Boolean) : Mutex, SelectClause2<Any?, Mutex> {
is LockedQueue -> {
check(state.owner !== owner) { "Already locked by $owner" }
val node = LockSelect(owner, select, block)
- if (state.addLastIf(node) { _state.value === state }) {
- // successfully enqueued
+ /*
+ * If the state has been changed while we were adding the waiter,
+ * it means that 'unlock' has taken it and _either_ resumed it successfully or just overwritten.
+ * To rendezvous that, we try to "invalidate" our node and go for retry.
+ *
+ * Node has to be re-instantiated as we do not support node re-adding, even to
+ * another list
+ */
+ state.addLast(node)
+ if (_state.value === state || !node.take()) {
+ // added to waiter list
select.disposeOnSelect(node)
return
}
@@ -299,7 +319,7 @@ internal class MutexImpl(locked: Boolean) : Mutex, SelectClause2<Any?, Mutex> {
}
}
- public override fun unlock(owner: Any?) {
+ override fun unlock(owner: Any?) {
_state.loop { state ->
when (state) {
is Empty -> {
@@ -318,10 +338,9 @@ internal class MutexImpl(locked: Boolean) : Mutex, SelectClause2<Any?, Mutex> {
val op = UnlockOp(state)
if (_state.compareAndSet(state, op) && op.perform(this) == null) return
} else {
- val token = (waiter as LockWaiter).tryResumeLockWaiter()
- if (token != null) {
+ if ((waiter as LockWaiter).tryResumeLockWaiter()) {
state.owner = waiter.owner ?: LOCKED
- waiter.completeResumeLockWaiter(token)
+ waiter.completeResumeLockWaiter()
return
}
}
@@ -351,21 +370,28 @@ internal class MutexImpl(locked: Boolean) : Mutex, SelectClause2<Any?, Mutex> {
private abstract inner class LockWaiter(
@JvmField val owner: Any?
) : LockFreeLinkedListNode(), DisposableHandle {
+ private val isTaken = atomic<Boolean>(false)
+ fun take(): Boolean = isTaken.compareAndSet(false, true)
final override fun dispose() { remove() }
- abstract fun tryResumeLockWaiter(): Any?
- abstract fun completeResumeLockWaiter(token: Any)
+ abstract fun tryResumeLockWaiter(): Boolean
+ abstract fun completeResumeLockWaiter()
}
private inner class LockCont(
owner: Any?,
- @JvmField val cont: CancellableContinuation<Unit>
+ private val cont: CancellableContinuation<Unit>
) : LockWaiter(owner) {
- override fun tryResumeLockWaiter() = cont.tryResume(Unit, idempotent = null) {
- // if this continuation gets cancelled during dispatch to the caller, then release the lock
- unlock(owner)
+
+ override fun tryResumeLockWaiter(): Boolean {
+ if (!take()) return false
+ return cont.tryResume(Unit, idempotent = null) {
+ // if this continuation gets cancelled during dispatch to the caller, then release the lock
+ unlock(owner)
+ } != null
}
- override fun completeResumeLockWaiter(token: Any) = cont.completeResume(token)
- override fun toString(): String = "LockCont[$owner, $cont] for ${this@MutexImpl}"
+
+ override fun completeResumeLockWaiter() = cont.completeResume(RESUME_TOKEN)
+ override fun toString(): String = "LockCont[$owner, ${cont}] for ${this@MutexImpl}"
}
private inner class LockSelect<R>(
@@ -373,9 +399,8 @@ internal class MutexImpl(locked: Boolean) : Mutex, SelectClause2<Any?, Mutex> {
@JvmField val select: SelectInstance<R>,
@JvmField val block: suspend (Mutex) -> R
) : LockWaiter(owner) {
- override fun tryResumeLockWaiter(): Any? = if (select.trySelect()) SELECT_SUCCESS else null
- override fun completeResumeLockWaiter(token: Any) {
- assert { token === SELECT_SUCCESS }
+ override fun tryResumeLockWaiter(): Boolean = take() && select.trySelect()
+ override fun completeResumeLockWaiter() {
block.startCoroutineCancellable(receiver = this@MutexImpl, completion = select.completion) {
// if this continuation gets cancelled during dispatch to the caller, then release the lock
unlock(owner)
diff --git a/kotlinx-coroutines-core/common/src/sync/Semaphore.kt b/kotlinx-coroutines-core/common/src/sync/Semaphore.kt
index 84b7f4f8..e8b28bc1 100644
--- a/kotlinx-coroutines-core/common/src/sync/Semaphore.kt
+++ b/kotlinx-coroutines-core/common/src/sync/Semaphore.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.sync
@@ -172,7 +172,7 @@ private class SemaphoreImpl(private val permits: Int, acquiredPermits: Int) : Se
if (addAcquireToQueue(cont)) return@sc
val p = _availablePermits.getAndDecrement()
if (p > 0) { // permit acquired
- cont.resume(Unit)
+ cont.resume(Unit, onCancellationRelease)
return@sc
}
}
@@ -206,9 +206,8 @@ private class SemaphoreImpl(private val permits: Int, acquiredPermits: Int) : Se
// On CAS failure -- the cell must be either PERMIT or BROKEN
// If the cell already has PERMIT from tryResumeNextFromQueue, try to grab it
if (segment.cas(i, PERMIT, TAKEN)) { // took permit thus eliminating acquire/release pair
- // The following resume must always succeed, since continuation was not published yet and we don't have
- // to pass onCancellationRelease handle, since the coroutine did not suspend yet and cannot be cancelled
- cont.resume(Unit)
+ /// This continuation is not yet published, but still can be cancelled via outer job
+ cont.resume(Unit, onCancellationRelease)
return true
}
assert { segment.get(i) === BROKEN } // it must be broken in this case, no other way around it
diff --git a/kotlinx-coroutines-core/common/test/AbstractCoroutineTest.kt b/kotlinx-coroutines-core/common/test/AbstractCoroutineTest.kt
index ce20837e..ebe88ce1 100644
--- a/kotlinx-coroutines-core/common/test/AbstractCoroutineTest.kt
+++ b/kotlinx-coroutines-core/common/test/AbstractCoroutineTest.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines
@@ -13,7 +13,7 @@ class AbstractCoroutineTest : TestBase() {
fun testNotifications() = runTest {
expect(1)
val coroutineContext = coroutineContext // workaround for KT-22984
- val coroutine = object : AbstractCoroutine<String>(coroutineContext, false) {
+ val coroutine = object : AbstractCoroutine<String>(coroutineContext, true, false) {
override fun onStart() {
expect(3)
}
@@ -53,7 +53,7 @@ class AbstractCoroutineTest : TestBase() {
fun testNotificationsWithException() = runTest {
expect(1)
val coroutineContext = coroutineContext // workaround for KT-22984
- val coroutine = object : AbstractCoroutine<String>(coroutineContext + NonCancellable, false) {
+ val coroutine = object : AbstractCoroutine<String>(coroutineContext + NonCancellable, true, false) {
override fun onStart() {
expect(3)
}
@@ -91,4 +91,4 @@ class AbstractCoroutineTest : TestBase() {
coroutine.resumeWithException(TestException2())
finish(10)
}
-} \ No newline at end of file
+}
diff --git a/kotlinx-coroutines-core/common/test/AsyncLazyTest.kt b/kotlinx-coroutines-core/common/test/AsyncLazyTest.kt
index 5b23b641..cd240104 100644
--- a/kotlinx-coroutines-core/common/test/AsyncLazyTest.kt
+++ b/kotlinx-coroutines-core/common/test/AsyncLazyTest.kt
@@ -76,7 +76,7 @@ class AsyncLazyTest : TestBase() {
expected = { it is TestException }
) {
expect(1)
- val d = async(start = CoroutineStart.LAZY) {
+ val d = async<Unit>(start = CoroutineStart.LAZY) {
finish(3)
throw TestException()
}
@@ -90,7 +90,7 @@ class AsyncLazyTest : TestBase() {
expected = { it is TestException }
) {
expect(1)
- val d = async(start = CoroutineStart.LAZY) {
+ val d = async<Unit>(start = CoroutineStart.LAZY) {
expect(3)
yield() // this has not effect, because parent coroutine is waiting
finish(4)
@@ -104,7 +104,7 @@ class AsyncLazyTest : TestBase() {
@Test
fun testCatchException() = runTest {
expect(1)
- val d = async(NonCancellable, start = CoroutineStart.LAZY) {
+ val d = async<Unit>(NonCancellable, start = CoroutineStart.LAZY) {
expect(3)
throw TestException()
}
@@ -184,4 +184,4 @@ class AsyncLazyTest : TestBase() {
assertEquals(d.await(), 42) // await shall throw CancellationException
expectUnreached()
}
-} \ No newline at end of file
+}
diff --git a/kotlinx-coroutines-core/common/test/AsyncTest.kt b/kotlinx-coroutines-core/common/test/AsyncTest.kt
index 3019ddea..2096a4d6 100644
--- a/kotlinx-coroutines-core/common/test/AsyncTest.kt
+++ b/kotlinx-coroutines-core/common/test/AsyncTest.kt
@@ -43,7 +43,7 @@ class AsyncTest : TestBase() {
@Test
fun testSimpleException() = runTest(expected = { it is TestException }) {
expect(1)
- val d = async {
+ val d = async<Unit> {
finish(3)
throw TestException()
}
@@ -170,7 +170,7 @@ class AsyncTest : TestBase() {
@Test
fun testDeferAndYieldException() = runTest(expected = { it is TestException }) {
expect(1)
- val d = async {
+ val d = async<Unit> {
expect(3)
yield() // no effect, parent waiting
finish(4)
@@ -266,4 +266,38 @@ class AsyncTest : TestBase() {
assertFalse(deferred.isCancelled)
}
+ @Test
+ fun testAsyncWithFinally() = runTest {
+ expect(1)
+
+ @Suppress("UNREACHABLE_CODE")
+ val d = async {
+ expect(3)
+ try {
+ yield() // to main, will cancel
+ } finally {
+ expect(6) // will go there on await
+ return@async "Fail" // result will not override cancellation
+ }
+ expectUnreached()
+ "Fail2"
+ }
+ expect(2)
+ yield() // to async
+ expect(4)
+ check(d.isActive && !d.isCompleted && !d.isCancelled)
+ d.cancel()
+ check(!d.isActive && !d.isCompleted && d.isCancelled)
+ check(!d.isActive && !d.isCompleted && d.isCancelled)
+ expect(5)
+ try {
+ d.await() // awaits
+ expectUnreached() // does not complete normally
+ } catch (e: Throwable) {
+ expect(7)
+ check(e is CancellationException)
+ }
+ check(!d.isActive && d.isCompleted && d.isCancelled)
+ finish(8)
+ }
}
diff --git a/kotlinx-coroutines-core/common/test/AtomicCancellationCommonTest.kt b/kotlinx-coroutines-core/common/test/AtomicCancellationCommonTest.kt
index c763faf2..3881eb27 100644
--- a/kotlinx-coroutines-core/common/test/AtomicCancellationCommonTest.kt
+++ b/kotlinx-coroutines-core/common/test/AtomicCancellationCommonTest.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines
@@ -32,6 +32,38 @@ class AtomicCancellationCommonTest : TestBase() {
}
@Test
+ fun testUndispatchedLaunch() = runTest {
+ expect(1)
+ assertFailsWith<CancellationException> {
+ withContext(Job()) {
+ cancel()
+ launch(start = CoroutineStart.UNDISPATCHED) {
+ expect(2)
+ yield()
+ expectUnreached()
+ }
+ }
+ }
+ finish(3)
+ }
+
+ @Test
+ fun testUndispatchedLaunchWithUnconfinedContext() = runTest {
+ expect(1)
+ assertFailsWith<CancellationException> {
+ withContext(Dispatchers.Unconfined + Job()) {
+ cancel()
+ launch(start = CoroutineStart.UNDISPATCHED) {
+ expect(2)
+ yield()
+ expectUnreached()
+ }
+ }
+ }
+ finish(3)
+ }
+
+ @Test
fun testDeferredAwaitCancellable() = runTest {
expect(1)
val deferred = async { // deferred, not yet complete
@@ -108,7 +140,7 @@ class AtomicCancellationCommonTest : TestBase() {
val mutex = Mutex(true) // locked mutex
val job = launch(start = CoroutineStart.UNDISPATCHED) {
expect(2)
- val result = select<String> { // suspends
+ select<String> { // suspends
mutex.onLock {
expect(4)
"OK"
@@ -122,4 +154,4 @@ class AtomicCancellationCommonTest : TestBase() {
yield() // now yield
finish(4)
}
-} \ No newline at end of file
+}
diff --git a/kotlinx-coroutines-core/common/test/AwaitTest.kt b/kotlinx-coroutines-core/common/test/AwaitTest.kt
index 0949b62c..10d5b919 100644
--- a/kotlinx-coroutines-core/common/test/AwaitTest.kt
+++ b/kotlinx-coroutines-core/common/test/AwaitTest.kt
@@ -351,4 +351,32 @@ class AwaitTest : TestBase() {
async(NonCancellable) { throw TestException() }
joinAll(job, job, job)
}
+
+ @Test
+ fun testAwaitAllDelegates() = runTest {
+ expect(1)
+ val deferred = CompletableDeferred<String>()
+ val delegate = object : Deferred<String> by deferred {}
+ launch {
+ expect(3)
+ deferred.complete("OK")
+ }
+ expect(2)
+ awaitAll(delegate)
+ finish(4)
+ }
+
+ @Test
+ fun testCancelAwaitAllDelegate() = runTest {
+ expect(1)
+ val deferred = CompletableDeferred<String>()
+ val delegate = object : Deferred<String> by deferred {}
+ launch {
+ expect(3)
+ deferred.cancel()
+ }
+ expect(2)
+ assertFailsWith<CancellationException> { awaitAll(delegate) }
+ finish(4)
+ }
}
diff --git a/kotlinx-coroutines-core/common/test/BuilderContractsTest.kt b/kotlinx-coroutines-core/common/test/BuilderContractsTest.kt
index b20dd6b1..5a96c544 100644
--- a/kotlinx-coroutines-core/common/test/BuilderContractsTest.kt
+++ b/kotlinx-coroutines-core/common/test/BuilderContractsTest.kt
@@ -1,9 +1,10 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines
+import kotlinx.coroutines.channels.*
import kotlinx.coroutines.selects.*
import kotlin.test.*
@@ -44,9 +45,22 @@ class BuilderContractsTest : TestBase() {
Job().apply { complete() }.onJoin {}
}
consume(s)
+
+
+ val ch: Int
+ val i = Channel<Int>()
+ i.consume {
+ ch = 321
+ }
+ consume(ch)
}
private fun consume(a: Int) {
- a.hashCode() // BE codegen verification
+ /*
+ * Verify the value is actually set correctly
+ * (non-zero, VerificationError is not triggered, can be read)
+ */
+ assertNotEquals(0, a)
+ assertEquals(a.hashCode(), a)
}
-} \ No newline at end of file
+}
diff --git a/kotlinx-coroutines-core/common/test/CancellableResumeTest.kt b/kotlinx-coroutines-core/common/test/CancellableResumeTest.kt
index fbfa0825..bff97196 100644
--- a/kotlinx-coroutines-core/common/test/CancellableResumeTest.kt
+++ b/kotlinx-coroutines-core/common/test/CancellableResumeTest.kt
@@ -82,7 +82,7 @@ class CancellableResumeTest : TestBase() {
cont.invokeOnCancellation { expect(3) }
ctx.cancel()
expect(4)
- cont.resume("OK") { cause ->
+ cont.resume("OK") {
expect(5)
}
finish(6)
@@ -108,7 +108,7 @@ class CancellableResumeTest : TestBase() {
}
ctx.cancel()
expect(4)
- cont.resume("OK") { cause ->
+ cont.resume("OK") {
expect(5)
throw TestException3("FAIL") // onCancellation block fails with exception
}
diff --git a/kotlinx-coroutines-core/common/test/CancelledParentAttachTest.kt b/kotlinx-coroutines-core/common/test/CancelledParentAttachTest.kt
new file mode 100644
index 00000000..9dd61b80
--- /dev/null
+++ b/kotlinx-coroutines-core/common/test/CancelledParentAttachTest.kt
@@ -0,0 +1,115 @@
+/*
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines
+
+import kotlinx.coroutines.channels.*
+import kotlinx.coroutines.flow.internal.*
+import kotlin.test.*
+
+class CancelledParentAttachTest : TestBase() {
+
+ @Test
+ fun testAsync() = runTest {
+ CoroutineStart.values().forEach { testAsyncCancelledParent(it) }
+ }
+
+ private suspend fun testAsyncCancelledParent(start: CoroutineStart) {
+ try {
+ withContext(Job()) {
+ cancel()
+ expect(1)
+ val d = async<Int>(start = start) { 42 }
+ expect(2)
+ d.invokeOnCompletion {
+ finish(3)
+ reset()
+ }
+ }
+ expectUnreached()
+ } catch (e: CancellationException) {
+ // Expected
+ }
+ }
+
+ @Test
+ fun testLaunch() = runTest {
+ CoroutineStart.values().forEach { testLaunchCancelledParent(it) }
+ }
+
+ private suspend fun testLaunchCancelledParent(start: CoroutineStart) {
+ try {
+ withContext(Job()) {
+ cancel()
+ expect(1)
+ val d = launch(start = start) { }
+ expect(2)
+ d.invokeOnCompletion {
+ finish(3)
+ reset()
+ }
+ }
+ expectUnreached()
+ } catch (e: CancellationException) {
+ // Expected
+ }
+ }
+
+ @Test
+ fun testProduce() = runTest({ it is CancellationException }) {
+ cancel()
+ expect(1)
+ val d = produce<Int> { }
+ expect(2)
+ (d as Job).invokeOnCompletion {
+ finish(3)
+ reset()
+ }
+ }
+
+ @Test
+ fun testBroadcast() = runTest {
+ CoroutineStart.values().forEach { testBroadcastCancelledParent(it) }
+ }
+
+ private suspend fun testBroadcastCancelledParent(start: CoroutineStart) {
+ try {
+ withContext(Job()) {
+ cancel()
+ expect(1)
+ val bc = broadcast<Int>(start = start) {}
+ expect(2)
+ (bc as Job).invokeOnCompletion {
+ finish(3)
+ reset()
+ }
+ }
+ expectUnreached()
+ } catch (e: CancellationException) {
+ // Expected
+ }
+ }
+
+ @Test
+ fun testScopes() = runTest {
+ testScope { coroutineScope { } }
+ testScope { supervisorScope { } }
+ testScope { flowScope { } }
+ testScope { withTimeout(Long.MAX_VALUE) { } }
+ testScope { withContext(Job()) { } }
+ testScope { withContext(CoroutineName("")) { } }
+ }
+
+ private suspend inline fun testScope(crossinline block: suspend () -> Unit) {
+ try {
+ withContext(Job()) {
+ cancel()
+ block()
+ }
+ expectUnreached()
+ } catch (e: CancellationException) {
+ // Expected
+ }
+ }
+}
diff --git a/kotlinx-coroutines-core/common/test/CoroutineDispatcherOperatorFunInvokeTest.kt b/kotlinx-coroutines-core/common/test/CoroutineDispatcherOperatorFunInvokeTest.kt
index 6fdd3bbe..e6b340cc 100644
--- a/kotlinx-coroutines-core/common/test/CoroutineDispatcherOperatorFunInvokeTest.kt
+++ b/kotlinx-coroutines-core/common/test/CoroutineDispatcherOperatorFunInvokeTest.kt
@@ -65,7 +65,6 @@ class CoroutineDispatcherOperatorFunInvokeTest : TestBase() {
dispatcher.dispatch(context, block)
}
- @ExperimentalCoroutinesApi
override fun isDispatchNeeded(context: CoroutineContext): Boolean {
return dispatcher.isDispatchNeeded(context)
}
diff --git a/kotlinx-coroutines-core/common/test/TestBase.common.kt b/kotlinx-coroutines-core/common/test/TestBase.common.kt
index 0ba80ee5..71c45769 100644
--- a/kotlinx-coroutines-core/common/test/TestBase.common.kt
+++ b/kotlinx-coroutines-core/common/test/TestBase.common.kt
@@ -13,7 +13,22 @@ import kotlin.test.*
public expect val isStressTest: Boolean
public expect val stressTestMultiplier: Int
+/**
+ * The result of a multiplatform asynchronous test.
+ * Aliases into Unit on K/JVM and K/N, and into Promise on K/JS.
+ */
+@Suppress("NO_ACTUAL_FOR_EXPECT")
+public expect class TestResult
+
public expect open class TestBase constructor() {
+ /*
+ * In common tests we emulate parameterized tests
+ * by iterating over parameters space in the single @Test method.
+ * This kind of tests is too slow for JS and does not fit into
+ * the default Mocha timeout, so we're using this flag to bail-out
+ * and run such tests only on JVM and K/N.
+ */
+ public val isBoundByJsTestTimeout: Boolean
public fun error(message: Any, cause: Throwable? = null): Nothing
public fun expect(index: Int)
public fun expectUnreached()
@@ -25,7 +40,7 @@ public expect open class TestBase constructor() {
expected: ((Throwable) -> Boolean)? = null,
unhandled: List<(Throwable) -> Boolean> = emptyList(),
block: suspend CoroutineScope.() -> Unit
- )
+ ): TestResult
}
public suspend inline fun hang(onCancellation: () -> Unit) {
diff --git a/kotlinx-coroutines-core/common/test/UndispatchedResultTest.kt b/kotlinx-coroutines-core/common/test/UndispatchedResultTest.kt
index e2625722..34b81644 100644
--- a/kotlinx-coroutines-core/common/test/UndispatchedResultTest.kt
+++ b/kotlinx-coroutines-core/common/test/UndispatchedResultTest.kt
@@ -55,7 +55,7 @@ class UndispatchedResultTest : TestBase() {
try {
expect(1)
// Will cancel its parent
- async(context) {
+ async<Unit>(context) {
expect(2)
throw TestException()
}.await()
diff --git a/kotlinx-coroutines-core/common/test/WithTimeoutDurationTest.kt b/kotlinx-coroutines-core/common/test/WithTimeoutDurationTest.kt
index b91a87f0..efd55fe2 100644
--- a/kotlinx-coroutines-core/common/test/WithTimeoutDurationTest.kt
+++ b/kotlinx-coroutines-core/common/test/WithTimeoutDurationTest.kt
@@ -17,7 +17,7 @@ class WithTimeoutDurationTest : TestBase() {
@Test
fun testBasicNoSuspend() = runTest {
expect(1)
- val result = withTimeout(10.seconds) {
+ val result = withTimeout(Duration.seconds(10)) {
expect(2)
"OK"
}
@@ -31,7 +31,7 @@ class WithTimeoutDurationTest : TestBase() {
@Test
fun testBasicSuspend() = runTest {
expect(1)
- val result = withTimeout(10.seconds) {
+ val result = withTimeout(Duration.seconds(10)) {
expect(2)
yield()
expect(3)
@@ -54,7 +54,7 @@ class WithTimeoutDurationTest : TestBase() {
}
expect(2)
// test that it does not yield to the above job when started
- val result = withTimeout(1.seconds) {
+ val result = withTimeout(Duration.seconds(1)) {
expect(3)
yield() // yield only now
expect(5)
@@ -74,7 +74,7 @@ class WithTimeoutDurationTest : TestBase() {
fun testYieldBlockingWithTimeout() = runTest(
expected = { it is CancellationException }
) {
- withTimeout(100.milliseconds) {
+ withTimeout(Duration.milliseconds(100)) {
while (true) {
yield()
}
@@ -87,7 +87,7 @@ class WithTimeoutDurationTest : TestBase() {
@Test
fun testWithTimeoutChildWait() = runTest {
expect(1)
- withTimeout(100.milliseconds) {
+ withTimeout(Duration.milliseconds(100)) {
expect(2)
// launch child with timeout
launch {
@@ -102,7 +102,7 @@ class WithTimeoutDurationTest : TestBase() {
@Test
fun testBadClass() = runTest {
val bad = BadClass()
- val result = withTimeout(100.milliseconds) {
+ val result = withTimeout(Duration.milliseconds(100)) {
bad
}
assertSame(bad, result)
@@ -118,9 +118,9 @@ class WithTimeoutDurationTest : TestBase() {
fun testExceptionOnTimeout() = runTest {
expect(1)
try {
- withTimeout(100.milliseconds) {
+ withTimeout(Duration.milliseconds(100)) {
expect(2)
- delay(1000.milliseconds)
+ delay(Duration.milliseconds(1000))
expectUnreached()
"OK"
}
@@ -135,10 +135,10 @@ class WithTimeoutDurationTest : TestBase() {
expected = { it is CancellationException }
) {
expect(1)
- withTimeout(100.milliseconds) {
+ withTimeout(Duration.milliseconds(100)) {
expect(2)
try {
- delay(1000.milliseconds)
+ delay(Duration.milliseconds(1000))
} catch (e: CancellationException) {
finish(3)
}
@@ -151,10 +151,10 @@ class WithTimeoutDurationTest : TestBase() {
fun testSuppressExceptionWithAnotherException() = runTest {
expect(1)
try {
- withTimeout(100.milliseconds) {
+ withTimeout(Duration.milliseconds(100)) {
expect(2)
try {
- delay(1000.milliseconds)
+ delay(Duration.milliseconds(1000))
} catch (e: CancellationException) {
expect(3)
throw TestException()
@@ -172,7 +172,7 @@ class WithTimeoutDurationTest : TestBase() {
fun testNegativeTimeout() = runTest {
expect(1)
try {
- withTimeout(-1.milliseconds) {
+ withTimeout(-Duration.milliseconds(1)) {
expectUnreached()
"OK"
}
@@ -187,7 +187,7 @@ class WithTimeoutDurationTest : TestBase() {
expect(1)
try {
expect(2)
- withTimeout(1.seconds) {
+ withTimeout(Duration.seconds(1)) {
expect(3)
throw TestException()
}
diff --git a/kotlinx-coroutines-core/common/test/WithTimeoutOrNullDurationTest.kt b/kotlinx-coroutines-core/common/test/WithTimeoutOrNullDurationTest.kt
index f72bb7d9..b5777753 100644
--- a/kotlinx-coroutines-core/common/test/WithTimeoutOrNullDurationTest.kt
+++ b/kotlinx-coroutines-core/common/test/WithTimeoutOrNullDurationTest.kt
@@ -19,7 +19,7 @@ class WithTimeoutOrNullDurationTest : TestBase() {
@Test
fun testBasicNoSuspend() = runTest {
expect(1)
- val result = withTimeoutOrNull(10.seconds) {
+ val result = withTimeoutOrNull(Duration.seconds(10)) {
expect(2)
"OK"
}
@@ -33,7 +33,7 @@ class WithTimeoutOrNullDurationTest : TestBase() {
@Test
fun testBasicSuspend() = runTest {
expect(1)
- val result = withTimeoutOrNull(10.seconds) {
+ val result = withTimeoutOrNull(Duration.seconds(10)) {
expect(2)
yield()
expect(3)
@@ -56,7 +56,7 @@ class WithTimeoutOrNullDurationTest : TestBase() {
}
expect(2)
// test that it does not yield to the above job when started
- val result = withTimeoutOrNull(1.seconds) {
+ val result = withTimeoutOrNull(Duration.seconds(1)) {
expect(3)
yield() // yield only now
expect(5)
@@ -74,7 +74,7 @@ class WithTimeoutOrNullDurationTest : TestBase() {
@Test
fun testYieldBlockingWithTimeout() = runTest {
expect(1)
- val result = withTimeoutOrNull(100.milliseconds) {
+ val result = withTimeoutOrNull(Duration.milliseconds(100)) {
while (true) {
yield()
}
@@ -86,7 +86,7 @@ class WithTimeoutOrNullDurationTest : TestBase() {
@Test
fun testSmallTimeout() = runTest {
val channel = Channel<Int>(1)
- val value = withTimeoutOrNull(1.milliseconds) {
+ val value = withTimeoutOrNull(Duration.milliseconds(1)) {
channel.receive()
}
assertNull(value)
@@ -94,7 +94,7 @@ class WithTimeoutOrNullDurationTest : TestBase() {
@Test
fun testThrowException() = runTest(expected = {it is AssertionError}) {
- withTimeoutOrNull(Duration.INFINITE) {
+ withTimeoutOrNull<Unit>(Duration.INFINITE) {
throw AssertionError()
}
}
@@ -103,12 +103,13 @@ class WithTimeoutOrNullDurationTest : TestBase() {
fun testInnerTimeout() = runTest(
expected = { it is CancellationException }
) {
- withTimeoutOrNull(1000.milliseconds) {
- withTimeout(10.milliseconds) {
+ withTimeoutOrNull(Duration.milliseconds(1000)) {
+ withTimeout(Duration.milliseconds(10)) {
while (true) {
yield()
}
}
+ @Suppress("UNREACHABLE_CODE")
expectUnreached() // will timeout
}
expectUnreached() // will timeout
@@ -118,7 +119,7 @@ class WithTimeoutOrNullDurationTest : TestBase() {
fun testNestedTimeout() = runTest(expected = { it is TimeoutCancellationException }) {
withTimeoutOrNull(Duration.INFINITE) {
// Exception from this withTimeout is not suppressed by withTimeoutOrNull
- withTimeout(10.milliseconds) {
+ withTimeout(Duration.milliseconds(10)) {
delay(Duration.INFINITE)
1
}
@@ -130,9 +131,9 @@ class WithTimeoutOrNullDurationTest : TestBase() {
@Test
fun testOuterTimeout() = runTest {
var counter = 0
- val result = withTimeoutOrNull(250.milliseconds) {
+ val result = withTimeoutOrNull(Duration.milliseconds(250)) {
while (true) {
- val inner = withTimeoutOrNull(100.milliseconds) {
+ val inner = withTimeoutOrNull(Duration.milliseconds(100)) {
while (true) {
yield()
}
@@ -148,7 +149,7 @@ class WithTimeoutOrNullDurationTest : TestBase() {
@Test
fun testBadClass() = runTest {
val bad = BadClass()
- val result = withTimeoutOrNull(100.milliseconds) {
+ val result = withTimeoutOrNull(Duration.milliseconds(100)) {
bad
}
assertSame(bad, result)
@@ -163,9 +164,9 @@ class WithTimeoutOrNullDurationTest : TestBase() {
@Test
fun testNullOnTimeout() = runTest {
expect(1)
- val result = withTimeoutOrNull(100.milliseconds) {
+ val result = withTimeoutOrNull(Duration.milliseconds(100)) {
expect(2)
- delay(1000.milliseconds)
+ delay(Duration.milliseconds(1000))
expectUnreached()
"OK"
}
@@ -176,10 +177,10 @@ class WithTimeoutOrNullDurationTest : TestBase() {
@Test
fun testSuppressExceptionWithResult() = runTest {
expect(1)
- val result = withTimeoutOrNull(100.milliseconds) {
+ val result = withTimeoutOrNull(Duration.milliseconds(100)) {
expect(2)
try {
- delay(1000.milliseconds)
+ delay(Duration.milliseconds(1000))
} catch (e: CancellationException) {
expect(3)
}
@@ -193,10 +194,10 @@ class WithTimeoutOrNullDurationTest : TestBase() {
fun testSuppressExceptionWithAnotherException() = runTest {
expect(1)
try {
- withTimeoutOrNull(100.milliseconds) {
+ withTimeoutOrNull(Duration.milliseconds(100)) {
expect(2)
try {
- delay(1000.milliseconds)
+ delay(Duration.milliseconds(1000))
} catch (e: CancellationException) {
expect(3)
throw TestException()
@@ -215,11 +216,11 @@ class WithTimeoutOrNullDurationTest : TestBase() {
@Test
fun testNegativeTimeout() = runTest {
expect(1)
- var result = withTimeoutOrNull(-1.milliseconds) {
+ var result = withTimeoutOrNull(-Duration.milliseconds(1)) {
expectUnreached()
}
assertNull(result)
- result = withTimeoutOrNull(0.milliseconds) {
+ result = withTimeoutOrNull(Duration.milliseconds(0)) {
expectUnreached()
}
assertNull(result)
@@ -231,7 +232,7 @@ class WithTimeoutOrNullDurationTest : TestBase() {
expect(1)
try {
expect(2)
- withTimeoutOrNull(1000.milliseconds) {
+ withTimeoutOrNull<Unit>(Duration.milliseconds(1000)) {
expect(3)
throw TestException()
}
diff --git a/kotlinx-coroutines-core/common/test/WithTimeoutOrNullTest.kt b/kotlinx-coroutines-core/common/test/WithTimeoutOrNullTest.kt
index 40d2758d..90bcf2da 100644
--- a/kotlinx-coroutines-core/common/test/WithTimeoutOrNullTest.kt
+++ b/kotlinx-coroutines-core/common/test/WithTimeoutOrNullTest.kt
@@ -92,7 +92,7 @@ class WithTimeoutOrNullTest : TestBase() {
@Test
fun testThrowException() = runTest(expected = {it is AssertionError}) {
- withTimeoutOrNull(Long.MAX_VALUE) {
+ withTimeoutOrNull<Unit>(Long.MAX_VALUE) {
throw AssertionError()
}
}
@@ -107,6 +107,7 @@ class WithTimeoutOrNullTest : TestBase() {
yield()
}
}
+ @Suppress("UNREACHABLE_CODE")
expectUnreached() // will timeout
}
expectUnreached() // will timeout
diff --git a/kotlinx-coroutines-core/common/test/channels/ArrayBroadcastChannelTest.kt b/kotlinx-coroutines-core/common/test/channels/ArrayBroadcastChannelTest.kt
index a7084296..2d71cc94 100644
--- a/kotlinx-coroutines-core/common/test/channels/ArrayBroadcastChannelTest.kt
+++ b/kotlinx-coroutines-core/common/test/channels/ArrayBroadcastChannelTest.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.channels
@@ -46,7 +46,7 @@ class ArrayBroadcastChannelTest : TestBase() {
assertEquals(2, first.receive()) // suspends
assertFalse(first.isClosedForReceive)
expect(10)
- assertNull(first.receiveOrNull()) // suspends
+ assertTrue(first.receiveCatching().isClosed) // suspends
assertTrue(first.isClosedForReceive)
expect(14)
}
@@ -62,7 +62,7 @@ class ArrayBroadcastChannelTest : TestBase() {
assertEquals(2, second.receive()) // suspends
assertFalse(second.isClosedForReceive)
expect(11)
- assertNull(second.receiveOrNull()) // suspends
+ assertNull(second.receiveCatching().getOrNull()) // suspends
assertTrue(second.isClosedForReceive)
expect(15)
}
@@ -116,9 +116,9 @@ class ArrayBroadcastChannelTest : TestBase() {
expect(6)
assertFalse(sub.isClosedForReceive)
for (x in 1..3)
- assertEquals(x, sub.receiveOrNull())
+ assertEquals(x, sub.receiveCatching().getOrNull())
// and receive close signal
- assertNull(sub.receiveOrNull())
+ assertNull(sub.receiveCatching().getOrNull())
assertTrue(sub.isClosedForReceive)
finish(7)
}
@@ -153,7 +153,7 @@ class ArrayBroadcastChannelTest : TestBase() {
// make sure all of them are consumed
check(!sub.isClosedForReceive)
for (x in 1..5) check(sub.receive() == x)
- check(sub.receiveOrNull() == null)
+ check(sub.receiveCatching().getOrNull() == null)
check(sub.isClosedForReceive)
}
@@ -196,7 +196,7 @@ class ArrayBroadcastChannelTest : TestBase() {
val channel = BroadcastChannel<Int>(1)
val subscription = channel.openSubscription()
subscription.cancel(TestCancellationException())
- subscription.receiveOrNull()
+ subscription.receive()
}
@Test
@@ -208,6 +208,6 @@ class ArrayBroadcastChannelTest : TestBase() {
channel.cancel()
assertTrue(channel.isClosedForSend)
assertTrue(sub.isClosedForReceive)
- check(sub.receiveOrNull() == null)
+ check(sub.receiveCatching().getOrNull() == null)
}
}
diff --git a/kotlinx-coroutines-core/common/test/channels/ArrayChannelTest.kt b/kotlinx-coroutines-core/common/test/channels/ArrayChannelTest.kt
index a57b519f..632fd292 100644
--- a/kotlinx-coroutines-core/common/test/channels/ArrayChannelTest.kt
+++ b/kotlinx-coroutines-core/common/test/channels/ArrayChannelTest.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.channels
@@ -38,17 +38,17 @@ class ArrayChannelTest : TestBase() {
}
@Test
- fun testClosedBufferedReceiveOrNull() = runTest {
+ fun testClosedBufferedReceiveCatching() = runTest {
val q = Channel<Int>(1)
check(q.isEmpty && !q.isClosedForSend && !q.isClosedForReceive)
expect(1)
launch {
expect(5)
check(!q.isEmpty && q.isClosedForSend && !q.isClosedForReceive)
- assertEquals(42, q.receiveOrNull())
+ assertEquals(42, q.receiveCatching().getOrNull())
expect(6)
check(!q.isEmpty && q.isClosedForSend && q.isClosedForReceive)
- assertNull(q.receiveOrNull())
+ assertNull(q.receiveCatching().getOrNull())
expect(7)
}
expect(2)
@@ -86,31 +86,31 @@ class ArrayChannelTest : TestBase() {
}
@Test
- fun testOfferAndPoll() = runTest {
+ fun testTryOp() = runTest {
val q = Channel<Int>(1)
- assertTrue(q.offer(1))
+ assertTrue(q.trySend(1).isSuccess)
expect(1)
launch {
expect(3)
- assertEquals(1, q.poll())
+ assertEquals(1, q.tryReceive().getOrNull())
expect(4)
- assertNull(q.poll())
+ assertNull(q.tryReceive().getOrNull())
expect(5)
assertEquals(2, q.receive()) // suspends
expect(9)
- assertEquals(3, q.poll())
+ assertEquals(3, q.tryReceive().getOrNull())
expect(10)
- assertNull(q.poll())
+ assertNull(q.tryReceive().getOrNull())
expect(11)
}
expect(2)
yield()
expect(6)
- assertTrue(q.offer(2))
+ assertTrue(q.trySend(2).isSuccess)
expect(7)
- assertTrue(q.offer(3))
+ assertTrue(q.trySend(3).isSuccess)
expect(8)
- assertFalse(q.offer(4))
+ assertFalse(q.trySend(4).isSuccess)
yield()
finish(12)
}
@@ -134,7 +134,7 @@ class ArrayChannelTest : TestBase() {
q.cancel()
check(q.isClosedForSend)
check(q.isClosedForReceive)
- assertFailsWith<CancellationException> { q.receiveOrNull() }
+ assertFailsWith<CancellationException> { q.receiveCatching().getOrThrow() }
finish(12)
}
@@ -142,7 +142,7 @@ class ArrayChannelTest : TestBase() {
fun testCancelWithCause() = runTest({ it is TestCancellationException }) {
val channel = Channel<Int>(5)
channel.cancel(TestCancellationException())
- channel.receiveOrNull()
+ channel.receive()
}
@Test
@@ -157,10 +157,10 @@ class ArrayChannelTest : TestBase() {
val capacity = 42
val channel = Channel<Int>(capacity)
repeat(4) {
- channel.offer(-1)
+ channel.trySend(-1)
}
repeat(4) {
- channel.receiveOrNull()
+ channel.receiveCatching().getOrNull()
}
checkBufferChannel(channel, capacity)
}
diff --git a/kotlinx-coroutines-core/common/test/channels/BasicOperationsTest.kt b/kotlinx-coroutines-core/common/test/channels/BasicOperationsTest.kt
index 91d941b3..4538f6c6 100644
--- a/kotlinx-coroutines-core/common/test/channels/BasicOperationsTest.kt
+++ b/kotlinx-coroutines-core/common/test/channels/BasicOperationsTest.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.channels
@@ -15,28 +15,23 @@ class BasicOperationsTest : TestBase() {
}
@Test
- fun testOfferAfterClose() = runTest {
- TestChannelKind.values().forEach { kind -> testOffer(kind) }
+ fun testTrySendToFullChannel() = runTest {
+ TestChannelKind.values().forEach { kind -> testTrySendToFullChannel(kind) }
}
@Test
- fun testSendAfterClose() = runTest {
- TestChannelKind.values().forEach { kind -> testSendAfterClose(kind) }
- }
-
- @Test
- fun testReceiveOrNullAfterClose() = runTest {
- TestChannelKind.values().forEach { kind -> testReceiveOrNull(kind) }
+ fun testTrySendAfterClose() = runTest {
+ TestChannelKind.values().forEach { kind -> testTrySend(kind) }
}
@Test
- fun testReceiveOrNullAfterCloseWithException() = runTest {
- TestChannelKind.values().forEach { kind -> testReceiveOrNullException(kind) }
+ fun testSendAfterClose() = runTest {
+ TestChannelKind.values().forEach { kind -> testSendAfterClose(kind) }
}
@Test
- fun testReceiveOrClosed() = runTest {
- TestChannelKind.values().forEach { kind -> testReceiveOrClosed(kind) }
+ fun testReceiveCatching() = runTest {
+ TestChannelKind.values().forEach { kind -> testReceiveCatching(kind) }
}
@Test
@@ -49,7 +44,7 @@ class BasicOperationsTest : TestBase() {
}
}
expect(1)
- channel.offer(42)
+ channel.trySend(42)
expect(2)
channel.close(AssertionError())
finish(4)
@@ -90,47 +85,8 @@ class BasicOperationsTest : TestBase() {
}
}
- private suspend fun testReceiveOrNull(kind: TestChannelKind) = coroutineScope {
- val channel = kind.create<Int>()
- val d = async(NonCancellable) {
- channel.receive()
- }
-
- yield()
- channel.close()
- assertTrue(channel.isClosedForReceive)
-
- assertNull(channel.receiveOrNull())
- assertNull(channel.poll())
-
- d.join()
- assertTrue(d.getCancellationException().cause is ClosedReceiveChannelException)
- }
-
- private suspend fun testReceiveOrNullException(kind: TestChannelKind) = coroutineScope {
- val channel = kind.create<Int>()
- val d = async(NonCancellable) {
- channel.receive()
- }
-
- yield()
- channel.close(TestException())
- assertTrue(channel.isClosedForReceive)
-
- assertFailsWith<TestException> { channel.poll() }
- try {
- channel.receiveOrNull()
- fail()
- } catch (e: TestException) {
- // Expected
- }
-
- d.join()
- assertTrue(d.getCancellationException().cause is TestException)
- }
-
@Suppress("ReplaceAssertBooleanWithAssertEquality")
- private suspend fun testReceiveOrClosed(kind: TestChannelKind) = coroutineScope {
+ private suspend fun testReceiveCatching(kind: TestChannelKind) = coroutineScope {
reset()
val channel = kind.create<Int>()
launch {
@@ -139,44 +95,58 @@ class BasicOperationsTest : TestBase() {
}
expect(1)
- val result = channel.receiveOrClosed()
- assertEquals(1, result.value)
- assertEquals(1, result.valueOrNull)
- assertTrue(ValueOrClosed.value(1) == result)
+ val result = channel.receiveCatching()
+ assertEquals(1, result.getOrThrow())
+ assertEquals(1, result.getOrNull())
+ assertTrue(ChannelResult.success(1) == result)
expect(3)
launch {
expect(4)
channel.close()
}
- val closed = channel.receiveOrClosed()
+ val closed = channel.receiveCatching()
expect(5)
- assertNull(closed.valueOrNull)
+ assertNull(closed.getOrNull())
assertTrue(closed.isClosed)
- assertNull(closed.closeCause)
- assertTrue(ValueOrClosed.closed<Int>(closed.closeCause) == closed)
+ assertNull(closed.exceptionOrNull())
+ assertTrue(ChannelResult.closed<Int>(closed.exceptionOrNull()) == closed)
finish(6)
}
- private suspend fun testOffer(kind: TestChannelKind) = coroutineScope {
+ private suspend fun testTrySend(kind: TestChannelKind) = coroutineScope {
val channel = kind.create<Int>()
val d = async { channel.send(42) }
yield()
channel.close()
assertTrue(channel.isClosedForSend)
- try {
- channel.offer(2)
- fail()
- } catch (e: ClosedSendChannelException) {
- if (!kind.isConflated) {
- assertEquals(42, channel.receive())
+ channel.trySend(2)
+ .onSuccess { expectUnreached() }
+ .onClosed {
+ assertTrue { it is ClosedSendChannelException}
+ if (!kind.isConflated) {
+ assertEquals(42, channel.receive())
+ }
}
- }
-
d.await()
}
+ private suspend fun testTrySendToFullChannel(kind: TestChannelKind) = coroutineScope {
+ if (kind.isConflated || kind.capacity == Int.MAX_VALUE) return@coroutineScope
+ val channel = kind.create<Int>()
+ // Make it full
+ repeat(11) {
+ channel.trySend(42)
+ }
+ channel.trySend(1)
+ .onSuccess { expectUnreached() }
+ .onFailure { assertNull(it) }
+ .onClosed {
+ expectUnreached()
+ }
+ }
+
/**
* [ClosedSendChannelException] should not be eaten.
* See [https://github.com/Kotlin/kotlinx.coroutines/issues/957]
diff --git a/kotlinx-coroutines-core/common/test/channels/ChannelBufferOverflowTest.kt b/kotlinx-coroutines-core/common/test/channels/ChannelBufferOverflowTest.kt
index 41f60479..0b9a0fdb 100644
--- a/kotlinx-coroutines-core/common/test/channels/ChannelBufferOverflowTest.kt
+++ b/kotlinx-coroutines-core/common/test/channels/ChannelBufferOverflowTest.kt
@@ -11,30 +11,30 @@ class ChannelBufferOverflowTest : TestBase() {
@Test
fun testDropLatest() = runTest {
val c = Channel<Int>(2, BufferOverflow.DROP_LATEST)
- assertTrue(c.offer(1))
- assertTrue(c.offer(2))
- assertTrue(c.offer(3)) // overflows, dropped
+ assertTrue(c.trySend(1).isSuccess)
+ assertTrue(c.trySend(2).isSuccess)
+ assertTrue(c.trySend(3).isSuccess) // overflows, dropped
c.send(4) // overflows dropped
assertEquals(1, c.receive())
- assertTrue(c.offer(5))
- assertTrue(c.offer(6)) // overflows, dropped
+ assertTrue(c.trySend(5).isSuccess)
+ assertTrue(c.trySend(6).isSuccess) // overflows, dropped
assertEquals(2, c.receive())
assertEquals(5, c.receive())
- assertEquals(null, c.poll())
+ assertEquals(null, c.tryReceive().getOrNull())
}
@Test
fun testDropOldest() = runTest {
val c = Channel<Int>(2, BufferOverflow.DROP_OLDEST)
- assertTrue(c.offer(1))
- assertTrue(c.offer(2))
- assertTrue(c.offer(3)) // overflows, keeps 2, 3
+ assertTrue(c.trySend(1).isSuccess)
+ assertTrue(c.trySend(2).isSuccess)
+ assertTrue(c.trySend(3).isSuccess) // overflows, keeps 2, 3
c.send(4) // overflows, keeps 3, 4
assertEquals(3, c.receive())
- assertTrue(c.offer(5))
- assertTrue(c.offer(6)) // overflows, keeps 5, 6
+ assertTrue(c.trySend(5).isSuccess)
+ assertTrue(c.trySend(6).isSuccess) // overflows, keeps 5, 6
assertEquals(5, c.receive())
assertEquals(6, c.receive())
- assertEquals(null, c.poll())
+ assertEquals(null, c.tryReceive().getOrNull())
}
-} \ No newline at end of file
+}
diff --git a/kotlinx-coroutines-core/common/test/channels/ChannelReceiveCatchingTest.kt b/kotlinx-coroutines-core/common/test/channels/ChannelReceiveCatchingTest.kt
new file mode 100644
index 00000000..2341c62e
--- /dev/null
+++ b/kotlinx-coroutines-core/common/test/channels/ChannelReceiveCatchingTest.kt
@@ -0,0 +1,146 @@
+/*
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.channels
+
+import kotlinx.coroutines.*
+import kotlin.test.*
+
+class ChannelReceiveCatchingTest : TestBase() {
+ @Test
+ fun testChannelOfThrowables() = runTest {
+ val channel = Channel<Throwable>()
+ launch {
+ channel.send(TestException1())
+ channel.close(TestException2())
+ }
+
+ val element = channel.receiveCatching()
+ assertTrue(element.getOrThrow() is TestException1)
+ assertTrue(element.getOrNull() is TestException1)
+
+ val closed = channel.receiveCatching()
+ assertTrue(closed.isClosed)
+ assertTrue(closed.isFailure)
+ assertTrue(closed.exceptionOrNull() is TestException2)
+ }
+
+ @Test
+ @Suppress("ReplaceAssertBooleanWithAssertEquality") // inline classes test
+ fun testNullableIntChanel() = runTest {
+ val channel = Channel<Int?>()
+ launch {
+ expect(2)
+ channel.send(1)
+ expect(3)
+ channel.send(null)
+
+ expect(6)
+ channel.close()
+ }
+
+ expect(1)
+ val element = channel.receiveCatching()
+ assertEquals(1, element.getOrThrow())
+ assertEquals(1, element.getOrNull())
+ assertEquals("Value(1)", element.toString())
+ assertTrue(ChannelResult.success(1) == element) // Don't box
+ assertFalse(element.isFailure)
+ assertFalse(element.isClosed)
+
+ expect(4)
+ val nullElement = channel.receiveCatching()
+ assertNull(nullElement.getOrThrow())
+ assertNull(nullElement.getOrNull())
+ assertEquals("Value(null)", nullElement.toString())
+ assertTrue(ChannelResult.success(null) == nullElement) // Don't box
+ assertFalse(element.isFailure)
+ assertFalse(element.isClosed)
+
+ expect(5)
+ val closed = channel.receiveCatching()
+ assertTrue(closed.isClosed)
+ assertTrue(closed.isFailure)
+
+ val closed2 = channel.receiveCatching()
+ assertTrue(closed2.isClosed)
+ assertTrue(closed.isFailure)
+ assertNull(closed2.exceptionOrNull())
+ finish(7)
+ }
+
+ @Test
+ @ExperimentalUnsignedTypes
+ fun testUIntChannel() = runTest {
+ val channel = Channel<UInt>()
+ launch {
+ expect(2)
+ channel.send(1u)
+ yield()
+ expect(4)
+ channel.send((Long.MAX_VALUE - 1).toUInt())
+ expect(5)
+ }
+
+ expect(1)
+ val element = channel.receiveCatching()
+ assertEquals(1u, element.getOrThrow())
+
+ expect(3)
+ val element2 = channel.receiveCatching()
+ assertEquals((Long.MAX_VALUE - 1).toUInt(), element2.getOrThrow())
+ finish(6)
+ }
+
+ @Test
+ fun testCancelChannel() = runTest {
+ val channel = Channel<Boolean>()
+ launch {
+ expect(2)
+ channel.cancel()
+ }
+
+ expect(1)
+ val closed = channel.receiveCatching()
+ assertTrue(closed.isClosed)
+ assertTrue(closed.isFailure)
+ finish(3)
+ }
+
+ @Test
+ @ExperimentalUnsignedTypes
+ fun testReceiveResultChannel() = runTest {
+ val channel = Channel<ChannelResult<UInt>>()
+ launch {
+ channel.send(ChannelResult.success(1u))
+ channel.send(ChannelResult.closed(TestException1()))
+ channel.close(TestException2())
+ }
+
+ val intResult = channel.receiveCatching()
+ assertEquals(1u, intResult.getOrThrow().getOrThrow())
+ assertFalse(intResult.isFailure)
+ assertFalse(intResult.isClosed)
+
+ val closeCauseResult = channel.receiveCatching()
+ assertTrue(closeCauseResult.getOrThrow().exceptionOrNull() is TestException1)
+
+ val closeCause = channel.receiveCatching()
+ assertTrue(closeCause.isClosed)
+ assertTrue(closeCause.isFailure)
+ assertTrue(closeCause.exceptionOrNull() is TestException2)
+ }
+
+ @Test
+ fun testToString() = runTest {
+ val channel = Channel<String>(1)
+ channel.send("message")
+ channel.close(TestException1("OK"))
+ assertEquals("Value(message)", channel.receiveCatching().toString())
+ // toString implementation for exception differs on every platform
+ val str = channel.receiveCatching().toString()
+ if (!str.matches("Closed\\(.*TestException1: OK\\)".toRegex()))
+ error("Unexpected string: '$str'")
+ }
+}
diff --git a/kotlinx-coroutines-core/common/test/channels/ChannelReceiveOrClosedTest.kt b/kotlinx-coroutines-core/common/test/channels/ChannelReceiveOrClosedTest.kt
deleted file mode 100644
index e58b0dee..00000000
--- a/kotlinx-coroutines-core/common/test/channels/ChannelReceiveOrClosedTest.kt
+++ /dev/null
@@ -1,135 +0,0 @@
-/*
- * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
- */
-
-package kotlinx.coroutines.channels
-
-import kotlinx.coroutines.*
-import kotlin.test.*
-
-class ChannelReceiveOrClosedTest : TestBase() {
- @Test
- fun testChannelOfThrowables() = runTest {
- val channel = Channel<Throwable>()
- launch {
- channel.send(TestException1())
- channel.close(TestException2())
- }
-
- val element = channel.receiveOrClosed()
- assertTrue(element.value is TestException1)
- assertTrue(element.valueOrNull is TestException1)
-
- val closed = channel.receiveOrClosed()
- assertTrue(closed.isClosed)
- assertTrue(closed.closeCause is TestException2)
- }
-
- @Test
- @Suppress("ReplaceAssertBooleanWithAssertEquality") // inline classes test
- fun testNullableIntChanel() = runTest {
- val channel = Channel<Int?>()
- launch {
- expect(2)
- channel.send(1)
- expect(3)
- channel.send(null)
-
- expect(6)
- channel.close()
- }
-
- expect(1)
- val element = channel.receiveOrClosed()
- assertEquals(1, element.value)
- assertEquals(1, element.valueOrNull)
- assertEquals("Value(1)", element.toString())
- assertTrue(ValueOrClosed.value(1) == element) // Don't box
-
- expect(4)
- val nullElement = channel.receiveOrClosed()
- assertNull(nullElement.value)
- assertNull(nullElement.valueOrNull)
- assertEquals("Value(null)", nullElement.toString())
- assertTrue(ValueOrClosed.value(null) == nullElement) // Don't box
-
- expect(5)
- val closed = channel.receiveOrClosed()
- assertTrue(closed.isClosed)
-
- val closed2 = channel.receiveOrClosed()
- assertTrue(closed2.isClosed)
- assertNull(closed2.closeCause)
- finish(7)
- }
-
- @Test
- @ExperimentalUnsignedTypes
- fun testUIntChannel() = runTest {
- val channel = Channel<UInt>()
- launch {
- expect(2)
- channel.send(1u)
- yield()
- expect(4)
- channel.send((Long.MAX_VALUE - 1).toUInt())
- expect(5)
- }
-
- expect(1)
- val element = channel.receiveOrClosed()
- assertEquals(1u, element.value)
-
- expect(3)
- val element2 = channel.receiveOrClosed()
- assertEquals((Long.MAX_VALUE - 1).toUInt(), element2.value)
- finish(6)
- }
-
- @Test
- fun testCancelChannel() = runTest {
- val channel = Channel<Boolean>()
- launch {
- expect(2)
- channel.cancel()
- }
-
- expect(1)
- val closed = channel.receiveOrClosed()
- assertTrue(closed.isClosed)
- finish(3)
- }
-
- @Test
- @ExperimentalUnsignedTypes
- fun testReceiveResultChannel() = runTest {
- val channel = Channel<ValueOrClosed<UInt>>()
- launch {
- channel.send(ValueOrClosed.value(1u))
- channel.send(ValueOrClosed.closed(TestException1()))
- channel.close(TestException2())
- }
-
- val intResult = channel.receiveOrClosed()
- assertEquals(1u, intResult.value.value)
-
- val closeCauseResult = channel.receiveOrClosed()
- assertTrue(closeCauseResult.value.closeCause is TestException1)
-
- val closeCause = channel.receiveOrClosed()
- assertTrue(closeCause.isClosed)
- assertTrue(closeCause.closeCause is TestException2)
- }
-
- @Test
- fun testToString() = runTest {
- val channel = Channel<String>(1)
- channel.send("message")
- channel.close(TestException1("OK"))
- assertEquals("Value(message)", channel.receiveOrClosed().toString())
- // toString implementation for exception differs on every platform
- val str = channel.receiveOrClosed().toString()
- if (!str.matches("Closed\\(.*TestException1: OK\\)".toRegex()))
- error("Unexpected string: '$str'")
- }
-}
diff --git a/kotlinx-coroutines-core/common/test/channels/ChannelUndeliveredElementFailureTest.kt b/kotlinx-coroutines-core/common/test/channels/ChannelUndeliveredElementFailureTest.kt
index d2ef3d26..ae05fb8d 100644
--- a/kotlinx-coroutines-core/common/test/channels/ChannelUndeliveredElementFailureTest.kt
+++ b/kotlinx-coroutines-core/common/test/channels/ChannelUndeliveredElementFailureTest.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.channels
@@ -70,36 +70,10 @@ class ChannelUndeliveredElementFailureTest : TestBase() {
}
@Test
- fun testReceiveOrNullCancelledFail() = runTest(unhandled = shouldBeUnhandled) {
+ fun testReceiveCatchingCancelledFail() = runTest(unhandled = shouldBeUnhandled) {
val channel = Channel(onUndeliveredElement = onCancelFail)
val job = launch(start = CoroutineStart.UNDISPATCHED) {
- channel.receiveOrNull()
- expectUnreached() // will be cancelled before it dispatches
- }
- channel.send(item)
- job.cancel()
- }
-
- @Test
- fun testReceiveOrNullSelectCancelledFail() = runTest(unhandled = shouldBeUnhandled) {
- val channel = Channel(onUndeliveredElement = onCancelFail)
- val job = launch(start = CoroutineStart.UNDISPATCHED) {
- select<Unit> {
- channel.onReceiveOrNull {
- expectUnreached()
- }
- }
- expectUnreached() // will be cancelled before it dispatches
- }
- channel.send(item)
- job.cancel()
- }
-
- @Test
- fun testReceiveOrClosedCancelledFail() = runTest(unhandled = shouldBeUnhandled) {
- val channel = Channel(onUndeliveredElement = onCancelFail)
- val job = launch(start = CoroutineStart.UNDISPATCHED) {
- channel.receiveOrClosed()
+ channel.receiveCatching()
expectUnreached() // will be cancelled before it dispatches
}
channel.send(item)
@@ -111,7 +85,7 @@ class ChannelUndeliveredElementFailureTest : TestBase() {
val channel = Channel(onUndeliveredElement = onCancelFail)
val job = launch(start = CoroutineStart.UNDISPATCHED) {
select<Unit> {
- channel.onReceiveOrClosed {
+ channel.onReceiveCatching {
expectUnreached()
}
}
@@ -140,4 +114,4 @@ class ChannelUndeliveredElementFailureTest : TestBase() {
expectUnreached()
}
-} \ No newline at end of file
+}
diff --git a/kotlinx-coroutines-core/common/test/channels/ChannelUndeliveredElementTest.kt b/kotlinx-coroutines-core/common/test/channels/ChannelUndeliveredElementTest.kt
index 0391e000..f26361f2 100644
--- a/kotlinx-coroutines-core/common/test/channels/ChannelUndeliveredElementTest.kt
+++ b/kotlinx-coroutines-core/common/test/channels/ChannelUndeliveredElementTest.kt
@@ -1,3 +1,7 @@
+/*
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
package kotlinx.coroutines.channels
import kotlinx.atomicfu.*
@@ -6,17 +10,19 @@ import kotlin.test.*
class ChannelUndeliveredElementTest : TestBase() {
@Test
- fun testSendSuccessfully() = runAllKindsTest { kind ->
- val channel = kind.create<Resource> { it.cancel() }
- val res = Resource("OK")
- launch {
- channel.send(res)
+ fun testSendSuccessfully() = runTest {
+ runAllKindsTest { kind ->
+ val channel = kind.create<Resource> { it.cancel() }
+ val res = Resource("OK")
+ launch {
+ channel.send(res)
+ }
+ val ok = channel.receive()
+ assertEquals("OK", ok.value)
+ assertFalse(res.isCancelled) // was not cancelled
+ channel.close()
+ assertFalse(res.isCancelled) // still was not cancelled
}
- val ok = channel.receive()
- assertEquals("OK", ok.value)
- assertFalse(res.isCancelled) // was not cancelled
- channel.close()
- assertFalse(res.isCancelled) // still was not cancelled
}
@Test
@@ -51,6 +57,20 @@ class ChannelUndeliveredElementTest : TestBase() {
}
@Test
+ fun testUnlimitedChannelCancelled() = runTest {
+ val channel = Channel<Resource>(Channel.UNLIMITED) { it.cancel() }
+ val resA = Resource("A")
+ val resB = Resource("B")
+ channel.send(resA) // goes to buffer
+ channel.send(resB) // goes to buffer
+ assertFalse(resA.isCancelled) // it is in buffer, not cancelled
+ assertFalse(resB.isCancelled) // it is in buffer, not cancelled
+ channel.cancel() // now cancel the channel
+ assertTrue(resA.isCancelled) // now cancelled in buffer
+ assertTrue(resB.isCancelled) // now cancelled in buffer
+ }
+
+ @Test
fun testConflatedResourceCancelled() = runTest {
val channel = Channel<Resource>(Channel.CONFLATED) { it.cancel() }
val resA = Resource("A")
@@ -68,21 +88,23 @@ class ChannelUndeliveredElementTest : TestBase() {
}
@Test
- fun testSendToClosedChannel() = runAllKindsTest { kind ->
- val channel = kind.create<Resource> { it.cancel() }
- channel.close() // immediately close channel
- val res = Resource("OK")
- assertFailsWith<ClosedSendChannelException> {
- channel.send(res) // send fails to closed channel, resource was not delivered
+ fun testSendToClosedChannel() = runTest {
+ runAllKindsTest { kind ->
+ val channel = kind.create<Resource> { it.cancel() }
+ channel.close() // immediately close channel
+ val res = Resource("OK")
+ assertFailsWith<ClosedSendChannelException> {
+ channel.send(res) // send fails to closed channel, resource was not delivered
+ }
+ assertTrue(res.isCancelled)
}
- assertTrue(res.isCancelled)
}
- private fun runAllKindsTest(test: suspend CoroutineScope.(TestChannelKind) -> Unit) {
+ private suspend fun runAllKindsTest(test: suspend CoroutineScope.(TestChannelKind) -> Unit) {
for (kind in TestChannelKind.values()) {
if (kind.viaBroadcast) continue // does not support onUndeliveredElement
try {
- runTest {
+ withContext(Job()) {
test(kind)
}
} catch(e: Throwable) {
@@ -101,4 +123,19 @@ class ChannelUndeliveredElementTest : TestBase() {
check(!_cancelled.getAndSet(true)) { "Already cancelled" }
}
}
-} \ No newline at end of file
+
+ @Test
+ fun testHandlerIsNotInvoked() = runTest { // #2826
+ val channel = Channel<Unit> {
+ expectUnreached()
+ }
+
+ expect(1)
+ launch {
+ expect(2)
+ channel.receive()
+ }
+ channel.send(Unit)
+ finish(3)
+ }
+}
diff --git a/kotlinx-coroutines-core/common/test/channels/ConflatedBroadcastChannelTest.kt b/kotlinx-coroutines-core/common/test/channels/ConflatedBroadcastChannelTest.kt
index 7dd232f2..a8c2a29c 100644
--- a/kotlinx-coroutines-core/common/test/channels/ConflatedBroadcastChannelTest.kt
+++ b/kotlinx-coroutines-core/common/test/channels/ConflatedBroadcastChannelTest.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.channels
@@ -42,7 +42,7 @@ class ConflatedBroadcastChannelTest : TestBase() {
launch(start = CoroutineStart.UNDISPATCHED) {
expect(2)
val sub = broadcast.openSubscription()
- assertNull(sub.poll())
+ assertNull(sub.tryReceive().getOrNull())
expect(3)
assertEquals("one", sub.receive()) // suspends
expect(6)
@@ -68,7 +68,7 @@ class ConflatedBroadcastChannelTest : TestBase() {
expect(14)
assertEquals("three", sub.receive()) // suspends
expect(17)
- assertNull(sub.receiveOrNull()) // suspends until closed
+ assertNull(sub.receiveCatching().getOrNull()) // suspends until closed
expect(20)
sub.cancel()
expect(21)
diff --git a/kotlinx-coroutines-core/common/test/channels/ConflatedChannelTest.kt b/kotlinx-coroutines-core/common/test/channels/ConflatedChannelTest.kt
index 18f28438..370fd5b9 100644
--- a/kotlinx-coroutines-core/common/test/channels/ConflatedChannelTest.kt
+++ b/kotlinx-coroutines-core/common/test/channels/ConflatedChannelTest.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.channels
@@ -12,14 +12,14 @@ open class ConflatedChannelTest : TestBase() {
Channel<T>(Channel.CONFLATED)
@Test
- fun testBasicConflationOfferPoll() {
+ fun testBasicConflationOfferTryReceive() {
val q = createConflatedChannel<Int>()
- assertNull(q.poll())
- assertTrue(q.offer(1))
- assertTrue(q.offer(2))
- assertTrue(q.offer(3))
- assertEquals(3, q.poll())
- assertNull(q.poll())
+ assertNull(q.tryReceive().getOrNull())
+ assertTrue(q.trySend(1).isSuccess)
+ assertTrue(q.trySend(2).isSuccess)
+ assertTrue(q.trySend(3).isSuccess)
+ assertEquals(3, q.tryReceive().getOrNull())
+ assertNull(q.tryReceive().getOrNull())
}
@Test
@@ -27,7 +27,7 @@ open class ConflatedChannelTest : TestBase() {
val q = createConflatedChannel<Int>()
q.send(1)
q.send(2) // shall conflated previously sent
- assertEquals(2, q.receiveOrNull())
+ assertEquals(2, q.receiveCatching().getOrNull())
}
@Test
@@ -41,7 +41,7 @@ open class ConflatedChannelTest : TestBase() {
// not it is closed for receive, too
assertTrue(q.isClosedForSend)
assertTrue(q.isClosedForReceive)
- assertNull(q.receiveOrNull())
+ assertNull(q.receiveCatching().getOrNull())
}
@Test
@@ -82,7 +82,7 @@ open class ConflatedChannelTest : TestBase() {
q.cancel()
check(q.isClosedForSend)
check(q.isClosedForReceive)
- assertFailsWith<CancellationException> { q.receiveOrNull() }
+ assertFailsWith<CancellationException> { q.receiveCatching().getOrThrow() }
finish(2)
}
@@ -90,6 +90,6 @@ open class ConflatedChannelTest : TestBase() {
fun testCancelWithCause() = runTest({ it is TestCancellationException }) {
val channel = createConflatedChannel<Int>()
channel.cancel(TestCancellationException())
- channel.receiveOrNull()
+ channel.receive()
}
}
diff --git a/kotlinx-coroutines-core/common/test/channels/LinkedListChannelTest.kt b/kotlinx-coroutines-core/common/test/channels/LinkedListChannelTest.kt
index 4233a350..501affb4 100644
--- a/kotlinx-coroutines-core/common/test/channels/LinkedListChannelTest.kt
+++ b/kotlinx-coroutines-core/common/test/channels/LinkedListChannelTest.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.channels
@@ -12,14 +12,14 @@ class LinkedListChannelTest : TestBase() {
fun testBasic() = runTest {
val c = Channel<Int>(Channel.UNLIMITED)
c.send(1)
- check(c.offer(2))
+ assertTrue(c.trySend(2).isSuccess)
c.send(3)
check(c.close())
check(!c.close())
assertEquals(1, c.receive())
- assertEquals(2, c.poll())
- assertEquals(3, c.receiveOrNull())
- assertNull(c.receiveOrNull())
+ assertEquals(2, c.tryReceive().getOrNull())
+ assertEquals(3, c.receiveCatching().getOrNull())
+ assertNull(c.receiveCatching().getOrNull())
}
@Test
@@ -31,13 +31,13 @@ class LinkedListChannelTest : TestBase() {
q.cancel()
check(q.isClosedForSend)
check(q.isClosedForReceive)
- assertFailsWith<CancellationException> { q.receiveOrNull() }
+ assertFailsWith<CancellationException> { q.receive() }
}
@Test
fun testCancelWithCause() = runTest({ it is TestCancellationException }) {
val channel = Channel<Int>(Channel.UNLIMITED)
channel.cancel(TestCancellationException())
- channel.receiveOrNull()
+ channel.receive()
}
}
diff --git a/kotlinx-coroutines-core/common/test/channels/ProduceTest.kt b/kotlinx-coroutines-core/common/test/channels/ProduceTest.kt
index 6ddde001..61ef0726 100644
--- a/kotlinx-coroutines-core/common/test/channels/ProduceTest.kt
+++ b/kotlinx-coroutines-core/common/test/channels/ProduceTest.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.channels
@@ -24,7 +24,7 @@ class ProduceTest : TestBase() {
expect(4)
check(c.receive() == 2)
expect(5)
- check(c.receiveOrNull() == null)
+ assertNull(c.receiveCatching().getOrNull())
finish(7)
}
@@ -49,7 +49,7 @@ class ProduceTest : TestBase() {
expect(4)
c.cancel()
expect(5)
- assertFailsWith<CancellationException> { c.receiveOrNull() }
+ assertFailsWith<CancellationException> { c.receiveCatching().getOrThrow() }
expect(6)
yield() // to produce
finish(8)
@@ -76,7 +76,7 @@ class ProduceTest : TestBase() {
expect(4)
c.cancel(TestCancellationException())
try {
- assertNull(c.receiveOrNull())
+ c.receive()
expectUnreached()
} catch (e: TestCancellationException) {
expect(5)
@@ -96,6 +96,27 @@ class ProduceTest : TestBase() {
}
@Test
+ fun testCancelWhenTheChannelIsClosed() = runTest {
+ val channel = produce<Int> {
+ send(1)
+ close()
+ expect(2)
+ launch {
+ expect(3)
+ hang { expect(5) }
+ }
+ }
+
+ expect(1)
+ channel.receive()
+ yield()
+ expect(4)
+ channel.cancel()
+ (channel as Job).join()
+ finish(6)
+ }
+
+ @Test
fun testAwaitConsumerCancellation() = runTest {
val parent = Job()
val channel = produce<Int>(parent) {
diff --git a/kotlinx-coroutines-core/common/test/channels/RendezvousChannelTest.kt b/kotlinx-coroutines-core/common/test/channels/RendezvousChannelTest.kt
index 4d20d715..c83813e4 100644
--- a/kotlinx-coroutines-core/common/test/channels/RendezvousChannelTest.kt
+++ b/kotlinx-coroutines-core/common/test/channels/RendezvousChannelTest.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.channels
@@ -36,15 +36,15 @@ class RendezvousChannelTest : TestBase() {
}
@Test
- fun testClosedReceiveOrNull() = runTest {
+ fun testClosedReceiveCatching() = runTest {
val q = Channel<Int>(Channel.RENDEZVOUS)
check(q.isEmpty && !q.isClosedForSend && !q.isClosedForReceive)
expect(1)
launch {
expect(3)
- assertEquals(42, q.receiveOrNull())
+ assertEquals(42, q.receiveCatching().getOrNull())
expect(4)
- assertNull(q.receiveOrNull())
+ assertNull(q.receiveCatching().getOrNull())
expect(6)
}
expect(2)
@@ -80,26 +80,26 @@ class RendezvousChannelTest : TestBase() {
}
@Test
- fun testOfferAndPool() = runTest {
+ fun testTrySendTryReceive() = runTest {
val q = Channel<Int>(Channel.RENDEZVOUS)
- assertFalse(q.offer(1))
+ assertFalse(q.trySend(1).isSuccess)
expect(1)
launch {
expect(3)
- assertNull(q.poll())
+ assertNull(q.tryReceive().getOrNull())
expect(4)
assertEquals(2, q.receive())
expect(7)
- assertNull(q.poll())
+ assertNull(q.tryReceive().getOrNull())
yield()
expect(9)
- assertEquals(3, q.poll())
+ assertEquals(3, q.tryReceive().getOrNull())
expect(10)
}
expect(2)
yield()
expect(5)
- assertTrue(q.offer(2))
+ assertTrue(q.trySend(2).isSuccess)
expect(6)
yield()
expect(8)
@@ -233,9 +233,9 @@ class RendezvousChannelTest : TestBase() {
expect(7)
yield() // try to resume sender (it will not resume despite the close!)
expect(8)
- assertEquals(42, q.receiveOrNull())
+ assertEquals(42, q.receiveCatching().getOrNull())
expect(9)
- assertNull(q.receiveOrNull())
+ assertNull(q.receiveCatching().getOrNull())
expect(10)
yield() // to sender, it was resumed!
finish(12)
@@ -266,7 +266,7 @@ class RendezvousChannelTest : TestBase() {
q.cancel()
check(q.isClosedForSend)
check(q.isClosedForReceive)
- assertFailsWith<CancellationException> { q.receiveOrNull() }
+ assertFailsWith<CancellationException> { q.receiveCatching().getOrThrow() }
finish(12)
}
@@ -274,6 +274,6 @@ class RendezvousChannelTest : TestBase() {
fun testCancelWithCause() = runTest({ it is TestCancellationException }) {
val channel = Channel<Int>(Channel.RENDEZVOUS)
channel.cancel(TestCancellationException())
- channel.receiveOrNull()
+ channel.receiveCatching().getOrThrow()
}
}
diff --git a/kotlinx-coroutines-core/common/test/channels/TestChannelKind.kt b/kotlinx-coroutines-core/common/test/channels/TestChannelKind.kt
index 993be78e..f234e141 100644
--- a/kotlinx-coroutines-core/common/test/channels/TestChannelKind.kt
+++ b/kotlinx-coroutines-core/common/test/channels/TestChannelKind.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.channels
@@ -42,11 +42,10 @@ private class ChannelViaBroadcast<E>(
override val isEmpty: Boolean get() = sub.isEmpty
override suspend fun receive(): E = sub.receive()
- override suspend fun receiveOrNull(): E? = sub.receiveOrNull()
- override suspend fun receiveOrClosed(): ValueOrClosed<E> = sub.receiveOrClosed()
- override fun poll(): E? = sub.poll()
+ override suspend fun receiveCatching(): ChannelResult<E> = sub.receiveCatching()
override fun iterator(): ChannelIterator<E> = sub.iterator()
-
+ override fun tryReceive(): ChannelResult<E> = sub.tryReceive()
+
override fun cancel(cause: CancellationException?) = sub.cancel(cause)
// implementing hidden method anyway, so can cast to an internal class
@@ -55,8 +54,6 @@ private class ChannelViaBroadcast<E>(
override val onReceive: SelectClause1<E>
get() = sub.onReceive
- override val onReceiveOrNull: SelectClause1<E?>
- get() = sub.onReceiveOrNull
- override val onReceiveOrClosed: SelectClause1<ValueOrClosed<E>>
- get() = sub.onReceiveOrClosed
+ override val onReceiveCatching: SelectClause1<ChannelResult<E>>
+ get() = sub.onReceiveCatching
}
diff --git a/kotlinx-coroutines-core/common/test/flow/VirtualTime.kt b/kotlinx-coroutines-core/common/test/flow/VirtualTime.kt
index ddb1d88a..bba5c6bd 100644
--- a/kotlinx-coroutines-core/common/test/flow/VirtualTime.kt
+++ b/kotlinx-coroutines-core/common/test/flow/VirtualTime.kt
@@ -26,7 +26,7 @@ internal class VirtualTimeDispatcher(enclosingScope: CoroutineScope) : Coroutine
?: error("Event loop is missing, virtual time source works only as part of event loop")
if (delayNanos <= 0) continue
if (delayNanos > 0 && delayNanos != Long.MAX_VALUE) error("Unexpected external delay: $delayNanos")
- val nextTask = heap.minBy { it.deadline } ?: return@launch
+ val nextTask = heap.minByOrNull { it.deadline } ?: return@launch
heap.remove(nextTask)
currentTime = nextTask.deadline
nextTask.run()
@@ -48,7 +48,6 @@ internal class VirtualTimeDispatcher(enclosingScope: CoroutineScope) : Coroutine
originalDispatcher.dispatch(context, block)
}
- @ExperimentalCoroutinesApi
override fun isDispatchNeeded(context: CoroutineContext): Boolean = originalDispatcher.isDispatchNeeded(context)
override fun invokeOnTimeout(timeMillis: Long, block: Runnable, context: CoroutineContext): DisposableHandle {
diff --git a/kotlinx-coroutines-core/common/test/flow/channels/ChannelBuildersFlowTest.kt b/kotlinx-coroutines-core/common/test/flow/channels/ChannelBuildersFlowTest.kt
index f93d0399..410955ce 100644
--- a/kotlinx-coroutines-core/common/test/flow/channels/ChannelBuildersFlowTest.kt
+++ b/kotlinx-coroutines-core/common/test/flow/channels/ChannelBuildersFlowTest.kt
@@ -128,145 +128,6 @@ class ChannelBuildersFlowTest : TestBase() {
}
@Test
- fun testBroadcastChannelAsFlow() = runTest {
- val channel = broadcast {
- repeat(10) {
- send(it + 1)
- }
- }
-
- val sum = channel.asFlow().sum()
- assertEquals(55, sum)
- }
-
- @Test
- fun testExceptionInBroadcast() = runTest {
- expect(1)
- val channel = broadcast(NonCancellable) { // otherwise failure will cancel scope as well
- repeat(10) {
- send(it + 1)
- }
- throw TestException()
- }
- assertEquals(15, channel.asFlow().take(5).sum())
-
- // Workaround for JS bug
- try {
- channel.asFlow().collect { /* Do nothing */ }
- expectUnreached()
- } catch (e: TestException) {
- finish(2)
- }
- }
-
- @Test
- fun testBroadcastChannelAsFlowLimits() = runTest {
- val channel = BroadcastChannel<Int>(1)
- val flow = channel.asFlow().map { it * it }.drop(1).take(2)
-
- var expected = 0
- launch {
- assertTrue(channel.offer(1)) // Handed to the coroutine
- assertTrue(channel.offer(2)) // Buffered
- assertFalse(channel.offer(3)) // Failed to offer
- channel.send(3)
- yield()
- assertEquals(1, expected)
- assertTrue(channel.offer(4)) // Handed to the coroutine
- assertTrue(channel.offer(5)) // Buffered
- assertFalse(channel.offer(6)) // Failed to offer
- channel.send(6)
- assertEquals(2, expected)
- }
-
- val sum = flow.sum()
- assertEquals(13, sum)
- ++expected
- val sum2 = flow.sum()
- assertEquals(61, sum2)
- ++expected
- }
-
- @Test
- fun flowAsBroadcast() = runTest {
- val flow = flow {
- repeat(10) {
- emit(it)
- }
- }
-
- val channel = flow.broadcastIn(this)
- assertEquals((0..9).toList(), channel.openSubscription().toList())
- }
-
- @Test
- fun flowAsBroadcastMultipleSubscription() = runTest {
- val flow = flow {
- repeat(10) {
- emit(it)
- }
- }
-
- val broadcast = flow.broadcastIn(this)
- val channel = broadcast.openSubscription()
- val channel2 = broadcast.openSubscription()
-
- assertEquals(0, channel.receive())
- assertEquals(0, channel2.receive())
- yield()
- assertEquals(1, channel.receive())
- assertEquals(1, channel2.receive())
-
- channel.cancel()
- channel2.cancel()
- yield()
- ensureActive()
- }
-
- @Test
- fun flowAsBroadcastException() = runTest {
- val flow = flow {
- repeat(10) {
- emit(it)
- }
-
- throw TestException()
- }
-
- val channel = flow.broadcastIn(this + NonCancellable)
- assertFailsWith<TestException> { channel.openSubscription().toList() }
- assertTrue(channel.isClosedForSend) // Failure in the flow fails the channel
- }
-
- // Semantics of these tests puzzle me, we should figure out the way to prohibit such chains
- @Test
- fun testFlowAsBroadcastAsFlow() = runTest {
- val flow = flow {
- emit(1)
- emit(2)
- emit(3)
- }.broadcastIn(this).asFlow()
-
- assertEquals(6, flow.sum())
- assertEquals(0, flow.sum()) // Well suddenly flow is no longer idempotent and cold
- }
-
- @Test
- fun testBroadcastAsFlowAsBroadcast() = runTest {
- val channel = broadcast {
- send(1)
- }.asFlow().broadcastIn(this)
-
- channel.openSubscription().consumeEach {
- assertEquals(1, it)
- }
-
- channel.openSubscription().consumeEach {
- fail()
- }
- }
-
- @Test
fun testProduceInAtomicity() = runTest {
val flow = flowOf(1).onCompletion { expect(2) }
val scope = CoroutineScope(wrapperDispatcher())
diff --git a/kotlinx-coroutines-core/common/test/flow/channels/ChannelFlowTest.kt b/kotlinx-coroutines-core/common/test/flow/channels/ChannelFlowTest.kt
index b115150a..f197a214 100644
--- a/kotlinx-coroutines-core/common/test/flow/channels/ChannelFlowTest.kt
+++ b/kotlinx-coroutines-core/common/test/flow/channels/ChannelFlowTest.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.flow
@@ -12,9 +12,9 @@ class ChannelFlowTest : TestBase() {
@Test
fun testRegular() = runTest {
val flow = channelFlow {
- assertTrue(offer(1))
- assertTrue(offer(2))
- assertTrue(offer(3))
+ assertTrue(trySend(1).isSuccess)
+ assertTrue(trySend(2).isSuccess)
+ assertTrue(trySend(3).isSuccess)
}
assertEquals(listOf(1, 2, 3), flow.toList())
}
@@ -22,9 +22,9 @@ class ChannelFlowTest : TestBase() {
@Test
fun testBuffer() = runTest {
val flow = channelFlow {
- assertTrue(offer(1))
- assertTrue(offer(2))
- assertFalse(offer(3))
+ assertTrue(trySend(1).isSuccess)
+ assertTrue(trySend(2).isSuccess)
+ assertFalse(trySend(3).isSuccess)
}.buffer(1)
assertEquals(listOf(1, 2), flow.toList())
}
@@ -32,10 +32,10 @@ class ChannelFlowTest : TestBase() {
@Test
fun testConflated() = runTest {
val flow = channelFlow {
- assertTrue(offer(1))
- assertTrue(offer(2))
- assertTrue(offer(3))
- assertTrue(offer(4))
+ assertTrue(trySend(1).isSuccess)
+ assertTrue(trySend(2).isSuccess)
+ assertTrue(trySend(3).isSuccess)
+ assertTrue(trySend(4).isSuccess)
}.buffer(Channel.CONFLATED)
assertEquals(listOf(1, 4), flow.toList()) // two elements in the middle got conflated
}
@@ -43,7 +43,7 @@ class ChannelFlowTest : TestBase() {
@Test
fun testFailureCancelsChannel() = runTest {
val flow = channelFlow {
- offer(1)
+ trySend(1)
invokeOnClose {
expect(2)
}
@@ -194,4 +194,17 @@ class ChannelFlowTest : TestBase() {
assertEquals(listOf(1), flow.toList())
finish(3)
}
+
+ @Test
+ fun testCancelledOnCompletion() = runTest {
+ val myFlow = callbackFlow<Any> {
+ expect(2)
+ close()
+ hang { expect(3) }
+ }
+
+ expect(1)
+ myFlow.collect()
+ finish(4)
+ }
}
diff --git a/kotlinx-coroutines-core/common/test/flow/internal/FlowScopeTest.kt b/kotlinx-coroutines-core/common/test/flow/internal/FlowScopeTest.kt
index d41ab889..e8666479 100644
--- a/kotlinx-coroutines-core/common/test/flow/internal/FlowScopeTest.kt
+++ b/kotlinx-coroutines-core/common/test/flow/internal/FlowScopeTest.kt
@@ -68,10 +68,10 @@ class FlowScopeTest : TestBase() {
flowScope {
flowScope {
launch {
- throw CancellationException(null)
+ throw CancellationException("")
}
}
}
}
}
-} \ No newline at end of file
+}
diff --git a/kotlinx-coroutines-core/common/test/flow/operators/CatchTest.kt b/kotlinx-coroutines-core/common/test/flow/operators/CatchTest.kt
index eedfac2e..447eb73b 100644
--- a/kotlinx-coroutines-core/common/test/flow/operators/CatchTest.kt
+++ b/kotlinx-coroutines-core/common/test/flow/operators/CatchTest.kt
@@ -133,7 +133,7 @@ class CatchTest : TestBase() {
.flowOn(d2)
// flowOn with a different dispatcher introduces asynchrony so that all exceptions in the
// upstream flows are handled before they go downstream
- .onEach { value ->
+ .onEach {
expectUnreached() // already cancelled
}
.catch { e ->
diff --git a/kotlinx-coroutines-core/common/test/flow/operators/DebounceTest.kt b/kotlinx-coroutines-core/common/test/flow/operators/DebounceTest.kt
index ce75e598..aa0893e8 100644
--- a/kotlinx-coroutines-core/common/test/flow/operators/DebounceTest.kt
+++ b/kotlinx-coroutines-core/common/test/flow/operators/DebounceTest.kt
@@ -205,19 +205,19 @@ class DebounceTest : TestBase() {
val flow = flow {
expect(3)
emit("A")
- delay(1500.milliseconds)
+ delay(Duration.milliseconds(1500))
emit("B")
- delay(500.milliseconds)
+ delay(Duration.milliseconds(500))
emit("C")
- delay(250.milliseconds)
+ delay(Duration.milliseconds(250))
emit("D")
- delay(2000.milliseconds)
+ delay(Duration.milliseconds(2000))
emit("E")
expect(4)
}
expect(2)
- val result = flow.debounce(1000.milliseconds).toList()
+ val result = flow.debounce(Duration.milliseconds(1000)).toList()
assertEquals(listOf("A", "D", "E"), result)
finish(5)
}
@@ -296,13 +296,13 @@ class DebounceTest : TestBase() {
val flow = flow {
expect(3)
emit("A")
- delay(1500.milliseconds)
+ delay(Duration.milliseconds(1500))
emit("B")
- delay(500.milliseconds)
+ delay(Duration.milliseconds(500))
emit("C")
- delay(250.milliseconds)
+ delay(Duration.milliseconds(250))
emit("D")
- delay(2000.milliseconds)
+ delay(Duration.milliseconds(2000))
emit("E")
expect(4)
}
@@ -310,9 +310,9 @@ class DebounceTest : TestBase() {
expect(2)
val result = flow.debounce {
if (it == "C") {
- 0.milliseconds
+ Duration.milliseconds(0)
} else {
- 1000.milliseconds
+ Duration.milliseconds(1000)
}
}.toList()
diff --git a/kotlinx-coroutines-core/common/test/flow/operators/FlatMapMergeBaseTest.kt b/kotlinx-coroutines-core/common/test/flow/operators/FlatMapMergeBaseTest.kt
index 44376980..4095172d 100644
--- a/kotlinx-coroutines-core/common/test/flow/operators/FlatMapMergeBaseTest.kt
+++ b/kotlinx-coroutines-core/common/test/flow/operators/FlatMapMergeBaseTest.kt
@@ -90,5 +90,5 @@ abstract class FlatMapMergeBaseTest : FlatMapBaseTest() {
}
@Test
- abstract fun testFlatMapConcurrency()
+ abstract fun testFlatMapConcurrency(): TestResult
}
diff --git a/kotlinx-coroutines-core/common/test/flow/operators/OnCompletionTest.kt b/kotlinx-coroutines-core/common/test/flow/operators/OnCompletionTest.kt
index f55e8bee..0ff2e0b8 100644
--- a/kotlinx-coroutines-core/common/test/flow/operators/OnCompletionTest.kt
+++ b/kotlinx-coroutines-core/common/test/flow/operators/OnCompletionTest.kt
@@ -1,10 +1,11 @@
/*
- * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.flow
import kotlinx.coroutines.*
+import kotlinx.coroutines.channels.*
import kotlinx.coroutines.flow.internal.*
import kotlin.test.*
@@ -290,4 +291,24 @@ class OnCompletionTest : TestBase() {
val expected = (1..5).toList() + (-1)
assertEquals(expected, result)
}
+
+ @Test
+ fun testCancelledEmitAllFlow() = runTest {
+ // emitAll does not call 'collect' on onCompletion collector
+ // if the target flow is empty
+ flowOf(1, 2, 3)
+ .onCompletion { emitAll(MutableSharedFlow()) }
+ .take(1)
+ .collect()
+ }
+
+ @Test
+ fun testCancelledEmitAllChannel() = runTest {
+ // emitAll does not call 'collect' on onCompletion collector
+ // if the target channel is empty
+ flowOf(1, 2, 3)
+ .onCompletion { emitAll(Channel()) }
+ .take(1)
+ .collect()
+ }
}
diff --git a/kotlinx-coroutines-core/common/test/flow/operators/OnEachTest.kt b/kotlinx-coroutines-core/common/test/flow/operators/OnEachTest.kt
index bad9db97..3c1ebfa0 100644
--- a/kotlinx-coroutines-core/common/test/flow/operators/OnEachTest.kt
+++ b/kotlinx-coroutines-core/common/test/flow/operators/OnEachTest.kt
@@ -42,7 +42,6 @@ class OnEachTest : TestBase() {
}.onEach {
latch.receive()
throw TestException()
- it + 1
}.catch { emit(42) }
assertEquals(42, flow.single())
diff --git a/kotlinx-coroutines-core/common/test/flow/operators/SampleTest.kt b/kotlinx-coroutines-core/common/test/flow/operators/SampleTest.kt
index 22a0d4a3..87bee56f 100644
--- a/kotlinx-coroutines-core/common/test/flow/operators/SampleTest.kt
+++ b/kotlinx-coroutines-core/common/test/flow/operators/SampleTest.kt
@@ -268,7 +268,6 @@ class SampleTest : TestBase() {
expect(2)
yield()
throw TestException()
- it
}
assertFailsWith<TestException>(flow)
@@ -282,19 +281,19 @@ class SampleTest : TestBase() {
val flow = flow {
expect(3)
emit("A")
- delay(1500.milliseconds)
+ delay(Duration.milliseconds(1500))
emit("B")
- delay(500.milliseconds)
+ delay(Duration.milliseconds(500))
emit("C")
- delay(250.milliseconds)
+ delay(Duration.milliseconds(250))
emit("D")
- delay(2000.milliseconds)
+ delay(Duration.milliseconds(2000))
emit("E")
expect(4)
}
expect(2)
- val result = flow.sample(1000.milliseconds).toList()
+ val result = flow.sample(Duration.milliseconds(1000)).toList()
assertEquals(listOf("A", "B", "D"), result)
finish(5)
}
diff --git a/kotlinx-coroutines-core/common/test/flow/operators/ScanTest.kt b/kotlinx-coroutines-core/common/test/flow/operators/ScanTest.kt
index 20e07873..c6be36d0 100644
--- a/kotlinx-coroutines-core/common/test/flow/operators/ScanTest.kt
+++ b/kotlinx-coroutines-core/common/test/flow/operators/ScanTest.kt
@@ -24,6 +24,13 @@ class ScanTest : TestBase() {
}
@Test
+ fun testFoldWithInitial() = runTest {
+ val flow = flowOf(1, 2, 3)
+ val result = flow.runningFold(emptyList<Int>()) { acc, value -> acc + value }.toList()
+ assertEquals(listOf(emptyList(), listOf(1), listOf(1, 2), listOf(1, 2, 3)), result)
+ }
+
+ @Test
fun testNulls() = runTest {
val flow = flowOf(null, 2, null, null, null, 5)
val result = flow.runningReduce { acc, v -> if (v == null) acc else (if (acc == null) v else acc + v) }.toList()
diff --git a/kotlinx-coroutines-core/common/test/flow/sharing/ShareInFusionTest.kt b/kotlinx-coroutines-core/common/test/flow/sharing/ShareInFusionTest.kt
index 371d0147..85a17ba0 100644
--- a/kotlinx-coroutines-core/common/test/flow/sharing/ShareInFusionTest.kt
+++ b/kotlinx-coroutines-core/common/test/flow/sharing/ShareInFusionTest.kt
@@ -42,7 +42,7 @@ class ShareInFusionTest : TestBase() {
val flow = channelFlow {
// send a batch of 10 elements using [offer]
for (i in 1..10) {
- assertTrue(offer(i)) // offer must succeed, because buffer
+ assertTrue(trySend(i).isSuccess) // offer must succeed, because buffer
}
send(0) // done
}.buffer(10) // request a buffer of 10
@@ -53,4 +53,4 @@ class ShareInFusionTest : TestBase() {
.collect { i -> expect(i + 1) }
finish(12)
}
-} \ No newline at end of file
+}
diff --git a/kotlinx-coroutines-core/common/test/flow/sharing/ShareInTest.kt b/kotlinx-coroutines-core/common/test/flow/sharing/ShareInTest.kt
index 9020f5f3..db69e2bc 100644
--- a/kotlinx-coroutines-core/common/test/flow/sharing/ShareInTest.kt
+++ b/kotlinx-coroutines-core/common/test/flow/sharing/ShareInTest.kt
@@ -167,11 +167,11 @@ class ShareInTest : TestBase() {
subs += shared
.onEach { value -> // only the first threshold subscribers get the value
when (i) {
- in 1..threshold -> log.offer("sub$i: $value")
+ in 1..threshold -> log.trySend("sub$i: $value")
else -> expectUnreached()
}
}
- .onCompletion { log.offer("sub$i: completion") }
+ .onCompletion { log.trySend("sub$i: completion") }
.launchIn(this)
checkStartTransition(i)
}
@@ -187,11 +187,9 @@ class ShareInTest : TestBase() {
}
@Suppress("TestFunctionName")
- private fun SharingStarted.Companion.WhileSubscribedAtLeast(threshold: Int): SharingStarted =
- object : SharingStarted {
- override fun command(subscriptionCount: StateFlow<Int>): Flow<SharingCommand> =
- subscriptionCount
- .map { if (it >= threshold) SharingCommand.START else SharingCommand.STOP }
+ private fun SharingStarted.Companion.WhileSubscribedAtLeast(threshold: Int) =
+ SharingStarted { subscriptionCount ->
+ subscriptionCount.map { if (it >= threshold) SharingCommand.START else SharingCommand.STOP }
}
private class FlowState {
@@ -212,4 +210,4 @@ class ShareInTest : TestBase() {
stop()
}
}
-} \ No newline at end of file
+}
diff --git a/kotlinx-coroutines-core/common/test/flow/sharing/SharedFlowTest.kt b/kotlinx-coroutines-core/common/test/flow/sharing/SharedFlowTest.kt
index 32d88f3c..6e18b38f 100644
--- a/kotlinx-coroutines-core/common/test/flow/sharing/SharedFlowTest.kt
+++ b/kotlinx-coroutines-core/common/test/flow/sharing/SharedFlowTest.kt
@@ -439,7 +439,8 @@ class SharedFlowTest : TestBase() {
}
@Test
- fun testDifferentBufferedFlowCapacities() {
+ fun testDifferentBufferedFlowCapacities() = runTest {
+ if (isBoundByJsTestTimeout) return@runTest // Too slow for JS, bounded by 2 sec. default JS timeout
for (replay in 0..10) {
for (extraBufferCapacity in 0..5) {
if (replay == 0 && extraBufferCapacity == 0) continue // test only buffered shared flows
@@ -456,7 +457,7 @@ class SharedFlowTest : TestBase() {
}
}
- private fun testBufferedFlow(sh: MutableSharedFlow<Int>, replay: Int) = runTest {
+ private suspend fun testBufferedFlow(sh: MutableSharedFlow<Int>, replay: Int) = withContext(Job()) {
reset()
expect(1)
val n = 100 // initially emitted to fill buffer
@@ -601,6 +602,7 @@ class SharedFlowTest : TestBase() {
}
@Test
+ @Suppress("DEPRECATION") // 'catch'
fun onSubscriptionThrows() = runTest {
expect(1)
val sh = MutableSharedFlow<String>(1)
@@ -678,6 +680,7 @@ class SharedFlowTest : TestBase() {
@Test
fun testStateFlowModel() = runTest {
+ if (isBoundByJsTestTimeout) return@runTest // Too slow for JS, bounded by 2 sec. default JS timeout
val stateFlow = MutableStateFlow<Data?>(null)
val expect = modelLog(stateFlow)
val sharedFlow = MutableSharedFlow<Data?>(
@@ -795,4 +798,4 @@ class SharedFlowTest : TestBase() {
job.join()
finish(5)
}
-} \ No newline at end of file
+}
diff --git a/kotlinx-coroutines-core/common/test/flow/sharing/SharingStartedWhileSubscribedTest.kt b/kotlinx-coroutines-core/common/test/flow/sharing/SharingStartedWhileSubscribedTest.kt
index bcf626e3..516bb2e2 100644
--- a/kotlinx-coroutines-core/common/test/flow/sharing/SharingStartedWhileSubscribedTest.kt
+++ b/kotlinx-coroutines-core/common/test/flow/sharing/SharingStartedWhileSubscribedTest.kt
@@ -30,11 +30,13 @@ class SharingStartedWhileSubscribedTest : TestBase() {
@Test
fun testDurationParams() {
assertEquals(SharingStarted.WhileSubscribed(0), SharingStarted.WhileSubscribed(Duration.ZERO))
- assertEquals(SharingStarted.WhileSubscribed(10), SharingStarted.WhileSubscribed(10.milliseconds))
+ assertEquals(SharingStarted.WhileSubscribed(10), SharingStarted.WhileSubscribed(Duration.milliseconds(10)))
assertEquals(SharingStarted.WhileSubscribed(1000), SharingStarted.WhileSubscribed(1.seconds))
assertEquals(SharingStarted.WhileSubscribed(Long.MAX_VALUE), SharingStarted.WhileSubscribed(Duration.INFINITE))
assertEquals(SharingStarted.WhileSubscribed(replayExpirationMillis = 0), SharingStarted.WhileSubscribed(replayExpiration = Duration.ZERO))
- assertEquals(SharingStarted.WhileSubscribed(replayExpirationMillis = 3), SharingStarted.WhileSubscribed(replayExpiration = 3.milliseconds))
+ assertEquals(SharingStarted.WhileSubscribed(replayExpirationMillis = 3), SharingStarted.WhileSubscribed(
+ replayExpiration = Duration.milliseconds(3)
+ ))
assertEquals(SharingStarted.WhileSubscribed(replayExpirationMillis = 7000), SharingStarted.WhileSubscribed(replayExpiration = 7.seconds))
assertEquals(SharingStarted.WhileSubscribed(replayExpirationMillis = Long.MAX_VALUE), SharingStarted.WhileSubscribed(replayExpiration = Duration.INFINITE))
}
diff --git a/kotlinx-coroutines-core/common/test/flow/sharing/StateFlowTest.kt b/kotlinx-coroutines-core/common/test/flow/sharing/StateFlowTest.kt
index 0a2c0458..be4f8c53 100644
--- a/kotlinx-coroutines-core/common/test/flow/sharing/StateFlowTest.kt
+++ b/kotlinx-coroutines-core/common/test/flow/sharing/StateFlowTest.kt
@@ -174,23 +174,11 @@ class StateFlowTest : TestBase() {
}
@Test
- fun testReferenceUpdatesAndCAS() {
- val d0 = Data(0)
- val d0_1 = Data(0)
- val d1 = Data(1)
- val d1_1 = Data(1)
- val d1_2 = Data(1)
- val state = MutableStateFlow(d0)
- assertSame(d0, state.value)
- state.value = d0_1 // equal, nothing changes
- assertSame(d0, state.value)
- state.value = d1 // updates
- assertSame(d1, state.value)
- assertFalse(state.compareAndSet(d0, d0)) // wrong value
- assertSame(d1, state.value)
- assertTrue(state.compareAndSet(d1_1, d1_2)) // "updates", but ref stays
- assertSame(d1, state.value)
- assertTrue(state.compareAndSet(d1_1, d0)) // updates, reference changes
- assertSame(d0, state.value)
+ fun testUpdate() = runTest {
+ val state = MutableStateFlow(0)
+ state.update { it + 2 }
+ assertEquals(2, state.value)
+ state.update { it + 3 }
+ assertEquals(5, state.value)
}
-} \ No newline at end of file
+}
diff --git a/kotlinx-coroutines-core/common/test/flow/terminal/FoldTest.kt b/kotlinx-coroutines-core/common/test/flow/terminal/FoldTest.kt
index 3c885713..9a920f1d 100644
--- a/kotlinx-coroutines-core/common/test/flow/terminal/FoldTest.kt
+++ b/kotlinx-coroutines-core/common/test/flow/terminal/FoldTest.kt
@@ -47,7 +47,6 @@ class FoldTest : TestBase() {
latch.receive()
expect(4)
throw TestException()
- 42 // Workaround for KT-30642, return type should not be Nothing
}
}
finish(6)
diff --git a/kotlinx-coroutines-core/common/test/flow/terminal/LastTest.kt b/kotlinx-coroutines-core/common/test/flow/terminal/LastTest.kt
new file mode 100644
index 00000000..e7699ccc
--- /dev/null
+++ b/kotlinx-coroutines-core/common/test/flow/terminal/LastTest.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.flow
+
+import kotlinx.coroutines.*
+import kotlin.test.*
+
+class LastTest : TestBase() {
+ @Test
+ fun testLast() = runTest {
+ val flow = flowOf(1, 2, 3)
+ assertEquals(3, flow.last())
+ assertEquals(3, flow.lastOrNull())
+ }
+
+ @Test
+ fun testNulls() = runTest {
+ val flow = flowOf(1, null)
+ assertNull(flow.last())
+ assertNull(flow.lastOrNull())
+ }
+
+ @Test
+ fun testNullsLastOrNull() = runTest {
+ val flow = flowOf(null, 1)
+ assertEquals(1, flow.lastOrNull())
+ }
+
+ @Test
+ fun testEmptyFlow() = runTest {
+ assertFailsWith<NoSuchElementException> { emptyFlow<Int>().last() }
+ assertNull(emptyFlow<Int>().lastOrNull())
+ }
+
+ @Test
+ fun testBadClass() = runTest {
+ val instance = BadClass()
+ val flow = flowOf(instance)
+ assertSame(instance, flow.last())
+ assertSame(instance, flow.lastOrNull())
+
+ }
+}
diff --git a/kotlinx-coroutines-core/common/test/flow/terminal/ReduceTest.kt b/kotlinx-coroutines-core/common/test/flow/terminal/ReduceTest.kt
index 99ee1d66..8ba0b5ef 100644
--- a/kotlinx-coroutines-core/common/test/flow/terminal/ReduceTest.kt
+++ b/kotlinx-coroutines-core/common/test/flow/terminal/ReduceTest.kt
@@ -30,7 +30,7 @@ class ReduceTest : TestBase() {
fun testNullableReduce() = runTest {
val flow = flowOf(1, null, null, 2)
var invocations = 0
- val sum = flow.reduce { acc, value ->
+ val sum = flow.reduce { _, value ->
++invocations
value
}
@@ -67,7 +67,6 @@ class ReduceTest : TestBase() {
latch.receive()
expect(4)
throw TestException()
- 42 // Workaround for KT-30642, return type should not be Nothing
}
}
finish(6)
diff --git a/kotlinx-coroutines-core/common/test/selects/SelectArrayChannelTest.kt b/kotlinx-coroutines-core/common/test/selects/SelectArrayChannelTest.kt
index a4f8c3ba..0158c843 100644
--- a/kotlinx-coroutines-core/common/test/selects/SelectArrayChannelTest.kt
+++ b/kotlinx-coroutines-core/common/test/selects/SelectArrayChannelTest.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.selects
@@ -295,10 +295,10 @@ class SelectArrayChannelTest : TestBase() {
}
expect(2)
select<Unit> {
- channel.onReceiveOrClosed {
+ channel.onReceiveCatching {
expect(5)
assertTrue(it.isClosed)
- assertNull(it.closeCause)
+ assertNull(it.exceptionOrNull())
}
}
@@ -316,10 +316,10 @@ class SelectArrayChannelTest : TestBase() {
}
expect(2)
select<Unit> {
- channel.onReceiveOrClosed {
+ channel.onReceiveCatching {
expect(5)
assertTrue(it.isClosed)
- assertTrue(it.closeCause is TestException)
+ assertTrue(it.exceptionOrNull() is TestException)
}
}
@@ -327,16 +327,16 @@ class SelectArrayChannelTest : TestBase() {
}
@Test
- fun testSelectReceiveOrClosed() = runTest {
+ fun testSelectReceiveCatching() = runTest {
val c = Channel<Int>(1)
val iterations = 10
expect(1)
val job = launch {
repeat(iterations) {
select<Unit> {
- c.onReceiveOrClosed { v ->
+ c.onReceiveCatching { v ->
expect(4 + it * 2)
- assertEquals(it, v.value)
+ assertEquals(it, v.getOrNull())
}
}
}
@@ -360,9 +360,9 @@ class SelectArrayChannelTest : TestBase() {
launch {
expect(3)
val res = select<String> {
- c.onReceiveOrClosed { v ->
+ c.onReceiveCatching { v ->
expect(6)
- assertEquals(42, v.value)
+ assertEquals(42, v.getOrNull())
yield() // back to main
expect(8)
"OK"
@@ -396,9 +396,9 @@ class SelectArrayChannelTest : TestBase() {
expect(1)
select<Unit> {
expect(2)
- channel.onReceiveOrClosed {
+ channel.onReceiveCatching {
assertTrue(it.isClosed)
- assertNull(it.closeCause)
+ assertNull(it.exceptionOrNull())
finish(3)
}
}
@@ -412,9 +412,9 @@ class SelectArrayChannelTest : TestBase() {
expect(1)
select<Unit> {
expect(2)
- channel.onReceiveOrClosed {
+ channel.onReceiveCatching {
assertFalse(it.isClosed)
- assertEquals(42, it.value)
+ assertEquals(42, it.getOrNull())
finish(3)
}
}
diff --git a/kotlinx-coroutines-core/common/test/selects/SelectLoopTest.kt b/kotlinx-coroutines-core/common/test/selects/SelectLoopTest.kt
index e31ccfc1..ba8f56ad 100644
--- a/kotlinx-coroutines-core/common/test/selects/SelectLoopTest.kt
+++ b/kotlinx-coroutines-core/common/test/selects/SelectLoopTest.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
@file:Suppress("NAMED_ARGUMENTS_NOT_ALLOWED") // KT-21913
@@ -27,7 +27,7 @@ class SelectLoopTest : TestBase() {
try {
while (true) {
select<Unit> {
- channel.onReceiveOrNull {
+ channel.onReceiveCatching {
expectUnreached()
}
job.onJoin {
@@ -40,4 +40,4 @@ class SelectLoopTest : TestBase() {
finish(4)
}
}
-} \ No newline at end of file
+}
diff --git a/kotlinx-coroutines-core/common/test/selects/SelectRendezvousChannelTest.kt b/kotlinx-coroutines-core/common/test/selects/SelectRendezvousChannelTest.kt
index 2027630f..6a157676 100644
--- a/kotlinx-coroutines-core/common/test/selects/SelectRendezvousChannelTest.kt
+++ b/kotlinx-coroutines-core/common/test/selects/SelectRendezvousChannelTest.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
@file:Suppress("NAMED_ARGUMENTS_NOT_ALLOWED") // KT-21913
@@ -306,7 +306,7 @@ class SelectRendezvousChannelTest : TestBase() {
}
@Test
- fun testSelectReceiveOrClosedWaitClosed() = runTest {
+ fun testSelectReceiveCatchingWaitClosed() = runTest {
expect(1)
val channel = Channel<String>(Channel.RENDEZVOUS)
launch {
@@ -316,10 +316,10 @@ class SelectRendezvousChannelTest : TestBase() {
}
expect(2)
select<Unit> {
- channel.onReceiveOrClosed {
+ channel.onReceiveCatching {
expect(5)
assertTrue(it.isClosed)
- assertNull(it.closeCause)
+ assertNull(it.exceptionOrNull())
}
}
@@ -327,7 +327,7 @@ class SelectRendezvousChannelTest : TestBase() {
}
@Test
- fun testSelectReceiveOrClosedWaitClosedWithCause() = runTest {
+ fun testSelectReceiveCatchingWaitClosedWithCause() = runTest {
expect(1)
val channel = Channel<String>(Channel.RENDEZVOUS)
launch {
@@ -337,10 +337,10 @@ class SelectRendezvousChannelTest : TestBase() {
}
expect(2)
select<Unit> {
- channel.onReceiveOrClosed {
+ channel.onReceiveCatching {
expect(5)
assertTrue(it.isClosed)
- assertTrue(it.closeCause is TestException)
+ assertTrue(it.exceptionOrNull() is TestException)
}
}
@@ -348,31 +348,31 @@ class SelectRendezvousChannelTest : TestBase() {
}
@Test
- fun testSelectReceiveOrClosedForClosedChannel() = runTest {
+ fun testSelectReceiveCatchingForClosedChannel() = runTest {
val channel = Channel<Unit>()
channel.close()
expect(1)
select<Unit> {
expect(2)
- channel.onReceiveOrClosed {
+ channel.onReceiveCatching {
assertTrue(it.isClosed)
- assertNull(it.closeCause)
+ assertNull(it.exceptionOrNull())
finish(3)
}
}
}
@Test
- fun testSelectReceiveOrClosed() = runTest {
+ fun testSelectReceiveCatching() = runTest {
val channel = Channel<Int>(Channel.RENDEZVOUS)
val iterations = 10
expect(1)
val job = launch {
repeat(iterations) {
select<Unit> {
- channel.onReceiveOrClosed { v ->
+ channel.onReceiveCatching { v ->
expect(4 + it * 2)
- assertEquals(it, v.value)
+ assertEquals(it, v.getOrThrow())
}
}
}
@@ -390,15 +390,15 @@ class SelectRendezvousChannelTest : TestBase() {
}
@Test
- fun testSelectReceiveOrClosedDispatch() = runTest {
+ fun testSelectReceiveCatchingDispatch() = runTest {
val c = Channel<Int>(Channel.RENDEZVOUS)
expect(1)
launch {
expect(3)
val res = select<String> {
- c.onReceiveOrClosed { v ->
+ c.onReceiveCatching { v ->
expect(6)
- assertEquals(42, v.value)
+ assertEquals(42, v.getOrThrow())
yield() // back to main
expect(8)
"OK"
diff --git a/kotlinx-coroutines-core/common/test/selects/SelectTimeoutDurationTest.kt b/kotlinx-coroutines-core/common/test/selects/SelectTimeoutDurationTest.kt
index 66cb72a5..26d6f809 100644
--- a/kotlinx-coroutines-core/common/test/selects/SelectTimeoutDurationTest.kt
+++ b/kotlinx-coroutines-core/common/test/selects/SelectTimeoutDurationTest.kt
@@ -14,15 +14,15 @@ class SelectTimeoutDurationTest : TestBase() {
fun testBasic() = runTest {
expect(1)
val result = select<String> {
- onTimeout(1000.milliseconds) {
+ onTimeout(Duration.milliseconds(1000)) {
expectUnreached()
"FAIL"
}
- onTimeout(100.milliseconds) {
+ onTimeout(Duration.milliseconds(100)) {
expect(2)
"OK"
}
- onTimeout(500.milliseconds) {
+ onTimeout(Duration.milliseconds(500)) {
expectUnreached()
"FAIL"
}
@@ -35,7 +35,7 @@ class SelectTimeoutDurationTest : TestBase() {
fun testZeroTimeout() = runTest {
expect(1)
val result = select<String> {
- onTimeout(1.seconds) {
+ onTimeout(Duration.seconds(1)) {
expectUnreached()
"FAIL"
}
@@ -52,11 +52,11 @@ class SelectTimeoutDurationTest : TestBase() {
fun testNegativeTimeout() = runTest {
expect(1)
val result = select<String> {
- onTimeout(1.seconds) {
+ onTimeout(Duration.seconds(1)) {
expectUnreached()
"FAIL"
}
- onTimeout(-10.milliseconds) {
+ onTimeout(-Duration.milliseconds(10)) {
expect(2)
"OK"
}
@@ -71,13 +71,13 @@ class SelectTimeoutDurationTest : TestBase() {
val iterations =10_000
for (i in 0..iterations) {
val result = selectUnbiased<Int> {
- onTimeout(-1.seconds) {
+ onTimeout(-Duration.seconds(1)) {
0
}
onTimeout(Duration.ZERO) {
1
}
- onTimeout(1.seconds) {
+ onTimeout(Duration.seconds(1)) {
expectUnreached()
2
}
diff --git a/kotlinx-coroutines-core/common/test/sync/MutexTest.kt b/kotlinx-coroutines-core/common/test/sync/MutexTest.kt
index c5d0ccf1..4f428bc4 100644
--- a/kotlinx-coroutines-core/common/test/sync/MutexTest.kt
+++ b/kotlinx-coroutines-core/common/test/sync/MutexTest.kt
@@ -4,6 +4,7 @@
package kotlinx.coroutines.sync
+import kotlinx.atomicfu.*
import kotlinx.coroutines.*
import kotlin.test.*
@@ -106,4 +107,4 @@ class MutexTest : TestBase() {
assertFalse(mutex.holdsLock(firstOwner))
assertFalse(mutex.holdsLock(secondOwner))
}
-} \ No newline at end of file
+}
diff --git a/kotlinx-coroutines-core/js/src/CompletionHandler.kt b/kotlinx-coroutines-core/js/src/CompletionHandler.kt
index e81e4351..2ff10702 100644
--- a/kotlinx-coroutines-core/js/src/CompletionHandler.kt
+++ b/kotlinx-coroutines-core/js/src/CompletionHandler.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines
diff --git a/kotlinx-coroutines-core/js/src/CoroutineContext.kt b/kotlinx-coroutines-core/js/src/CoroutineContext.kt
index c0b0c511..a98ea973 100644
--- a/kotlinx-coroutines-core/js/src/CoroutineContext.kt
+++ b/kotlinx-coroutines-core/js/src/CoroutineContext.kt
@@ -1,10 +1,11 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines
-import kotlin.browser.*
+import kotlinx.browser.*
+import kotlinx.coroutines.internal.*
import kotlin.coroutines.*
private external val navigator: dynamic
@@ -12,12 +13,6 @@ private const val UNDEFINED = "undefined"
internal external val process: dynamic
internal actual fun createDefaultDispatcher(): CoroutineDispatcher = when {
- // Check if we are running under ReactNative. We have to use NodeDispatcher under it.
- // The problem is that ReactNative has a `window` object with `addEventListener`, but it does not really work.
- // For details see https://github.com/Kotlin/kotlinx.coroutines/issues/236
- // The check for ReactNative is based on https://github.com/facebook/react-native/commit/3c65e62183ce05893be0822da217cb803b121c61
- jsTypeOf(navigator) != UNDEFINED && navigator != null && navigator.product == "ReactNative" ->
- NodeDispatcher
// Check if we are running under jsdom. WindowDispatcher doesn't work under jsdom because it accesses MessageEvent#source.
// It is not implemented in jsdom, see https://github.com/jsdom/jsdom/blob/master/Changelog.md
// "It's missing a few semantics, especially around origins, as well as MessageEvent source."
@@ -26,7 +21,7 @@ internal actual fun createDefaultDispatcher(): CoroutineDispatcher = when {
jsTypeOf(window) != UNDEFINED && window.asDynamic() != null && jsTypeOf(window.asDynamic().addEventListener) != UNDEFINED ->
window.asCoroutineDispatcher()
// If process is undefined (e.g. in NativeScript, #1404), use SetTimeout-based dispatcher
- jsTypeOf(process) == UNDEFINED -> SetTimeoutDispatcher
+ jsTypeOf(process) == UNDEFINED || jsTypeOf(process.nextTick) == UNDEFINED -> SetTimeoutDispatcher
// Fallback to NodeDispatcher when browser environment is not detected
else -> NodeDispatcher
}
@@ -49,5 +44,13 @@ public actual fun CoroutineScope.newCoroutineContext(context: CoroutineContext):
// No debugging facilities on JS
internal actual inline fun <T> withCoroutineContext(context: CoroutineContext, countOrElement: Any?, block: () -> T): T = block()
+internal actual inline fun <T> withContinuationContext(continuation: Continuation<*>, countOrElement: Any?, block: () -> T): T = block()
internal actual fun Continuation<*>.toDebugString(): String = toString()
internal actual val CoroutineContext.coroutineName: String? get() = null // not supported on JS
+
+internal actual class UndispatchedCoroutine<in T> actual constructor(
+ context: CoroutineContext,
+ uCont: Continuation<T>
+) : ScopeCoroutine<T>(context, uCont) {
+ override fun afterResume(state: Any?) = uCont.resumeWith(recoverResult(state, uCont))
+}
diff --git a/kotlinx-coroutines-core/js/src/CoroutineExceptionHandlerImpl.kt b/kotlinx-coroutines-core/js/src/CoroutineExceptionHandlerImpl.kt
index 524d4b55..54a65e10 100644
--- a/kotlinx-coroutines-core/js/src/CoroutineExceptionHandlerImpl.kt
+++ b/kotlinx-coroutines-core/js/src/CoroutineExceptionHandlerImpl.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines
diff --git a/kotlinx-coroutines-core/js/src/Debug.kt b/kotlinx-coroutines-core/js/src/Debug.kt
index 7cd9bedd..7655b543 100644
--- a/kotlinx-coroutines-core/js/src/Debug.kt
+++ b/kotlinx-coroutines-core/js/src/Debug.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines
diff --git a/kotlinx-coroutines-core/js/src/Dispatchers.kt b/kotlinx-coroutines-core/js/src/Dispatchers.kt
index 06b938d4..8d3bac32 100644
--- a/kotlinx-coroutines-core/js/src/Dispatchers.kt
+++ b/kotlinx-coroutines-core/js/src/Dispatchers.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines
diff --git a/kotlinx-coroutines-core/js/src/EventLoop.kt b/kotlinx-coroutines-core/js/src/EventLoop.kt
index 0039678a..b3a13641 100644
--- a/kotlinx-coroutines-core/js/src/EventLoop.kt
+++ b/kotlinx-coroutines-core/js/src/EventLoop.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines
diff --git a/kotlinx-coroutines-core/js/src/Exceptions.kt b/kotlinx-coroutines-core/js/src/Exceptions.kt
index c82199a4..da9979b6 100644
--- a/kotlinx-coroutines-core/js/src/Exceptions.kt
+++ b/kotlinx-coroutines-core/js/src/Exceptions.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines
@@ -10,12 +10,7 @@ package kotlinx.coroutines
* **It is not printed to console/log by default uncaught exception handler**.
* (see [CoroutineExceptionHandler]).
*/
-public actual open class CancellationException(
- message: String?,
- cause: Throwable?
-) : IllegalStateException(message, cause) {
- public actual constructor(message: String?) : this(message, null)
-}
+public actual typealias CancellationException = kotlin.coroutines.cancellation.CancellationException
/**
* Thrown by cancellable suspending functions if the [Job] of the coroutine is cancelled or completed
diff --git a/kotlinx-coroutines-core/js/src/JSDispatcher.kt b/kotlinx-coroutines-core/js/src/JSDispatcher.kt
index e1b3dcd7..6ad7d41b 100644
--- a/kotlinx-coroutines-core/js/src/JSDispatcher.kt
+++ b/kotlinx-coroutines-core/js/src/JSDispatcher.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines
diff --git a/kotlinx-coroutines-core/js/src/Promise.kt b/kotlinx-coroutines-core/js/src/Promise.kt
index ab200323..336a3883 100644
--- a/kotlinx-coroutines-core/js/src/Promise.kt
+++ b/kotlinx-coroutines-core/js/src/Promise.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines
diff --git a/kotlinx-coroutines-core/js/src/Runnable.kt b/kotlinx-coroutines-core/js/src/Runnable.kt
index 19710f97..b8e6980b 100644
--- a/kotlinx-coroutines-core/js/src/Runnable.kt
+++ b/kotlinx-coroutines-core/js/src/Runnable.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines
diff --git a/kotlinx-coroutines-core/js/src/SchedulerTask.kt b/kotlinx-coroutines-core/js/src/SchedulerTask.kt
index 29a92cfb..c0ecc4f2 100644
--- a/kotlinx-coroutines-core/js/src/SchedulerTask.kt
+++ b/kotlinx-coroutines-core/js/src/SchedulerTask.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines
diff --git a/kotlinx-coroutines-core/js/src/Window.kt b/kotlinx-coroutines-core/js/src/Window.kt
index 8284daef..dad0c04b 100644
--- a/kotlinx-coroutines-core/js/src/Window.kt
+++ b/kotlinx-coroutines-core/js/src/Window.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines
diff --git a/kotlinx-coroutines-core/js/src/flow/internal/FlowExceptions.kt b/kotlinx-coroutines-core/js/src/flow/internal/FlowExceptions.kt
index f818bfbb..000395ea 100644
--- a/kotlinx-coroutines-core/js/src/flow/internal/FlowExceptions.kt
+++ b/kotlinx-coroutines-core/js/src/flow/internal/FlowExceptions.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.flow.internal
diff --git a/kotlinx-coroutines-core/js/src/flow/internal/SafeCollector.kt b/kotlinx-coroutines-core/js/src/flow/internal/SafeCollector.kt
index ace633cc..b74b547d 100644
--- a/kotlinx-coroutines-core/js/src/flow/internal/SafeCollector.kt
+++ b/kotlinx-coroutines-core/js/src/flow/internal/SafeCollector.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.flow.internal
diff --git a/kotlinx-coroutines-core/js/src/internal/Concurrent.kt b/kotlinx-coroutines-core/js/src/internal/Concurrent.kt
index 5555137f..0a1b0310 100644
--- a/kotlinx-coroutines-core/js/src/internal/Concurrent.kt
+++ b/kotlinx-coroutines-core/js/src/internal/Concurrent.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.internal
diff --git a/kotlinx-coroutines-core/js/src/internal/CopyOnWriteList.kt b/kotlinx-coroutines-core/js/src/internal/CopyOnWriteList.kt
index 8f42160b..335e35d3 100644
--- a/kotlinx-coroutines-core/js/src/internal/CopyOnWriteList.kt
+++ b/kotlinx-coroutines-core/js/src/internal/CopyOnWriteList.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.internal
diff --git a/kotlinx-coroutines-core/js/src/internal/LinkedList.kt b/kotlinx-coroutines-core/js/src/internal/LinkedList.kt
index b6985057..147b31dc 100644
--- a/kotlinx-coroutines-core/js/src/internal/LinkedList.kt
+++ b/kotlinx-coroutines-core/js/src/internal/LinkedList.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
@file:Suppress("unused", "NO_EXPLICIT_RETURN_TYPE_IN_API_MODE", "NO_EXPLICIT_VISIBILITY_IN_API_MODE")
@@ -32,7 +32,18 @@ public open class LinkedListNode {
this._prev = node
}
+ /*
+ * Remove that is invoked as a virtual function with a
+ * potentially augmented behaviour.
+ * I.g. `LockFreeLinkedListHead` throws, while `SendElementWithUndeliveredHandler`
+ * invokes handler on remove
+ */
public open fun remove(): Boolean {
+ return removeImpl()
+ }
+
+ @PublishedApi
+ internal fun removeImpl(): Boolean {
if (_removed) return false
val prev = this._prev
val next = this._next
@@ -76,7 +87,7 @@ public open class LinkedListNode {
public fun removeFirstOrNull(): Node? {
val next = _next
if (next === this) return null
- check(next.remove()) { "Should remove" }
+ check(next.removeImpl()) { "Should remove" }
return next
}
@@ -85,7 +96,7 @@ public open class LinkedListNode {
if (next === this) return null
if (next !is T) return null
if (predicate(next)) return next
- check(next.remove()) { "Should remove" }
+ check(next.removeImpl()) { "Should remove" }
return next
}
}
diff --git a/kotlinx-coroutines-core/js/src/internal/LocalAtomics.kt b/kotlinx-coroutines-core/js/src/internal/LocalAtomics.kt
index fffd76c4..643fe85d 100644
--- a/kotlinx-coroutines-core/js/src/internal/LocalAtomics.kt
+++ b/kotlinx-coroutines-core/js/src/internal/LocalAtomics.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.internal
diff --git a/kotlinx-coroutines-core/js/src/internal/ProbesSupport.kt b/kotlinx-coroutines-core/js/src/internal/ProbesSupport.kt
index a13a141f..47d3fdba 100644
--- a/kotlinx-coroutines-core/js/src/internal/ProbesSupport.kt
+++ b/kotlinx-coroutines-core/js/src/internal/ProbesSupport.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.internal
diff --git a/kotlinx-coroutines-core/js/src/internal/StackTraceRecovery.kt b/kotlinx-coroutines-core/js/src/internal/StackTraceRecovery.kt
index 234bbcad..06107b8f 100644
--- a/kotlinx-coroutines-core/js/src/internal/StackTraceRecovery.kt
+++ b/kotlinx-coroutines-core/js/src/internal/StackTraceRecovery.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.internal
diff --git a/kotlinx-coroutines-core/js/src/internal/Synchronized.kt b/kotlinx-coroutines-core/js/src/internal/Synchronized.kt
index 0911dbe1..dcbb2021 100644
--- a/kotlinx-coroutines-core/js/src/internal/Synchronized.kt
+++ b/kotlinx-coroutines-core/js/src/internal/Synchronized.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.internal
diff --git a/kotlinx-coroutines-core/js/src/internal/SystemProps.kt b/kotlinx-coroutines-core/js/src/internal/SystemProps.kt
index 564630f6..4fb334ed 100644
--- a/kotlinx-coroutines-core/js/src/internal/SystemProps.kt
+++ b/kotlinx-coroutines-core/js/src/internal/SystemProps.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.internal
diff --git a/kotlinx-coroutines-core/js/src/internal/ThreadContext.kt b/kotlinx-coroutines-core/js/src/internal/ThreadContext.kt
index 4a9513ab..2370e42f 100644
--- a/kotlinx-coroutines-core/js/src/internal/ThreadContext.kt
+++ b/kotlinx-coroutines-core/js/src/internal/ThreadContext.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.internal
diff --git a/kotlinx-coroutines-core/js/src/internal/ThreadLocal.kt b/kotlinx-coroutines-core/js/src/internal/ThreadLocal.kt
index 09f501a4..e1825d67 100644
--- a/kotlinx-coroutines-core/js/src/internal/ThreadLocal.kt
+++ b/kotlinx-coroutines-core/js/src/internal/ThreadLocal.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.internal
diff --git a/kotlinx-coroutines-core/js/test/PromiseTest.kt b/kotlinx-coroutines-core/js/test/PromiseTest.kt
index d0f6b2b7..cc1297cd 100644
--- a/kotlinx-coroutines-core/js/test/PromiseTest.kt
+++ b/kotlinx-coroutines-core/js/test/PromiseTest.kt
@@ -74,4 +74,16 @@ class PromiseTest : TestBase() {
assertSame(d2, deferred)
assertEquals("OK", d2.await())
}
-} \ No newline at end of file
+
+ @Test
+ fun testLeverageTestResult(): TestResult {
+ // Cannot use expect(..) here
+ var seq = 0
+ val result = runTest {
+ ++seq
+ }
+ return result.then {
+ if (seq != 1) error("Unexpected result: $seq")
+ }
+ }
+}
diff --git a/kotlinx-coroutines-core/js/test/TestBase.kt b/kotlinx-coroutines-core/js/test/TestBase.kt
index 8b3d69a7..cc7865ba 100644
--- a/kotlinx-coroutines-core/js/test/TestBase.kt
+++ b/kotlinx-coroutines-core/js/test/TestBase.kt
@@ -9,10 +9,15 @@ import kotlin.js.*
public actual val isStressTest: Boolean = false
public actual val stressTestMultiplier: Int = 1
+@Suppress("ACTUAL_WITHOUT_EXPECT", "ACTUAL_TYPE_ALIAS_TO_CLASS_WITH_DECLARATION_SITE_VARIANCE")
+public actual typealias TestResult = Promise<Unit>
+
public actual open class TestBase actual constructor() {
+ public actual val isBoundByJsTestTimeout = true
private var actionIndex = 0
private var finished = false
private var error: Throwable? = null
+ private var lastTestPromise: Promise<*>? = null
/**
* Throws [IllegalStateException] like `error` in stdlib, but also ensures that the test will not
@@ -70,16 +75,37 @@ public actual open class TestBase actual constructor() {
finished = false
}
- // todo: The dynamic (promise) result is a work-around for missing suspend tests, see KT-22228
@Suppress("ACTUAL_FUNCTION_WITH_DEFAULT_ARGUMENTS")
public actual fun runTest(
expected: ((Throwable) -> Boolean)? = null,
unhandled: List<(Throwable) -> Boolean> = emptyList(),
block: suspend CoroutineScope.() -> Unit
- ): dynamic {
+ ): TestResult {
var exCount = 0
var ex: Throwable? = null
- return GlobalScope.promise(block = block, context = CoroutineExceptionHandler { context, e ->
+ /*
+ * This is an additional sanity check against `runTest` mis-usage on JS.
+ * The only way to write an async test on JS is to return Promise from the test function.
+ * _Just_ launching promise and returning `Unit` won't suffice as the underlying test framework
+ * won't be able to detect an asynchronous failure in a timely manner.
+ * We cannot detect such situations, but we can detect the most common erroneous pattern
+ * in our code base, an attempt to use multiple `runTest` in the same `@Test` method,
+ * which typically is a premise to the same error:
+ * ```
+ * @Test
+ * fun incorrectTestForJs() { // <- promise is not returned
+ * for (parameter in parameters) {
+ * runTest {
+ * runTestForParameter(parameter)
+ * }
+ * }
+ * }
+ * ```
+ */
+ if (lastTestPromise != null) {
+ error("Attempt to run multiple asynchronous test within one @Test method")
+ }
+ val result = GlobalScope.promise(block = block, context = CoroutineExceptionHandler { _, e ->
if (e is CancellationException) return@CoroutineExceptionHandler // are ignored
exCount++
when {
@@ -102,6 +128,8 @@ public actual open class TestBase actual constructor() {
error?.let { throw it }
check(actionIndex == 0 || finished) { "Expecting that 'finish(...)' was invoked, but it was not" }
}
+ lastTestPromise = result
+ return result
}
}
diff --git a/kotlinx-coroutines-core/jvm/resources/DebugProbesKt.bin b/kotlinx-coroutines-core/jvm/resources/DebugProbesKt.bin
index 76ee4115..397aaf67 100644
--- a/kotlinx-coroutines-core/jvm/resources/DebugProbesKt.bin
+++ b/kotlinx-coroutines-core/jvm/resources/DebugProbesKt.bin
Binary files differ
diff --git a/kotlinx-coroutines-core/jvm/resources/META-INF/proguard/coroutines.pro b/kotlinx-coroutines-core/jvm/resources/META-INF/proguard/coroutines.pro
index 60c8d612..1a9ae1c7 100644
--- a/kotlinx-coroutines-core/jvm/resources/META-INF/proguard/coroutines.pro
+++ b/kotlinx-coroutines-core/jvm/resources/META-INF/proguard/coroutines.pro
@@ -3,12 +3,12 @@
-keepnames class kotlinx.coroutines.CoroutineExceptionHandler {}
# Most of volatile fields are updated with AFU and should not be mangled
--keepclassmembernames class kotlinx.** {
+-keepclassmembers class kotlinx.coroutines.** {
volatile <fields>;
}
# Same story for the standard library's SafeContinuation that also uses AtomicReferenceFieldUpdater
--keepclassmembernames class kotlin.coroutines.SafeContinuation {
+-keepclassmembers class kotlin.coroutines.SafeContinuation {
volatile <fields>;
}
diff --git a/kotlinx-coroutines-core/jvm/src/TimeSource.kt b/kotlinx-coroutines-core/jvm/src/AbstractTimeSource.kt
index 4b6fd991..3f7ac675 100644
--- a/kotlinx-coroutines-core/jvm/src/TimeSource.kt
+++ b/kotlinx-coroutines-core/jvm/src/AbstractTimeSource.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
// Need InlineOnly for efficient bytecode on Android
@@ -10,21 +10,21 @@ package kotlinx.coroutines
import java.util.concurrent.locks.*
import kotlin.internal.InlineOnly
-internal interface TimeSource {
- fun currentTimeMillis(): Long
- fun nanoTime(): Long
- fun wrapTask(block: Runnable): Runnable
- fun trackTask()
- fun unTrackTask()
- fun registerTimeLoopThread()
- fun unregisterTimeLoopThread()
- fun parkNanos(blocker: Any, nanos: Long) // should return immediately when nanos <= 0
- fun unpark(thread: Thread)
+internal abstract class AbstractTimeSource {
+ abstract fun currentTimeMillis(): Long
+ abstract fun nanoTime(): Long
+ abstract fun wrapTask(block: Runnable): Runnable
+ abstract fun trackTask()
+ abstract fun unTrackTask()
+ abstract fun registerTimeLoopThread()
+ abstract fun unregisterTimeLoopThread()
+ abstract fun parkNanos(blocker: Any, nanos: Long) // should return immediately when nanos <= 0
+ abstract fun unpark(thread: Thread)
}
// For tests only
// @JvmField: Don't use JvmField here to enable R8 optimizations via "assumenosideeffects"
-internal var timeSource: TimeSource? = null
+internal var timeSource: AbstractTimeSource? = null
@InlineOnly
internal inline fun currentTimeMillis(): Long =
diff --git a/kotlinx-coroutines-core/jvm/src/Builders.kt b/kotlinx-coroutines-core/jvm/src/Builders.kt
index e4504ccd..edb43031 100644
--- a/kotlinx-coroutines-core/jvm/src/Builders.kt
+++ b/kotlinx-coroutines-core/jvm/src/Builders.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
@file:JvmMultifileClass
@@ -63,13 +63,14 @@ private class BlockingCoroutine<T>(
parentContext: CoroutineContext,
private val blockedThread: Thread,
private val eventLoop: EventLoop?
-) : AbstractCoroutine<T>(parentContext, true) {
+) : AbstractCoroutine<T>(parentContext, true, true) {
+
override val isScopedCoroutine: Boolean get() = true
override fun afterCompletion(state: Any?) {
// wake up blocked thread
if (Thread.currentThread() != blockedThread)
- LockSupport.unpark(blockedThread)
+ unpark(blockedThread)
}
@Suppress("UNCHECKED_CAST")
diff --git a/kotlinx-coroutines-core/jvm/src/CommonPool.kt b/kotlinx-coroutines-core/jvm/src/CommonPool.kt
index 22033131..502630b0 100644
--- a/kotlinx-coroutines-core/jvm/src/CommonPool.kt
+++ b/kotlinx-coroutines-core/jvm/src/CommonPool.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines
@@ -28,7 +28,7 @@ internal object CommonPool : ExecutorCoroutineDispatcher() {
* Note that until Java 10, if an application is run within a container,
* `Runtime.getRuntime().availableProcessors()` is not aware of container constraints and will return the real number of cores.
*/
- public const val DEFAULT_PARALLELISM_PROPERTY_NAME = "kotlinx.coroutines.default.parallelism"
+ private const val DEFAULT_PARALLELISM_PROPERTY_NAME = "kotlinx.coroutines.default.parallelism"
override val executor: Executor
get() = pool ?: getOrCreatePoolSync()
@@ -62,7 +62,7 @@ internal object CommonPool : ExecutorCoroutineDispatcher() {
?: return createPlainPool() // Fallback to plain thread pool
// Try to use commonPool unless parallelism was explicitly specified or in debug privatePool mode
if (!usePrivatePool && requestedParallelism < 0) {
- Try { fjpClass.getMethod("commonPool")?.invoke(null) as? ExecutorService }
+ Try { fjpClass.getMethod("commonPool").invoke(null) as? ExecutorService }
?.takeIf { isGoodCommonPool(fjpClass, it) }
?.let { return it }
}
diff --git a/kotlinx-coroutines-core/jvm/src/CompletionHandler.kt b/kotlinx-coroutines-core/jvm/src/CompletionHandler.kt
index 706f6c49..4835f796 100644
--- a/kotlinx-coroutines-core/jvm/src/CompletionHandler.kt
+++ b/kotlinx-coroutines-core/jvm/src/CompletionHandler.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines
diff --git a/kotlinx-coroutines-core/jvm/src/CoroutineContext.kt b/kotlinx-coroutines-core/jvm/src/CoroutineContext.kt
index 5a69d48a..e91bb9fd 100644
--- a/kotlinx-coroutines-core/jvm/src/CoroutineContext.kt
+++ b/kotlinx-coroutines-core/jvm/src/CoroutineContext.kt
@@ -1,13 +1,13 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines
import kotlinx.coroutines.internal.*
import kotlinx.coroutines.scheduling.*
-import java.util.concurrent.atomic.*
import kotlin.coroutines.*
+import kotlin.coroutines.jvm.internal.CoroutineStackFrame
internal const val COROUTINES_SCHEDULER_PROPERTY_NAME = "kotlinx.coroutines.scheduler"
@@ -48,6 +48,102 @@ internal actual inline fun <T> withCoroutineContext(context: CoroutineContext, c
}
}
+/**
+ * Executes a block using a context of a given continuation.
+ */
+internal actual inline fun <T> withContinuationContext(continuation: Continuation<*>, countOrElement: Any?, block: () -> T): T {
+ val context = continuation.context
+ val oldValue = updateThreadContext(context, countOrElement)
+ val undispatchedCompletion = if (oldValue !== NO_THREAD_ELEMENTS) {
+ // Only if some values were replaced we'll go to the slow path of figuring out where/how to restore them
+ continuation.updateUndispatchedCompletion(context, oldValue)
+ } else {
+ null // fast path -- don't even try to find undispatchedCompletion as there's nothing to restore in the context
+ }
+ try {
+ return block()
+ } finally {
+ if (undispatchedCompletion == null || undispatchedCompletion.clearThreadContext()) {
+ restoreThreadContext(context, oldValue)
+ }
+ }
+}
+
+internal fun Continuation<*>.updateUndispatchedCompletion(context: CoroutineContext, oldValue: Any?): UndispatchedCoroutine<*>? {
+ if (this !is CoroutineStackFrame) return null
+ /*
+ * Fast-path to detect whether we have unispatched coroutine at all in our stack.
+ *
+ * Implementation note.
+ * If we ever find that stackwalking for thread-locals is way too slow, here is another idea:
+ * 1) Store undispatched coroutine right in the `UndispatchedMarker` instance
+ * 2) To avoid issues with cross-dispatch boundary, remove `UndispatchedMarker`
+ * from the context when creating dispatched coroutine in `withContext`.
+ * Another option is to "unmark it" instead of removing to save an allocation.
+ * Both options should work, but it requires more careful studying of the performance
+ * and, mostly, maintainability impact.
+ */
+ val potentiallyHasUndispatchedCorotuine = context[UndispatchedMarker] !== null
+ if (!potentiallyHasUndispatchedCorotuine) return null
+ val completion = undispatchedCompletion()
+ completion?.saveThreadContext(context, oldValue)
+ return completion
+}
+
+internal tailrec fun CoroutineStackFrame.undispatchedCompletion(): UndispatchedCoroutine<*>? {
+ // Find direct completion of this continuation
+ val completion: CoroutineStackFrame = when (this) {
+ is DispatchedCoroutine<*> -> return null
+ else -> callerFrame ?: return null // something else -- not supported
+ }
+ if (completion is UndispatchedCoroutine<*>) return completion // found UndispatchedCoroutine!
+ return completion.undispatchedCompletion() // walk up the call stack with tail call
+}
+
+/**
+ * Marker indicating that [UndispatchedCoroutine] exists somewhere up in the stack.
+ * Used as a performance optimization to avoid stack walking where it is not nesessary.
+ */
+private object UndispatchedMarker: CoroutineContext.Element, CoroutineContext.Key<UndispatchedMarker> {
+ override val key: CoroutineContext.Key<*>
+ get() = this
+}
+
+// Used by withContext when context changes, but dispatcher stays the same
+internal actual class UndispatchedCoroutine<in T>actual constructor (
+ context: CoroutineContext,
+ uCont: Continuation<T>
+) : ScopeCoroutine<T>(if (context[UndispatchedMarker] == null) context + UndispatchedMarker else context, uCont) {
+
+ private var savedContext: CoroutineContext? = null
+ private var savedOldValue: Any? = null
+
+ fun saveThreadContext(context: CoroutineContext, oldValue: Any?) {
+ savedContext = context
+ savedOldValue = oldValue
+ }
+
+ fun clearThreadContext(): Boolean {
+ if (savedContext == null) return false
+ savedContext = null
+ savedOldValue = null
+ return true
+ }
+
+ override fun afterResume(state: Any?) {
+ savedContext?.let { context ->
+ restoreThreadContext(context, savedOldValue)
+ savedContext = null
+ savedOldValue = null
+ }
+ // resume undispatched -- update context but stay on the same dispatcher
+ val result = recoverResult(state, uCont)
+ withContinuationContext(uCont, null) {
+ uCont.resumeWith(result)
+ }
+ }
+}
+
internal actual val CoroutineContext.coroutineName: String? get() {
if (!DEBUG) return null
val coroutineId = this[CoroutineId] ?: return null
diff --git a/kotlinx-coroutines-core/jvm/src/CoroutineExceptionHandlerImpl.kt b/kotlinx-coroutines-core/jvm/src/CoroutineExceptionHandlerImpl.kt
index af37e73c..6d069692 100644
--- a/kotlinx-coroutines-core/jvm/src/CoroutineExceptionHandlerImpl.kt
+++ b/kotlinx-coroutines-core/jvm/src/CoroutineExceptionHandlerImpl.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines
diff --git a/kotlinx-coroutines-core/jvm/src/Debug.kt b/kotlinx-coroutines-core/jvm/src/Debug.kt
index 8108d235..911914a0 100644
--- a/kotlinx-coroutines-core/jvm/src/Debug.kt
+++ b/kotlinx-coroutines-core/jvm/src/Debug.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
// Need InlineOnly for efficient bytecode on Android
diff --git a/kotlinx-coroutines-core/jvm/src/DebugStrings.kt b/kotlinx-coroutines-core/jvm/src/DebugStrings.kt
index 2ccfebc6..32bd07a7 100644
--- a/kotlinx-coroutines-core/jvm/src/DebugStrings.kt
+++ b/kotlinx-coroutines-core/jvm/src/DebugStrings.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines
diff --git a/kotlinx-coroutines-core/jvm/src/DefaultExecutor.kt b/kotlinx-coroutines-core/jvm/src/DefaultExecutor.kt
index 787cbf9c..fe020276 100644
--- a/kotlinx-coroutines-core/jvm/src/DefaultExecutor.kt
+++ b/kotlinx-coroutines-core/jvm/src/DefaultExecutor.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines
diff --git a/kotlinx-coroutines-core/jvm/src/Dispatchers.kt b/kotlinx-coroutines-core/jvm/src/Dispatchers.kt
index 8033fb38..d82598ea 100644
--- a/kotlinx-coroutines-core/jvm/src/Dispatchers.kt
+++ b/kotlinx-coroutines-core/jvm/src/Dispatchers.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
@file:Suppress("unused")
@@ -8,7 +8,6 @@ package kotlinx.coroutines
import kotlinx.coroutines.internal.*
import kotlinx.coroutines.scheduling.*
-import java.util.*
import kotlin.coroutines.*
/**
@@ -108,9 +107,10 @@ public actual object Dispatchers {
*
* ### Implementation note
*
- * This dispatcher shares threads with a [Default][Dispatchers.Default] dispatcher, so using
- * `withContext(Dispatchers.IO) { ... }` does not lead to an actual switching to another thread &mdash;
- * typically execution continues in the same thread.
+ * This dispatcher shares threads with the [Default][Dispatchers.Default] dispatcher, so using
+ * `withContext(Dispatchers.IO) { ... }` when already running on the [Default][Dispatchers.Default]
+ * dispatcher does not lead to an actual switching to another thread &mdash; typically execution
+ * continues in the same thread.
* As a result of thread sharing, more than 64 (default parallelism) threads can be created (but not used)
* during operations over IO dispatcher.
*/
diff --git a/kotlinx-coroutines-core/jvm/src/EventLoop.kt b/kotlinx-coroutines-core/jvm/src/EventLoop.kt
index d86f632a..e49c7dc7 100644
--- a/kotlinx-coroutines-core/jvm/src/EventLoop.kt
+++ b/kotlinx-coroutines-core/jvm/src/EventLoop.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines
diff --git a/kotlinx-coroutines-core/jvm/src/Exceptions.kt b/kotlinx-coroutines-core/jvm/src/Exceptions.kt
index 0684ce23..007a0c98 100644
--- a/kotlinx-coroutines-core/jvm/src/Exceptions.kt
+++ b/kotlinx-coroutines-core/jvm/src/Exceptions.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
@file:Suppress("FunctionName")
diff --git a/kotlinx-coroutines-core/jvm/src/Executors.kt b/kotlinx-coroutines-core/jvm/src/Executors.kt
index 8ffc22d8..7ea3cc68 100644
--- a/kotlinx-coroutines-core/jvm/src/Executors.kt
+++ b/kotlinx-coroutines-core/jvm/src/Executors.kt
@@ -1,9 +1,10 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines
+import kotlinx.coroutines.flow.*
import kotlinx.coroutines.internal.*
import java.io.*
import java.util.concurrent.*
@@ -39,6 +40,22 @@ public abstract class ExecutorCoroutineDispatcher: CoroutineDispatcher(), Closea
/**
* Converts an instance of [ExecutorService] to an implementation of [ExecutorCoroutineDispatcher].
*
+ * ## Interaction with [delay] and time-based coroutines.
+ *
+ * If the given [ExecutorService] is an instance of [ScheduledExecutorService], then all time-related
+ * coroutine operations such as [delay], [withTimeout] and time-based [Flow] operators will be scheduled
+ * on this executor using [schedule][ScheduledExecutorService.schedule] method. If the corresponding
+ * coroutine is cancelled, [ScheduledFuture.cancel] will be invoked on the corresponding future.
+ *
+ * If the given [ExecutorService] is an instance of [ScheduledThreadPoolExecutor], then prior to any scheduling,
+ * remove on cancel policy will be set via [ScheduledThreadPoolExecutor.setRemoveOnCancelPolicy] in order
+ * to reduce the memory pressure of cancelled coroutines.
+ *
+ * If the executor service is neither of this types, the separate internal thread will be used to
+ * _track_ the delay and time-related executions, but the coroutine itself will still be executed
+ * on top of the given executor.
+ *
+ * ## Rejected execution
* If the underlying executor throws [RejectedExecutionException] on
* attempt to submit a continuation task (it happens when [closing][ExecutorCoroutineDispatcher.close] the
* resulting dispatcher, on underlying executor [shutdown][ExecutorService.shutdown], or when it uses limited queues),
@@ -52,6 +69,23 @@ public fun ExecutorService.asCoroutineDispatcher(): ExecutorCoroutineDispatcher
/**
* Converts an instance of [Executor] to an implementation of [CoroutineDispatcher].
*
+ * ## Interaction with [delay] and time-based coroutines.
+ *
+ * If the given [Executor] is an instance of [ScheduledExecutorService], then all time-related
+ * coroutine operations such as [delay], [withTimeout] and time-based [Flow] operators will be scheduled
+ * on this executor using [schedule][ScheduledExecutorService.schedule] method. If the corresponding
+ * coroutine is cancelled, [ScheduledFuture.cancel] will be invoked on the corresponding future.
+ *
+ * If the given [Executor] is an instance of [ScheduledThreadPoolExecutor], then prior to any scheduling,
+ * remove on cancel policy will be set via [ScheduledThreadPoolExecutor.setRemoveOnCancelPolicy] in order
+ * to reduce the memory pressure of cancelled coroutines.
+ *
+ * If the executor is neither of this types, the separate internal thread will be used to
+ * _track_ the delay and time-related executions, but the coroutine itself will still be executed
+ * on top of the given executor.
+ *
+ * ## Rejected execution
+ *
* If the underlying executor throws [RejectedExecutionException] on
* attempt to submit a continuation task (it happens when [closing][ExecutorCoroutineDispatcher.close] the
* resulting dispatcher, on underlying executor [shutdown][ExecutorService.shutdown], or when it uses limited queues),
@@ -75,18 +109,15 @@ private class DispatcherExecutor(@JvmField val dispatcher: CoroutineDispatcher)
override fun toString(): String = dispatcher.toString()
}
-private class ExecutorCoroutineDispatcherImpl(override val executor: Executor) : ExecutorCoroutineDispatcherBase() {
- init {
- initFutureCancellation()
- }
-}
+internal class ExecutorCoroutineDispatcherImpl(override val executor: Executor) : ExecutorCoroutineDispatcher(), Delay {
-internal abstract class ExecutorCoroutineDispatcherBase : ExecutorCoroutineDispatcher(), Delay {
-
- private var removesFutureOnCancellation: Boolean = false
-
- internal fun initFutureCancellation() {
- removesFutureOnCancellation = removeFutureOnCancel(executor)
+ /*
+ * Attempts to reflectively (to be Java 6 compatible) invoke
+ * ScheduledThreadPoolExecutor.setRemoveOnCancelPolicy in order to cleanup
+ * internal scheduler queue on cancellation.
+ */
+ init {
+ removeFutureOnCancel(executor)
}
override fun dispatch(context: CoroutineContext, block: Runnable) {
@@ -99,17 +130,12 @@ internal abstract class ExecutorCoroutineDispatcherBase : ExecutorCoroutineDispa
}
}
- /*
- * removesFutureOnCancellation is required to avoid memory leak.
- * On Java 7+ we reflectively invoke ScheduledThreadPoolExecutor.setRemoveOnCancelPolicy(true) and we're fine.
- * On Java 6 we're scheduling time-based coroutines to our own thread safe heap which supports cancellation.
- */
override fun scheduleResumeAfterDelay(timeMillis: Long, continuation: CancellableContinuation<Unit>) {
- val future = if (removesFutureOnCancellation) {
- scheduleBlock(ResumeUndispatchedRunnable(this, continuation), continuation.context, timeMillis)
- } else {
- null
- }
+ val future = (executor as? ScheduledExecutorService)?.scheduleBlock(
+ ResumeUndispatchedRunnable(this, continuation),
+ continuation.context,
+ timeMillis
+ )
// If everything went fine and the scheduling attempt was not rejected -- use it
if (future != null) {
continuation.cancelFutureOnCancellation(future)
@@ -120,20 +146,16 @@ internal abstract class ExecutorCoroutineDispatcherBase : ExecutorCoroutineDispa
}
override fun invokeOnTimeout(timeMillis: Long, block: Runnable, context: CoroutineContext): DisposableHandle {
- val future = if (removesFutureOnCancellation) {
- scheduleBlock(block, context, timeMillis)
- } else {
- null
- }
+ val future = (executor as? ScheduledExecutorService)?.scheduleBlock(block, context, timeMillis)
return when {
future != null -> DisposableFutureHandle(future)
else -> DefaultExecutor.invokeOnTimeout(timeMillis, block, context)
}
}
- private fun scheduleBlock(block: Runnable, context: CoroutineContext, timeMillis: Long): ScheduledFuture<*>? {
+ private fun ScheduledExecutorService.scheduleBlock(block: Runnable, context: CoroutineContext, timeMillis: Long): ScheduledFuture<*>? {
return try {
- (executor as? ScheduledExecutorService)?.schedule(block, timeMillis, TimeUnit.MILLISECONDS)
+ schedule(block, timeMillis, TimeUnit.MILLISECONDS)
} catch (e: RejectedExecutionException) {
cancelJobOnRejection(context, e)
null
@@ -149,7 +171,7 @@ internal abstract class ExecutorCoroutineDispatcherBase : ExecutorCoroutineDispa
}
override fun toString(): String = executor.toString()
- override fun equals(other: Any?): Boolean = other is ExecutorCoroutineDispatcherBase && other.executor === executor
+ override fun equals(other: Any?): Boolean = other is ExecutorCoroutineDispatcherImpl && other.executor === executor
override fun hashCode(): Int = System.identityHashCode(executor)
}
diff --git a/kotlinx-coroutines-core/jvm/src/Future.kt b/kotlinx-coroutines-core/jvm/src/Future.kt
index bd16f49a..b27a9708 100644
--- a/kotlinx-coroutines-core/jvm/src/Future.kt
+++ b/kotlinx-coroutines-core/jvm/src/Future.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
@file:JvmMultifileClass
@@ -13,42 +13,40 @@ import java.util.concurrent.*
* Cancels a specified [future] when this job is cancelled.
* This is a shortcut for the following code with slightly more efficient implementation (one fewer object created).
* ```
- * invokeOnCompletion { future.cancel(false) }
+ * invokeOnCompletion { if (it != null) future.cancel(false) }
* ```
*
* @suppress **This an internal API and should not be used from general code.**
*/
@InternalCoroutinesApi
public fun Job.cancelFutureOnCompletion(future: Future<*>): DisposableHandle =
- invokeOnCompletion(handler = CancelFutureOnCompletion(this, future)) // TODO make it work only on cancellation as well?
+ invokeOnCompletion(handler = CancelFutureOnCompletion(future))
/**
* Cancels a specified [future] when this job is cancelled.
* This is a shortcut for the following code with slightly more efficient implementation (one fewer object created).
* ```
- * invokeOnCancellation { future.cancel(false) }
+ * invokeOnCancellation { if (it != null) future.cancel(false) }
* ```
*/
public fun CancellableContinuation<*>.cancelFutureOnCancellation(future: Future<*>): Unit =
invokeOnCancellation(handler = CancelFutureOnCancel(future))
private class CancelFutureOnCompletion(
- job: Job,
private val future: Future<*>
-) : JobNode<Job>(job) {
+) : JobNode() {
override fun invoke(cause: Throwable?) {
// Don't interrupt when cancelling future on completion, because no one is going to reset this
// interruption flag and it will cause spurious failures elsewhere
- future.cancel(false)
+ if (cause != null) future.cancel(false)
}
- override fun toString() = "CancelFutureOnCompletion[$future]"
}
private class CancelFutureOnCancel(private val future: Future<*>) : CancelHandler() {
override fun invoke(cause: Throwable?) {
// Don't interrupt when cancelling future on completion, because no one is going to reset this
// interruption flag and it will cause spurious failures elsewhere
- future.cancel(false)
+ if (cause != null) future.cancel(false)
}
override fun toString() = "CancelFutureOnCancel[$future]"
}
diff --git a/kotlinx-coroutines-core/jvm/src/Interruptible.kt b/kotlinx-coroutines-core/jvm/src/Interruptible.kt
index 070aa624..b873eadf 100644
--- a/kotlinx-coroutines-core/jvm/src/Interruptible.kt
+++ b/kotlinx-coroutines-core/jvm/src/Interruptible.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines
diff --git a/kotlinx-coroutines-core/jvm/src/Runnable.kt b/kotlinx-coroutines-core/jvm/src/Runnable.kt
index 14d01105..844f9fca 100644
--- a/kotlinx-coroutines-core/jvm/src/Runnable.kt
+++ b/kotlinx-coroutines-core/jvm/src/Runnable.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines
diff --git a/kotlinx-coroutines-core/jvm/src/SchedulerTask.kt b/kotlinx-coroutines-core/jvm/src/SchedulerTask.kt
index 478df822..6a00f45f 100644
--- a/kotlinx-coroutines-core/jvm/src/SchedulerTask.kt
+++ b/kotlinx-coroutines-core/jvm/src/SchedulerTask.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines
diff --git a/kotlinx-coroutines-core/jvm/src/ThreadContextElement.kt b/kotlinx-coroutines-core/jvm/src/ThreadContextElement.kt
index 1fd85110..37fd70a2 100644
--- a/kotlinx-coroutines-core/jvm/src/ThreadContextElement.kt
+++ b/kotlinx-coroutines-core/jvm/src/ThreadContextElement.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines
diff --git a/kotlinx-coroutines-core/jvm/src/ThreadPoolDispatcher.kt b/kotlinx-coroutines-core/jvm/src/ThreadPoolDispatcher.kt
index aa18cd38..99e3b46c 100644
--- a/kotlinx-coroutines-core/jvm/src/ThreadPoolDispatcher.kt
+++ b/kotlinx-coroutines-core/jvm/src/ThreadPoolDispatcher.kt
@@ -1,13 +1,11 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines
-import kotlinx.coroutines.internal.*
import java.util.concurrent.*
import java.util.concurrent.atomic.AtomicInteger
-import kotlin.coroutines.*
/**
* Creates a coroutine execution context using a single thread with built-in [yield] support.
@@ -61,40 +59,11 @@ public fun newSingleThreadContext(name: String): ExecutorCoroutineDispatcher =
@ObsoleteCoroutinesApi
public fun newFixedThreadPoolContext(nThreads: Int, name: String): ExecutorCoroutineDispatcher {
require(nThreads >= 1) { "Expected at least one thread, but $nThreads specified" }
- return ThreadPoolDispatcher(nThreads, name)
-}
-
-internal class PoolThread(
- @JvmField val dispatcher: ThreadPoolDispatcher, // for debugging & tests
- target: Runnable, name: String
-) : Thread(target, name) {
- init { isDaemon = true }
-}
-
-/**
- * Dispatches coroutine execution to a thread pool of a fixed size. Instances of this dispatcher are
- * created with [newSingleThreadContext] and [newFixedThreadPoolContext].
- */
-internal class ThreadPoolDispatcher internal constructor(
- private val nThreads: Int,
- private val name: String
-) : ExecutorCoroutineDispatcherBase() {
- private val threadNo = AtomicInteger()
-
- override val executor: Executor = Executors.newScheduledThreadPool(nThreads) { target ->
- PoolThread(this, target, if (nThreads == 1) name else name + "-" + threadNo.incrementAndGet())
- }
-
- init {
- initFutureCancellation()
+ val threadNo = AtomicInteger()
+ val executor = Executors.newScheduledThreadPool(nThreads) { runnable ->
+ val t = Thread(runnable, if (nThreads == 1) name else name + "-" + threadNo.incrementAndGet())
+ t.isDaemon = true
+ t
}
-
- /**
- * Closes this dispatcher -- shuts down all threads in this pool and releases resources.
- */
- public override fun close() {
- (executor as ExecutorService).shutdown()
- }
-
- override fun toString(): String = "ThreadPoolDispatcher[$nThreads, $name]"
+ return executor.asCoroutineDispatcher()
}
diff --git a/kotlinx-coroutines-core/jvm/src/channels/Actor.kt b/kotlinx-coroutines-core/jvm/src/channels/Actor.kt
index a9054265..4657bc7d 100644
--- a/kotlinx-coroutines-core/jvm/src/channels/Actor.kt
+++ b/kotlinx-coroutines-core/jvm/src/channels/Actor.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.channels
@@ -127,7 +127,11 @@ private open class ActorCoroutine<E>(
parentContext: CoroutineContext,
channel: Channel<E>,
active: Boolean
-) : ChannelCoroutine<E>(parentContext, channel, active), ActorScope<E> {
+) : ChannelCoroutine<E>(parentContext, channel, initParentJob = false, active = active), ActorScope<E> {
+
+ init {
+ initParentJob(parentContext[Job])
+ }
override fun onCancelling(cause: Throwable?) {
_channel.cancel(cause?.let {
@@ -159,11 +163,17 @@ private class LazyActorCoroutine<E>(
return super.send(element)
}
+ @Suppress("DEPRECATION")
override fun offer(element: E): Boolean {
start()
return super.offer(element)
}
+ override fun trySend(element: E): ChannelResult<Unit> {
+ start()
+ return super.trySend(element)
+ }
+
override fun close(cause: Throwable?): Boolean {
// close the channel _first_
val closed = super.close(cause)
diff --git a/kotlinx-coroutines-core/jvm/src/channels/Channels.kt b/kotlinx-coroutines-core/jvm/src/channels/Channels.kt
index 2c949959..0df8278b 100644
--- a/kotlinx-coroutines-core/jvm/src/channels/Channels.kt
+++ b/kotlinx-coroutines-core/jvm/src/channels/Channels.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
@file:JvmMultifileClass
@@ -10,18 +10,87 @@ package kotlinx.coroutines.channels
import kotlinx.coroutines.*
/**
- * Adds [element] into to this channel, **blocking** the caller while this channel [Channel.isFull],
- * or throws exception if the channel [Channel.isClosedForSend] (see [Channel.close] for details).
+ * **Deprecated** blocking variant of send.
+ * This method is deprecated in the favour of [trySendBlocking].
*
- * This is a way to call [Channel.send] method inside a blocking code using [runBlocking],
- * so this function should not be used from coroutine.
+ * `sendBlocking` is a dangerous primitive &mdash; it throws an exception
+ * if the channel was closed or, more commonly, cancelled.
+ * Cancellation exceptions in non-blocking code are unexpected and frequently
+ * trigger internal failures.
+ *
+ * These bugs are hard-to-spot during code review and they forced users to write
+ * their own wrappers around `sendBlocking`.
+ * So this function is deprecated and replaced with a more explicit primitive.
+ *
+ * The real-world example of broken usage with Firebase:
+ *
+ * ```kotlin
+ * callbackFlow {
+ * val listener = object : ValueEventListener {
+ * override fun onDataChange(snapshot: DataSnapshot) {
+ * // This line may fail and crash the app when the downstream flow is cancelled
+ * sendBlocking(DataSnapshot(snapshot))
+ * }
+ *
+ * override fun onCancelled(error: DatabaseError) {
+ * close(error.toException())
+ * }
+ * }
+ *
+ * firebaseQuery.addValueEventListener(listener)
+ * awaitClose { firebaseQuery.removeEventListener(listener) }
+ * }
+ * ```
*/
+@Deprecated(
+ level = DeprecationLevel.WARNING,
+ message = "Deprecated in the favour of 'trySendBlocking'. " +
+ "Consider handling the result of 'trySendBlocking' explicitly and rethrow exception if necessary",
+ replaceWith = ReplaceWith("trySendBlocking(element)")
+)
public fun <E> SendChannel<E>.sendBlocking(element: E) {
// fast path
- if (offer(element))
+ if (trySend(element).isSuccess)
return
// slow path
runBlocking {
send(element)
}
}
+
+/**
+ * Adds [element] into to this channel, **blocking** the caller while this channel is full,
+ * and returning either [successful][ChannelResult.isSuccess] result when the element was added, or
+ * failed result representing closed channel with a corresponding exception.
+ *
+ * This is a way to call [Channel.send] method in a safe manner inside a blocking code using [runBlocking] and catching,
+ * so this function should not be used from coroutine.
+ *
+ * Example of usage:
+ *
+ * ```
+ * // From callback API
+ * channel.trySendBlocking(element)
+ * .onSuccess { /* request next element or debug log */ }
+ * .onFailure { t: Throwable? -> /* throw or log */ }
+ * ```
+ *
+ * For this operation it is guaranteed that [failure][ChannelResult.failed] always contains an exception in it.
+ *
+ * @throws [InterruptedException] if the current thread is interrupted during the blocking send operation.
+ */
+@Throws(InterruptedException::class)
+public fun <E> SendChannel<E>.trySendBlocking(element: E): ChannelResult<Unit> {
+ /*
+ * Sent successfully -- bail out.
+ * But failure may indicate either that the channel it full or that
+ * it is close. Go to slow path on failure to simplify the successful path and
+ * to materialize default exception.
+ */
+ trySend(element).onSuccess { return ChannelResult.success(Unit) }
+ return runBlocking {
+ val r = runCatching { send(element) }
+ if (r.isSuccess) ChannelResult.success(Unit)
+ else ChannelResult.closed(r.exceptionOrNull())
+ }
+}
diff --git a/kotlinx-coroutines-core/jvm/src/channels/TickerChannels.kt b/kotlinx-coroutines-core/jvm/src/channels/TickerChannels.kt
index 1e6797ac..6c23982e 100644
--- a/kotlinx-coroutines-core/jvm/src/channels/TickerChannels.kt
+++ b/kotlinx-coroutines-core/jvm/src/channels/TickerChannels.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.channels
@@ -21,13 +21,13 @@ public enum class TickerMode {
* ```
* val channel = ticker(delay = 100)
* delay(350) // 250 ms late
- * println(channel.poll()) // prints Unit
- * println(channel.poll()) // prints null
+ * println(channel.tryReceive().getOrNull()) // prints Unit
+ * println(channel.tryReceive().getOrNull()) // prints null
*
* delay(50)
- * println(channel.poll()) // prints Unit, delay was adjusted
+ * println(channel.tryReceive().getOrNull()) // prints Unit, delay was adjusted
* delay(50)
- * println(channel.poll()) // prints null, we'are not late relatively to previous element
+ * println(channel.tryReceive().getOrNull()) // prints null, we're not late relatively to previous element
* ```
*/
FIXED_PERIOD,
diff --git a/kotlinx-coroutines-core/jvm/src/debug/AgentPremain.kt b/kotlinx-coroutines-core/jvm/src/debug/AgentPremain.kt
index 5a1a1ed1..8ef0c182 100644
--- a/kotlinx-coroutines-core/jvm/src/debug/AgentPremain.kt
+++ b/kotlinx-coroutines-core/jvm/src/debug/AgentPremain.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.debug
diff --git a/kotlinx-coroutines-core/jvm/src/debug/internal/ConcurrentWeakMap.kt b/kotlinx-coroutines-core/jvm/src/debug/internal/ConcurrentWeakMap.kt
index 79f024cc..ffb9c2da 100644
--- a/kotlinx-coroutines-core/jvm/src/debug/internal/ConcurrentWeakMap.kt
+++ b/kotlinx-coroutines-core/jvm/src/debug/internal/ConcurrentWeakMap.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.debug.internal
diff --git a/kotlinx-coroutines-core/jvm/src/debug/internal/DebugCoroutineInfo.kt b/kotlinx-coroutines-core/jvm/src/debug/internal/DebugCoroutineInfo.kt
index 9d9fa3fb..6c353929 100644
--- a/kotlinx-coroutines-core/jvm/src/debug/internal/DebugCoroutineInfo.kt
+++ b/kotlinx-coroutines-core/jvm/src/debug/internal/DebugCoroutineInfo.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.debug.internal
@@ -27,4 +27,4 @@ internal class DebugCoroutineInfo(
public val lastObservedFrame: CoroutineStackFrame? = source.lastObservedFrame // field is used as of 1.4-M3
@get:JvmName("lastObservedStackTrace") // method with this name is used as of 1.4-M3
public val lastObservedStackTrace: List<StackTraceElement> = source.lastObservedStackTrace()
-} \ No newline at end of file
+}
diff --git a/kotlinx-coroutines-core/jvm/src/debug/internal/DebugCoroutineInfoImpl.kt b/kotlinx-coroutines-core/jvm/src/debug/internal/DebugCoroutineInfoImpl.kt
index cf007bb9..07b9419f 100644
--- a/kotlinx-coroutines-core/jvm/src/debug/internal/DebugCoroutineInfoImpl.kt
+++ b/kotlinx-coroutines-core/jvm/src/debug/internal/DebugCoroutineInfoImpl.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.debug.internal
@@ -72,7 +72,7 @@ internal class DebugCoroutineInfoImpl(
private fun creationStackTrace(): List<StackTraceElement> {
val bottom = creationStackBottom ?: return emptyList()
// Skip "Coroutine creation stacktrace" frame
- return sequence<StackTraceElement> { yieldFrames(bottom.callerFrame) }.toList()
+ return sequence { yieldFrames(bottom.callerFrame) }.toList()
}
private tailrec suspend fun SequenceScope<StackTraceElement>.yieldFrames(frame: CoroutineStackFrame?) {
diff --git a/kotlinx-coroutines-core/jvm/src/debug/internal/DebugProbes.kt b/kotlinx-coroutines-core/jvm/src/debug/internal/DebugProbes.kt
new file mode 100644
index 00000000..8dc5b7c2
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/src/debug/internal/DebugProbes.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+@file:Suppress("unused")
+
+package kotlinx.coroutines.debug.internal
+
+import kotlin.coroutines.*
+
+/*
+ * This class is used by ByteBuddy from kotlinx-coroutines-debug as kotlin.coroutines.jvm.internal.DebugProbesKt replacement.
+ * In theory, it should belong to kotlinx-coroutines-debug, but placing it here significantly simplifies the
+ * Android AS debugger that does on-load DEX transformation
+ */
+
+// Stubs which are injected as coroutine probes. Require direct match of signatures
+internal fun probeCoroutineResumed(frame: Continuation<*>) = DebugProbesImpl.probeCoroutineResumed(frame)
+
+internal fun probeCoroutineSuspended(frame: Continuation<*>) = DebugProbesImpl.probeCoroutineSuspended(frame)
+internal fun <T> probeCoroutineCreated(completion: Continuation<T>): Continuation<T> =
+ DebugProbesImpl.probeCoroutineCreated(completion)
diff --git a/kotlinx-coroutines-core/jvm/src/debug/internal/DebugProbesImpl.kt b/kotlinx-coroutines-core/jvm/src/debug/internal/DebugProbesImpl.kt
index 83bc02c6..05befc1a 100644
--- a/kotlinx-coroutines-core/jvm/src/debug/internal/DebugProbesImpl.kt
+++ b/kotlinx-coroutines-core/jvm/src/debug/internal/DebugProbesImpl.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.debug.internal
@@ -282,7 +282,7 @@ internal object DebugProbesImpl {
it.fileName == "ContinuationImpl.kt"
}
- val (continuationStartFrame, frameSkipped) = findContinuationStartIndex(
+ val (continuationStartFrame, delta) = findContinuationStartIndex(
indexOfResumeWith,
actualTrace,
coroutineTrace
@@ -290,7 +290,6 @@ internal object DebugProbesImpl {
if (continuationStartFrame == -1) return coroutineTrace
- val delta = if (frameSkipped) 1 else 0
val expectedSize = indexOfResumeWith + coroutineTrace.size - continuationStartFrame - 1 - delta
val result = ArrayList<StackTraceElement>(expectedSize)
for (index in 0 until indexOfResumeWith - delta) {
@@ -312,16 +311,22 @@ internal object DebugProbesImpl {
* If method above `resumeWith` has no line number (thus it is `stateMachine.invokeSuspend`),
* it's skipped and attempt to match next one is made because state machine could have been missing in the original coroutine stacktrace.
*
- * Returns index of such frame (or -1) and flag indicating whether frame with state machine was skipped
+ * Returns index of such frame (or -1) and number of skipped frames (up to 2, for state machine and for access$).
*/
private fun findContinuationStartIndex(
indexOfResumeWith: Int,
actualTrace: Array<StackTraceElement>,
coroutineTrace: List<StackTraceElement>
- ): Pair<Int, Boolean> {
- val result = findIndexOfFrame(indexOfResumeWith - 1, actualTrace, coroutineTrace)
- if (result == -1) return findIndexOfFrame(indexOfResumeWith - 2, actualTrace, coroutineTrace) to true
- return result to false
+ ): Pair<Int, Int> {
+ /*
+ * Since Kotlin 1.5.0 we have these access$ methods that we have to skip.
+ * So we have to test next frame for invokeSuspend, for $access and for actual suspending call.
+ */
+ repeat(3) {
+ val result = findIndexOfFrame(indexOfResumeWith - 1 - it, actualTrace, coroutineTrace)
+ if (result != -1) return result to it
+ }
+ return -1 to 0
}
private fun findIndexOfFrame(
@@ -477,33 +482,40 @@ internal object DebugProbesImpl {
/*
* Trim intervals of internal methods from the stacktrace (bounds are excluded from trimming)
- * E.g. for sequence [e, i1, i2, i3, e, i4, e, i5, i6, e7]
+ * E.g. for sequence [e, i1, i2, i3, e, i4, e, i5, i6, i7]
* output will be [e, i1, i3, e, i4, e, i5, i7]
+ *
+ * If an interval of internal methods ends in a synthetic method, the outermost non-synthetic method in that
+ * interval will also be included.
*/
val result = ArrayList<StackTraceElement>(size - probeIndex + 1)
result += createArtificialFrame(ARTIFICIAL_FRAME_MESSAGE)
- var includeInternalFrame = true
- for (i in (probeIndex + 1) until size - 1) {
- val element = stackTrace[i]
- if (!element.isInternalMethod) {
- includeInternalFrame = true
- result += element
- continue
- }
-
- if (includeInternalFrame) {
- result += element
- includeInternalFrame = false
- } else if (stackTrace[i + 1].isInternalMethod) {
- continue
+ var i = probeIndex + 1
+ while (i < size) {
+ if (stackTrace[i].isInternalMethod) {
+ result += stackTrace[i] // we include the boundary of the span in any case
+ // first index past the end of the span of internal methods that starts from `i`
+ var j = i + 1
+ while (j < size && stackTrace[j].isInternalMethod) {
+ ++j
+ }
+ // index of the last non-synthetic internal methods in this span, or `i` if there are no such methods
+ var k = j - 1
+ while (k > i && stackTrace[k].fileName == null) {
+ k -= 1
+ }
+ if (k > i && k < j - 1) {
+ /* there are synthetic internal methods at the end of this span, but there is a non-synthetic method
+ after `i`, so we include it. */
+ result += stackTrace[k]
+ }
+ result += stackTrace[j - 1] // we include the other boundary of this span in any case, too
+ i = j
} else {
- result += element
- includeInternalFrame = true
+ result += stackTrace[i]
+ ++i
}
-
}
-
- result += stackTrace[size - 1]
return result
}
diff --git a/kotlinx-coroutines-core/jvm/src/debug/internal/DebuggerInfo.kt b/kotlinx-coroutines-core/jvm/src/debug/internal/DebuggerInfo.kt
index 3e9533b0..0b63cd23 100644
--- a/kotlinx-coroutines-core/jvm/src/debug/internal/DebuggerInfo.kt
+++ b/kotlinx-coroutines-core/jvm/src/debug/internal/DebuggerInfo.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
@file:Suppress("UNUSED")
diff --git a/kotlinx-coroutines-core/jvm/src/debug/internal/StackTraceFrame.kt b/kotlinx-coroutines-core/jvm/src/debug/internal/StackTraceFrame.kt
index 37c60eeb..40101192 100644
--- a/kotlinx-coroutines-core/jvm/src/debug/internal/StackTraceFrame.kt
+++ b/kotlinx-coroutines-core/jvm/src/debug/internal/StackTraceFrame.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.debug.internal
@@ -14,4 +14,4 @@ internal class StackTraceFrame(
private val stackTraceElement: StackTraceElement
) : CoroutineStackFrame {
override fun getStackTraceElement(): StackTraceElement = stackTraceElement
-} \ No newline at end of file
+}
diff --git a/kotlinx-coroutines-core/jvm/src/flow/internal/FlowExceptions.kt b/kotlinx-coroutines-core/jvm/src/flow/internal/FlowExceptions.kt
index 4fb958ac..d178060d 100644
--- a/kotlinx-coroutines-core/jvm/src/flow/internal/FlowExceptions.kt
+++ b/kotlinx-coroutines-core/jvm/src/flow/internal/FlowExceptions.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.flow.internal
diff --git a/kotlinx-coroutines-core/jvm/src/flow/internal/SafeCollector.kt b/kotlinx-coroutines-core/jvm/src/flow/internal/SafeCollector.kt
index ab42b634..ea973287 100644
--- a/kotlinx-coroutines-core/jvm/src/flow/internal/SafeCollector.kt
+++ b/kotlinx-coroutines-core/jvm/src/flow/internal/SafeCollector.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.flow.internal
@@ -36,7 +36,7 @@ internal actual class SafeCollector<T> actual constructor(
override val context: CoroutineContext
get() = completion?.context ?: EmptyCoroutineContext
- override fun invokeSuspend(result: Result<Any?>): Any? {
+ override fun invokeSuspend(result: Result<Any?>): Any {
result.onFailure { lastEmissionContext = DownstreamExceptionElement(it) }
completion?.resumeWith(result as Result<Unit>)
return COROUTINE_SUSPENDED
diff --git a/kotlinx-coroutines-core/jvm/src/internal/Concurrent.kt b/kotlinx-coroutines-core/jvm/src/internal/Concurrent.kt
index 75b668a3..050b9747 100644
--- a/kotlinx-coroutines-core/jvm/src/internal/Concurrent.kt
+++ b/kotlinx-coroutines-core/jvm/src/internal/Concurrent.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.internal
@@ -9,7 +9,7 @@ import java.util.*
import java.util.concurrent.*
import kotlin.concurrent.withLock as withLockJvm
-internal actual fun <E> subscriberList(): SubscribersList<E> = CopyOnWriteArrayList<E>()
+internal actual fun <E> subscriberList(): SubscribersList<E> = CopyOnWriteArrayList()
@Suppress("ACTUAL_WITHOUT_EXPECT")
internal actual typealias ReentrantLock = java.util.concurrent.locks.ReentrantLock
diff --git a/kotlinx-coroutines-core/jvm/src/internal/ExceptionsConstuctor.kt b/kotlinx-coroutines-core/jvm/src/internal/ExceptionsConstuctor.kt
index a03163db..60328ebd 100644
--- a/kotlinx-coroutines-core/jvm/src/internal/ExceptionsConstuctor.kt
+++ b/kotlinx-coroutines-core/jvm/src/internal/ExceptionsConstuctor.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.internal
diff --git a/kotlinx-coroutines-core/jvm/src/internal/FastServiceLoader.kt b/kotlinx-coroutines-core/jvm/src/internal/FastServiceLoader.kt
index 30cd09ef..e93de2aa 100644
--- a/kotlinx-coroutines-core/jvm/src/internal/FastServiceLoader.kt
+++ b/kotlinx-coroutines-core/jvm/src/internal/FastServiceLoader.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.internal
diff --git a/kotlinx-coroutines-core/jvm/src/internal/LocalAtomics.kt b/kotlinx-coroutines-core/jvm/src/internal/LocalAtomics.kt
index f508749e..9dda30b5 100644
--- a/kotlinx-coroutines-core/jvm/src/internal/LocalAtomics.kt
+++ b/kotlinx-coroutines-core/jvm/src/internal/LocalAtomics.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.internal
diff --git a/kotlinx-coroutines-core/jvm/src/internal/LockFreeLinkedList.kt b/kotlinx-coroutines-core/jvm/src/internal/LockFreeLinkedList.kt
index 97f99781..9bbc6dc9 100644
--- a/kotlinx-coroutines-core/jvm/src/internal/LockFreeLinkedList.kt
+++ b/kotlinx-coroutines-core/jvm/src/internal/LockFreeLinkedList.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
@file:Suppress("NO_EXPLICIT_VISIBILITY_IN_API_MODE")
@@ -11,13 +11,13 @@ import kotlinx.coroutines.*
private typealias Node = LockFreeLinkedListNode
@PublishedApi
-internal const val UNDECIDED = 0
+internal const val UNDECIDED: Int = 0
@PublishedApi
-internal const val SUCCESS = 1
+internal const val SUCCESS: Int = 1
@PublishedApi
-internal const val FAILURE = 2
+internal const val FAILURE: Int = 2
@PublishedApi
internal val CONDITION_FALSE: Any = Symbol("CONDITION_FALSE")
@@ -259,7 +259,7 @@ public actual open class LockFreeLinkedListNode {
// Helps with removal of this node
public actual fun helpRemove() {
// Note: this node must be already removed
- (next as Removed).ref.correctPrev(null)
+ (next as Removed).ref.helpRemovePrev()
}
// Helps with removal of nodes that are previous to this
@@ -322,7 +322,7 @@ public actual open class LockFreeLinkedListNode {
private val _affectedNode = atomic<Node?>(null)
final override val affectedNode: Node? get() = _affectedNode.value
- final override val originalNext: Node? get() = queue
+ final override val originalNext: Node get() = queue
override fun retry(affected: Node, next: Any): Boolean = next !== queue
diff --git a/kotlinx-coroutines-core/jvm/src/internal/MainDispatchers.kt b/kotlinx-coroutines-core/jvm/src/internal/MainDispatchers.kt
index 5b2b9ff6..2d447413 100644
--- a/kotlinx-coroutines-core/jvm/src/internal/MainDispatchers.kt
+++ b/kotlinx-coroutines-core/jvm/src/internal/MainDispatchers.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.internal
@@ -35,7 +35,7 @@ internal object MainDispatcherLoader {
).iterator().asSequence().toList()
}
@Suppress("ConstantConditionIf")
- factories.maxBy { it.loadPriority }?.tryCreateDispatcher(factories)
+ factories.maxByOrNull { it.loadPriority }?.tryCreateDispatcher(factories)
?: createMissingDispatcher()
} catch (e: Throwable) {
// Service loader can throw an exception as well
@@ -67,7 +67,10 @@ public fun MainCoroutineDispatcher.isMissing(): Boolean = this is MissingMainCor
@Suppress("MayBeConstant")
private val SUPPORT_MISSING = true
-@Suppress("ConstantConditionIf")
+@Suppress(
+ "ConstantConditionIf",
+ "IMPLICIT_NOTHING_TYPE_ARGUMENT_AGAINST_NOT_NOTHING_EXPECTED_TYPE" // KT-47626
+)
private fun createMissingDispatcher(cause: Throwable? = null, errorHint: String? = null) =
if (SUPPORT_MISSING) MissingMainCoroutineDispatcher(cause, errorHint) else
cause?.let { throw it } ?: throwMissingMainDispatcherException()
diff --git a/kotlinx-coroutines-core/jvm/src/internal/ProbesSupport.kt b/kotlinx-coroutines-core/jvm/src/internal/ProbesSupport.kt
index 2f4d1e05..48e01fbe 100644
--- a/kotlinx-coroutines-core/jvm/src/internal/ProbesSupport.kt
+++ b/kotlinx-coroutines-core/jvm/src/internal/ProbesSupport.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
@file:Suppress("NOTHING_TO_INLINE", "INVISIBLE_REFERENCE", "INVISIBLE_MEMBER")
diff --git a/kotlinx-coroutines-core/jvm/src/internal/StackTraceRecovery.kt b/kotlinx-coroutines-core/jvm/src/internal/StackTraceRecovery.kt
index 208d3f2e..6153862e 100644
--- a/kotlinx-coroutines-core/jvm/src/internal/StackTraceRecovery.kt
+++ b/kotlinx-coroutines-core/jvm/src/internal/StackTraceRecovery.kt
@@ -1,8 +1,8 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
-@file:Suppress("UNCHECKED_CAST", "NO_EXPLICIT_VISIBILITY_IN_API_MODE")
+@file:Suppress("UNCHECKED_CAST")
package kotlinx.coroutines.internal
@@ -29,7 +29,7 @@ private val stackTraceRecoveryClassName = runCatching {
internal actual fun <E : Throwable> recoverStackTrace(exception: E): E {
if (!RECOVER_STACK_TRACES) return exception
// No unwrapping on continuation-less path: exception is not reported multiple times via slow paths
- val copy = tryCopyException(exception) ?: return exception
+ val copy = tryCopyAndVerify(exception) ?: return exception
return copy.sanitizeStackTrace()
}
@@ -66,9 +66,7 @@ private fun <E : Throwable> recoverFromStackFrame(exception: E, continuation: Co
val (cause, recoveredStacktrace) = exception.causeAndStacktrace()
// Try to create an exception of the same type and get stacktrace from continuation
- val newException = tryCopyException(cause) ?: return exception
- // Verify that the new exception has the same message as the original one (bail out if not, see #1631)
- if (newException.message != cause.message) return exception
+ val newException = tryCopyAndVerify(cause) ?: return exception
// Update stacktrace
val stacktrace = createStackTrace(continuation)
if (stacktrace.isEmpty()) return exception
@@ -80,6 +78,14 @@ private fun <E : Throwable> recoverFromStackFrame(exception: E, continuation: Co
return createFinalException(cause, newException, stacktrace)
}
+private fun <E : Throwable> tryCopyAndVerify(exception: E): E? {
+ val newException = tryCopyException(exception) ?: return null
+ // Verify that the new exception has the same message as the original one (bail out if not, see #1631)
+ // CopyableThrowable has control over its message and thus can modify it the way it wants
+ if (exception !is CopyableThrowable<*> && newException.message != exception.message) return null
+ return newException
+}
+
/*
* Here we partially copy original exception stackTrace to make current one much prettier.
* E.g. for
@@ -210,6 +216,7 @@ internal actual typealias CoroutineStackFrame = kotlin.coroutines.jvm.internal.C
@Suppress("ACTUAL_WITHOUT_EXPECT")
internal actual typealias StackTraceElement = java.lang.StackTraceElement
+@Suppress("EXTENSION_SHADOWED_BY_MEMBER")
internal actual fun Throwable.initCause(cause: Throwable) {
// Resolved to member, verified by test
initCause(cause)
diff --git a/kotlinx-coroutines-core/jvm/src/internal/Synchronized.kt b/kotlinx-coroutines-core/jvm/src/internal/Synchronized.kt
index 2b57b26c..6284f3a0 100644
--- a/kotlinx-coroutines-core/jvm/src/internal/Synchronized.kt
+++ b/kotlinx-coroutines-core/jvm/src/internal/Synchronized.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.internal
diff --git a/kotlinx-coroutines-core/jvm/src/internal/SystemProps.kt b/kotlinx-coroutines-core/jvm/src/internal/SystemProps.kt
index bf34c1a9..73853720 100644
--- a/kotlinx-coroutines-core/jvm/src/internal/SystemProps.kt
+++ b/kotlinx-coroutines-core/jvm/src/internal/SystemProps.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
@file:JvmName("SystemPropsKt")
diff --git a/kotlinx-coroutines-core/jvm/src/internal/ThreadContext.kt b/kotlinx-coroutines-core/jvm/src/internal/ThreadContext.kt
index 9d9d30e4..8536cef6 100644
--- a/kotlinx-coroutines-core/jvm/src/internal/ThreadContext.kt
+++ b/kotlinx-coroutines-core/jvm/src/internal/ThreadContext.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.internal
@@ -7,17 +7,26 @@ package kotlinx.coroutines.internal
import kotlinx.coroutines.*
import kotlin.coroutines.*
-
-private val ZERO = Symbol("ZERO")
+@JvmField
+internal val NO_THREAD_ELEMENTS = Symbol("NO_THREAD_ELEMENTS")
// Used when there are >= 2 active elements in the context
-private class ThreadState(val context: CoroutineContext, n: Int) {
- private var a = arrayOfNulls<Any>(n)
+@Suppress("UNCHECKED_CAST")
+private class ThreadState(@JvmField val context: CoroutineContext, n: Int) {
+ private val values = arrayOfNulls<Any>(n)
+ private val elements = arrayOfNulls<ThreadContextElement<Any?>>(n)
private var i = 0
- fun append(value: Any?) { a[i++] = value }
- fun take() = a[i++]
- fun start() { i = 0 }
+ fun append(element: ThreadContextElement<*>, value: Any?) {
+ values[i] = value
+ elements[i++] = element as ThreadContextElement<Any?>
+ }
+
+ fun restore(context: CoroutineContext) {
+ for (i in elements.indices.reversed()) {
+ elements[i]!!.restoreThreadContext(context, values[i])
+ }
+ }
}
// Counts ThreadContextElements in the context
@@ -42,17 +51,7 @@ private val findOne =
private val updateState =
fun (state: ThreadState, element: CoroutineContext.Element): ThreadState {
if (element is ThreadContextElement<*>) {
- state.append(element.updateThreadContext(state.context))
- }
- return state
- }
-
-// Restores state for all ThreadContextElements in the context from the given ThreadState
-private val restoreState =
- fun (state: ThreadState, element: CoroutineContext.Element): ThreadState {
- @Suppress("UNCHECKED_CAST")
- if (element is ThreadContextElement<*>) {
- (element as ThreadContextElement<Any?>).restoreThreadContext(state.context, state.take())
+ state.append(element, element.updateThreadContext(state.context))
}
return state
}
@@ -60,12 +59,13 @@ private val restoreState =
internal actual fun threadContextElements(context: CoroutineContext): Any = context.fold(0, countAll)!!
// countOrElement is pre-cached in dispatched continuation
+// returns NO_THREAD_ELEMENTS if the contest does not have any ThreadContextElements
internal fun updateThreadContext(context: CoroutineContext, countOrElement: Any?): Any? {
@Suppress("NAME_SHADOWING")
val countOrElement = countOrElement ?: threadContextElements(context)
@Suppress("IMPLICIT_BOXING_IN_IDENTITY_EQUALS")
return when {
- countOrElement === 0 -> ZERO // very fast path when there are no active ThreadContextElements
+ countOrElement === 0 -> NO_THREAD_ELEMENTS // very fast path when there are no active ThreadContextElements
// ^^^ identity comparison for speed, we know zero always has the same identity
countOrElement is Int -> {
// slow path for multiple active ThreadContextElements, allocates ThreadState for multiple old values
@@ -82,11 +82,10 @@ internal fun updateThreadContext(context: CoroutineContext, countOrElement: Any?
internal fun restoreThreadContext(context: CoroutineContext, oldState: Any?) {
when {
- oldState === ZERO -> return // very fast path when there are no ThreadContextElements
+ oldState === NO_THREAD_ELEMENTS -> return // very fast path when there are no ThreadContextElements
oldState is ThreadState -> {
// slow path with multiple stored ThreadContextElements
- oldState.start()
- context.fold(oldState, restoreState)
+ oldState.restore(context)
}
else -> {
// fast path for one ThreadContextElement, but need to find it
diff --git a/kotlinx-coroutines-core/jvm/src/internal/ThreadLocal.kt b/kotlinx-coroutines-core/jvm/src/internal/ThreadLocal.kt
index ff0970a2..0207334a 100644
--- a/kotlinx-coroutines-core/jvm/src/internal/ThreadLocal.kt
+++ b/kotlinx-coroutines-core/jvm/src/internal/ThreadLocal.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.internal
diff --git a/kotlinx-coroutines-core/jvm/src/scheduling/CoroutineScheduler.kt b/kotlinx-coroutines-core/jvm/src/scheduling/CoroutineScheduler.kt
index 62cf80f7..84d9d9f8 100644
--- a/kotlinx-coroutines-core/jvm/src/scheduling/CoroutineScheduler.kt
+++ b/kotlinx-coroutines-core/jvm/src/scheduling/CoroutineScheduler.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.scheduling
@@ -146,7 +146,7 @@ internal class CoroutineScheduler(
*
* Note, [newIndex] can be zero for the worker that is being terminated (removed from [workers]).
*/
- internal fun parkedWorkersStackTopUpdate(worker: Worker, oldIndex: Int, newIndex: Int) {
+ fun parkedWorkersStackTopUpdate(worker: Worker, oldIndex: Int, newIndex: Int) {
parkedWorkersStack.loop { top ->
val index = (top and PARKED_INDEX_MASK).toInt()
val updVersion = (top + PARKED_VERSION_INC) and PARKED_VERSION_MASK
@@ -169,12 +169,12 @@ internal class CoroutineScheduler(
* It does nothing is this worker is already physically linked to the stack.
* This method is invoked only from the worker thread itself.
* This invocation always precedes [LockSupport.parkNanos].
- * See [Worker.doPark].
+ * See [Worker.tryPark].
*
* Returns `true` if worker was added to the stack by this invocation, `false` if it was already
* registered in the stack.
*/
- internal fun parkedWorkersStackPush(worker: Worker): Boolean {
+ fun parkedWorkersStackPush(worker: Worker): Boolean {
if (worker.nextParkedWorker !== NOT_IN_STACK) return false // already in stack, bail out
/*
* The below loop can be entered only if this worker was not in the stack and, since no other thread
@@ -403,7 +403,7 @@ internal class CoroutineScheduler(
}
}
- internal fun createTask(block: Runnable, taskContext: TaskContext): Task {
+ fun createTask(block: Runnable, taskContext: TaskContext): Task {
val nanoTime = schedulerTimeSource.nanoTime()
if (block is Task) {
block.submissionTime = nanoTime
@@ -422,7 +422,7 @@ internal class CoroutineScheduler(
tryUnpark() // Try unpark again in case there was race between permit release and parking
}
- internal fun signalCpuWork() {
+ fun signalCpuWork() {
if (tryUnpark()) return
if (tryCreateWorker()) return
tryUnpark()
@@ -654,7 +654,7 @@ internal class CoroutineScheduler(
* Releases CPU token if worker has any and changes state to [newState].
* Returns `true` if CPU permit was returned to the pool
*/
- internal fun tryReleaseCpu(newState: WorkerState): Boolean {
+ fun tryReleaseCpu(newState: WorkerState): Boolean {
val previousState = state
val hadCpu = previousState == WorkerState.CPU_ACQUIRED
if (hadCpu) releaseCpuPermit()
@@ -721,7 +721,19 @@ internal class CoroutineScheduler(
}
assert { localQueue.size == 0 }
workerCtl.value = PARKED // Update value once
- while (inStack()) { // Prevent spurious wakeups
+ /*
+ * inStack() prevents spurious wakeups, while workerCtl.value == PARKED
+ * prevents the following race:
+ *
+ * - T2 scans the queue, adds itself to the stack, goes to rescan
+ * - T2 suspends in 'workerCtl.value = PARKED' line
+ * - T1 pops T2 from the stack, claims workerCtl, suspends
+ * - T2 fails 'while (inStack())' check, goes to full rescan
+ * - T2 adds itself to the stack, parks
+ * - T1 unparks T2, bails out with success
+ * - T2 unparks and loops in 'while (inStack())'
+ */
+ while (inStack() && workerCtl.value == PARKED) { // Prevent spurious wakeups
if (isTerminated || state == WorkerState.TERMINATED) break
tryReleaseCpu(WorkerState.PARKING)
interrupted() // Cleanup interruptions
@@ -762,7 +774,7 @@ internal class CoroutineScheduler(
* Marsaglia xorshift RNG with period 2^32-1 for work stealing purposes.
* ThreadLocalRandom cannot be used to support Android and ThreadLocal<Random> is up to 15% slower on Ktor benchmarks
*/
- internal fun nextInt(upperBound: Int): Int {
+ fun nextInt(upperBound: Int): Int {
var r = rngState
r = r xor (r shl 13)
r = r xor (r shr 17)
diff --git a/kotlinx-coroutines-core/jvm/src/scheduling/Dispatcher.kt b/kotlinx-coroutines-core/jvm/src/scheduling/Dispatcher.kt
index 202c6e1d..7227b07c 100644
--- a/kotlinx-coroutines-core/jvm/src/scheduling/Dispatcher.kt
+++ b/kotlinx-coroutines-core/jvm/src/scheduling/Dispatcher.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.scheduling
diff --git a/kotlinx-coroutines-core/jvm/src/scheduling/Tasks.kt b/kotlinx-coroutines-core/jvm/src/scheduling/Tasks.kt
index a317b975..da867c98 100644
--- a/kotlinx-coroutines-core/jvm/src/scheduling/Tasks.kt
+++ b/kotlinx-coroutines-core/jvm/src/scheduling/Tasks.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.scheduling
@@ -52,7 +52,7 @@ internal val IDLE_WORKER_KEEP_ALIVE_NS = TimeUnit.SECONDS.toNanos(
)
@JvmField
-internal var schedulerTimeSource: TimeSource = NanoTimeSource
+internal var schedulerTimeSource: SchedulerTimeSource = NanoTimeSource
/**
* Marker indicating that task is CPU-bound and will not block
@@ -108,10 +108,11 @@ internal class TaskImpl(
// Open for tests
internal class GlobalQueue : LockFreeTaskQueue<Task>(singleConsumer = false)
-internal abstract class TimeSource {
+// Was previously TimeSource, renamed due to KT-42625 and KT-23727
+internal abstract class SchedulerTimeSource {
abstract fun nanoTime(): Long
}
-internal object NanoTimeSource : TimeSource() {
+internal object NanoTimeSource : SchedulerTimeSource() {
override fun nanoTime() = System.nanoTime()
}
diff --git a/kotlinx-coroutines-core/jvm/src/scheduling/WorkQueue.kt b/kotlinx-coroutines-core/jvm/src/scheduling/WorkQueue.kt
index 354b3a1b..6a9a8a5a 100644
--- a/kotlinx-coroutines-core/jvm/src/scheduling/WorkQueue.kt
+++ b/kotlinx-coroutines-core/jvm/src/scheduling/WorkQueue.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.scheduling
diff --git a/kotlinx-coroutines-core/jvm/src/test_/TestCoroutineContext.kt b/kotlinx-coroutines-core/jvm/src/test_/TestCoroutineContext.kt
index 649c9537..8526ca21 100644
--- a/kotlinx-coroutines-core/jvm/src/test_/TestCoroutineContext.kt
+++ b/kotlinx-coroutines-core/jvm/src/test_/TestCoroutineContext.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.test
diff --git a/kotlinx-coroutines-core/jvm/test-resources/stacktraces/channels/testCancelledOffer.txt b/kotlinx-coroutines-core/jvm/test-resources/stacktraces/channels/testCancelledOffer.txt
deleted file mode 100644
index cfed5af4..00000000
--- a/kotlinx-coroutines-core/jvm/test-resources/stacktraces/channels/testCancelledOffer.txt
+++ /dev/null
@@ -1,10 +0,0 @@
-kotlinx.coroutines.JobCancellationException: Job was cancelled; job=JobImpl{Cancelling}@2a06d350
- (Coroutine boundary)
- at kotlinx.coroutines.channels.AbstractSendChannel.offer(AbstractChannel.kt:170)
- at kotlinx.coroutines.channels.ChannelCoroutine.offer(ChannelCoroutine.kt)
- at kotlinx.coroutines.exceptions.StackTraceRecoveryChannelsTest$testCancelledOffer$1.invokeSuspend(StackTraceRecoveryChannelsTest.kt:153)
-Caused by: kotlinx.coroutines.JobCancellationException: Job was cancelled; job=JobImpl{Cancelling}@2a06d350
- at kotlinx.coroutines.JobSupport.cancel(JobSupport.kt:599)
- at kotlinx.coroutines.Job$DefaultImpls.cancel$default(Job.kt:164)
- at kotlinx.coroutines.exceptions.StackTraceRecoveryChannelsTest$testCancelledOffer$1.invokeSuspend(StackTraceRecoveryChannelsTest.kt:151)
- at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
diff --git a/kotlinx-coroutines-core/jvm/test-resources/stacktraces/channels/testReceiveOrNullFromClosedChannel.txt b/kotlinx-coroutines-core/jvm/test-resources/stacktraces/channels/testReceiveOrNullFromClosedChannel.txt
deleted file mode 100644
index ac8f5f4e..00000000
--- a/kotlinx-coroutines-core/jvm/test-resources/stacktraces/channels/testReceiveOrNullFromClosedChannel.txt
+++ /dev/null
@@ -1,8 +0,0 @@
-kotlinx.coroutines.RecoverableTestException
- at kotlinx.coroutines.exceptions.StackTraceRecoveryChannelsTest$testReceiveOrNullFromClosedChannel$1.invokeSuspend(StackTraceRecoveryChannelsTest.kt:43)
- (Coroutine boundary)
- at kotlinx.coroutines.exceptions.StackTraceRecoveryChannelsTest.channelReceiveOrNull(StackTraceRecoveryChannelsTest.kt:70)
- at kotlinx.coroutines.exceptions.StackTraceRecoveryChannelsTest$testReceiveOrNullFromClosedChannel$1.invokeSuspend(StackTraceRecoveryChannelsTest.kt:44)
-Caused by: kotlinx.coroutines.RecoverableTestException
- at kotlinx.coroutines.exceptions.StackTraceRecoveryChannelsTest$testReceiveOrNullFromClosedChannel$1.invokeSuspend(StackTraceRecoveryChannelsTest.kt:43)
- at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33) \ No newline at end of file
diff --git a/kotlinx-coroutines-core/jvm/test-resources/stacktraces/select/testSelectJoin.txt b/kotlinx-coroutines-core/jvm/test-resources/stacktraces/select/testSelectJoin.txt
index 2d480861..3bfd08e5 100644
--- a/kotlinx-coroutines-core/jvm/test-resources/stacktraces/select/testSelectJoin.txt
+++ b/kotlinx-coroutines-core/jvm/test-resources/stacktraces/select/testSelectJoin.txt
@@ -1,7 +1,7 @@
kotlinx.coroutines.RecoverableTestException
- at kotlinx.coroutines.exceptions.StackTraceRecoverySelectTest$doSelect$$inlined$select$lambda$1.invokeSuspend(StackTraceRecoverySelectTest.kt:33)
+ at kotlinx.coroutines.exceptions.StackTraceRecoverySelectTest$doSelect$2$1.invokeSuspend(StackTraceRecoverySelectTest.kt)
(Coroutine boundary)
- at kotlinx.coroutines.exceptions.StackTraceRecoverySelectTest$testSelectJoin$1.invokeSuspend(StackTraceRecoverySelectTest.kt:20)
+ at kotlinx.coroutines.exceptions.StackTraceRecoverySelectTest$testSelectJoin$1.invokeSuspend(StackTraceRecoverySelectTest.kt)
Caused by: kotlinx.coroutines.RecoverableTestException
- at kotlinx.coroutines.exceptions.StackTraceRecoverySelectTest$doSelect$$inlined$select$lambda$1.invokeSuspend(StackTraceRecoverySelectTest.kt:33)
- at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33) \ No newline at end of file
+ at kotlinx.coroutines.exceptions.StackTraceRecoverySelectTest$doSelect$2$1.invokeSuspend(StackTraceRecoverySelectTest.kt)
+ at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt)
diff --git a/kotlinx-coroutines-core/jvm/test/AbstractLincheckTest.kt b/kotlinx-coroutines-core/jvm/test/AbstractLincheckTest.kt
new file mode 100644
index 00000000..89bbbfd7
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/AbstractLincheckTest.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+package kotlinx.coroutines
+
+import org.jetbrains.kotlinx.lincheck.*
+import org.jetbrains.kotlinx.lincheck.strategy.managed.modelchecking.*
+import org.jetbrains.kotlinx.lincheck.strategy.stress.*
+import org.jetbrains.kotlinx.lincheck.verifier.*
+import org.junit.*
+
+abstract class AbstractLincheckTest : VerifierState() {
+ open fun <O: Options<O, *>> O.customize(isStressTest: Boolean): O = this
+ open fun ModelCheckingOptions.customize(isStressTest: Boolean): ModelCheckingOptions = this
+ open fun StressOptions.customize(isStressTest: Boolean): StressOptions = this
+
+ @Test
+ fun modelCheckingTest() = ModelCheckingOptions()
+ .iterations(if (isStressTest) 100 else 20)
+ .invocationsPerIteration(if (isStressTest) 10_000 else 1_000)
+ .commonConfiguration()
+ .customize(isStressTest)
+ .check(this::class)
+
+ @Test
+ fun stressTest() = StressOptions()
+ .iterations(if (isStressTest) 100 else 20)
+ .invocationsPerIteration(if (isStressTest) 10_000 else 1_000)
+ .commonConfiguration()
+ .customize(isStressTest)
+ .check(this::class)
+
+ private fun <O : Options<O, *>> O.commonConfiguration(): O = this
+ .actorsBefore(if (isStressTest) 3 else 1)
+ .threads(3)
+ .actorsPerThread(if (isStressTest) 4 else 2)
+ .actorsAfter(if (isStressTest) 3 else 0)
+ .customize(isStressTest)
+
+ override fun extractState(): Any = error("Not implemented")
+}
diff --git a/kotlinx-coroutines-core/jvm/test/AsyncJvmTest.kt b/kotlinx-coroutines-core/jvm/test/AsyncJvmTest.kt
index 02675208..59ff76a5 100644
--- a/kotlinx-coroutines-core/jvm/test/AsyncJvmTest.kt
+++ b/kotlinx-coroutines-core/jvm/test/AsyncJvmTest.kt
@@ -8,7 +8,10 @@ import kotlin.test.*
class AsyncJvmTest : TestBase() {
- // This must be a common test but it fails on JS because of KT-21961
+ // We have the same test in common module, but the maintainer uses this particular file
+ // and semi-automatically types cmd+N + AsyncJvm in order to duck-tape any JVM samples/repros,
+ // please do not remove this test
+
@Test
fun testAsyncWithFinally() = runTest {
expect(1)
diff --git a/kotlinx-coroutines-core/jvm/test/CancelledAwaitStressTest.kt b/kotlinx-coroutines-core/jvm/test/CancelledAwaitStressTest.kt
index 55c05c55..c7c2c04e 100644
--- a/kotlinx-coroutines-core/jvm/test/CancelledAwaitStressTest.kt
+++ b/kotlinx-coroutines-core/jvm/test/CancelledAwaitStressTest.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines
@@ -52,4 +52,4 @@ class CancelledAwaitStressTest : TestBase() {
private fun keepMe(a: ByteArray) {
// does nothing, makes sure the variable is kept in state-machine
}
-} \ No newline at end of file
+}
diff --git a/kotlinx-coroutines-core/jvm/test/ExecutorAsCoroutineDispatcherDelayTest.kt b/kotlinx-coroutines-core/jvm/test/ExecutorAsCoroutineDispatcherDelayTest.kt
new file mode 100644
index 00000000..dbe9cb37
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/ExecutorAsCoroutineDispatcherDelayTest.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines
+
+import org.junit.Test
+import java.lang.Runnable
+import java.util.concurrent.*
+import kotlin.test.*
+
+class ExecutorAsCoroutineDispatcherDelayTest : TestBase() {
+
+ private var callsToSchedule = 0
+
+ private inner class STPE : ScheduledThreadPoolExecutor(1) {
+ override fun schedule(command: Runnable, delay: Long, unit: TimeUnit): ScheduledFuture<*> {
+ if (delay != 0L) ++callsToSchedule
+ return super.schedule(command, delay, unit)
+ }
+ }
+
+ private inner class SES : ScheduledExecutorService by STPE()
+
+ @Test
+ fun testScheduledThreadPool() = runTest {
+ val executor = STPE()
+ withContext(executor.asCoroutineDispatcher()) {
+ delay(100)
+ }
+ executor.shutdown()
+ assertEquals(1, callsToSchedule)
+ }
+
+ @Test
+ fun testScheduledExecutorService() = runTest {
+ val executor = SES()
+ withContext(executor.asCoroutineDispatcher()) {
+ delay(100)
+ }
+ executor.shutdown()
+ assertEquals(1, callsToSchedule)
+ }
+}
diff --git a/kotlinx-coroutines-core/jvm/test/FailFastOnStartTest.kt b/kotlinx-coroutines-core/jvm/test/FailFastOnStartTest.kt
index 15cb83ce..8a7878c9 100644
--- a/kotlinx-coroutines-core/jvm/test/FailFastOnStartTest.kt
+++ b/kotlinx-coroutines-core/jvm/test/FailFastOnStartTest.kt
@@ -70,8 +70,18 @@ class FailFastOnStartTest : TestBase() {
val actor = actor<Int>(Dispatchers.Main, start = CoroutineStart.LAZY) { fail() }
actor.send(1)
}
-
+
private fun mainException(e: Throwable): Boolean {
return e is IllegalStateException && e.message?.contains("Module with the Main dispatcher is missing") ?: false
}
+
+ @Test
+ fun testProduceNonChild() = runTest(expected = ::mainException) {
+ produce<Int>(Job() + Dispatchers.Main) { fail() }
+ }
+
+ @Test
+ fun testAsyncNonChild() = runTest(expected = ::mainException) {
+ async<Int>(Job() + Dispatchers.Main) { fail() }
+ }
}
diff --git a/kotlinx-coroutines-core/jvm/test/FailingCoroutinesMachineryTest.kt b/kotlinx-coroutines-core/jvm/test/FailingCoroutinesMachineryTest.kt
index c9f722a5..04b0ba54 100644
--- a/kotlinx-coroutines-core/jvm/test/FailingCoroutinesMachineryTest.kt
+++ b/kotlinx-coroutines-core/jvm/test/FailingCoroutinesMachineryTest.kt
@@ -33,7 +33,7 @@ class FailingCoroutinesMachineryTest(
private var caught: Throwable? = null
private val latch = CountDownLatch(1)
- private var exceptionHandler = CoroutineExceptionHandler { _, t -> caught = t;latch.countDown() }
+ private var exceptionHandler = CoroutineExceptionHandler { _, t -> caught = t; latch.countDown() }
private val lazyOuterDispatcher = lazy { newFixedThreadPoolContext(1, "") }
private object FailingUpdate : ThreadContextElement<Unit> {
@@ -115,14 +115,20 @@ class FailingCoroutinesMachineryTest(
@Test
fun testElement() = runTest {
- launch(NonCancellable + dispatcher.value + exceptionHandler + element) {}
+ // Top-level throwing dispatcher may rethrow an exception right here
+ runCatching {
+ launch(NonCancellable + dispatcher.value + exceptionHandler + element) {}
+ }
checkException()
}
@Test
fun testNestedElement() = runTest {
- launch(NonCancellable + dispatcher.value + exceptionHandler) {
- launch(element) { }
+ // Top-level throwing dispatcher may rethrow an exception right here
+ runCatching {
+ launch(NonCancellable + dispatcher.value + exceptionHandler) {
+ launch(element) { }
+ }
}
checkException()
}
diff --git a/kotlinx-coroutines-core/jvm/test/FieldWalker.kt b/kotlinx-coroutines-core/jvm/test/FieldWalker.kt
index e8079ebd..179b2e5e 100644
--- a/kotlinx-coroutines-core/jvm/test/FieldWalker.kt
+++ b/kotlinx-coroutines-core/jvm/test/FieldWalker.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines
@@ -11,7 +11,6 @@ import java.util.*
import java.util.Collections.*
import java.util.concurrent.atomic.*
import java.util.concurrent.locks.*
-import kotlin.collections.ArrayList
import kotlin.test.*
object FieldWalker {
@@ -56,7 +55,7 @@ object FieldWalker {
* Reflectively starts to walk through object graph and map to all the reached object to their path
* in from root. Use [showPath] do display a path if needed.
*/
- private fun walkRefs(root: Any?, rootStatics: Boolean): Map<Any, Ref> {
+ private fun walkRefs(root: Any?, rootStatics: Boolean): IdentityHashMap<Any, Ref> {
val visited = IdentityHashMap<Any, Ref>()
if (root == null) return visited
visited[root] = Ref.RootRef
@@ -90,6 +89,7 @@ object FieldWalker {
cur = ref.parent
path += "[${ref.index}]"
}
+ else -> error("Should not be reached")
}
}
path.reverse()
@@ -154,8 +154,9 @@ object FieldWalker {
while (true) {
val fields = type.declaredFields.filter {
!it.type.isPrimitive
- && (statics || !Modifier.isStatic(it.modifiers))
- && !(it.type.isArray && it.type.componentType.isPrimitive)
+ && (statics || !Modifier.isStatic(it.modifiers))
+ && !(it.type.isArray && it.type.componentType.isPrimitive)
+ && it.name != "previousOut" // System.out from TestBase that we store in a field to restore later
}
fields.forEach { it.isAccessible = true } // make them all accessible
result.addAll(fields)
diff --git a/kotlinx-coroutines-core/jvm/test/IntellijIdeaDebuggerEvaluatorCompatibilityTest.kt b/kotlinx-coroutines-core/jvm/test/IntellijIdeaDebuggerEvaluatorCompatibilityTest.kt
new file mode 100644
index 00000000..6bbfdd1b
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/IntellijIdeaDebuggerEvaluatorCompatibilityTest.kt
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines
+
+import org.junit.Test
+import kotlin.coroutines.*
+import kotlin.test.*
+
+class IntellijIdeaDebuggerEvaluatorCompatibilityTest {
+
+ /*
+ * This test verifies that our CoroutineScope is accessible to IDEA debugger.
+ *
+ * Consider the following scenario:
+ * ```
+ * runBlocking<Unit> { // this: CoroutineScope
+ * println("runBlocking")
+ * }
+ * ```
+ * user puts breakpoint to `println` line, opens "Evaluate" window
+ * and executes `launch { println("launch") }`. They (obviously) expect it to work, but
+ * it won't: `{}` in `runBlocking` is `SuspendLambda` and `this` is an unused implicit receiver
+ * that is removed by the compiler (because it's unused).
+ *
+ * But we still want to provide consistent user experience for functions with `CoroutineScope` receiver,
+ * for that IDEA debugger tries to retrieve the scope via `kotlin.coroutines.coroutineContext[Job] as? CoroutineScope`
+ * and with this test we're fixing this behaviour.
+ *
+ * Note that this behaviour is not carved in stone: IDEA fallbacks to `kotlin.coroutines.coroutineContext` for the context if necessary.
+ */
+
+ @Test
+ fun testScopeIsAccessible() = runBlocking<Unit> {
+ verify()
+
+ withContext(Job()) {
+ verify()
+ }
+
+ coroutineScope {
+ verify()
+ }
+
+ supervisorScope {
+ verify()
+ }
+
+ }
+
+ private suspend fun verify() {
+ val ctx = coroutineContext
+ assertTrue { ctx.job is CoroutineScope }
+ }
+}
diff --git a/kotlinx-coroutines-core/jvm/test/JoinStrTest.kt b/kotlinx-coroutines-core/jvm/test/JoinStressTest.kt
index 5090e7c0..6d474185 100644
--- a/kotlinx-coroutines-core/jvm/test/JoinStrTest.kt
+++ b/kotlinx-coroutines-core/jvm/test/JoinStressTest.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines
diff --git a/kotlinx-coroutines-core/jvm/test/LCStressOptionsDefault.kt b/kotlinx-coroutines-core/jvm/test/LCStressOptionsDefault.kt
deleted file mode 100644
index 62ded9f9..00000000
--- a/kotlinx-coroutines-core/jvm/test/LCStressOptionsDefault.kt
+++ /dev/null
@@ -1,20 +0,0 @@
-/*
- * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
- */
-package kotlinx.coroutines
-
-import org.jetbrains.kotlinx.lincheck.*
-import org.jetbrains.kotlinx.lincheck.strategy.stress.*
-import kotlin.reflect.*
-
-class LCStressOptionsDefault : StressOptions() {
- init {
- iterations(100 * stressTestMultiplierCbrt)
- invocationsPerIteration(1000 * stressTestMultiplierCbrt)
- actorsBefore(if (isStressTest) 3 else 0)
- threads(3)
- actorsPerThread(if (isStressTest) 3 else 2)
- }
-}
-
-fun Options<*,*>.check(testClass: KClass<*>) = LinChecker.check(testClass.java, this) \ No newline at end of file
diff --git a/kotlinx-coroutines-core/jvm/test/ReusableCancellableContinuationLeakStressTest.kt b/kotlinx-coroutines-core/jvm/test/ReusableCancellableContinuationLeakStressTest.kt
new file mode 100644
index 00000000..8a20e084
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/ReusableCancellableContinuationLeakStressTest.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines
+
+import kotlinx.coroutines.channels.*
+import org.junit.Test
+import kotlin.test.*
+
+class ReusableCancellableContinuationLeakStressTest : TestBase() {
+
+ @Suppress("UnnecessaryVariable")
+ private suspend fun <T : Any> ReceiveChannel<T>.receiveBatch(): T {
+ val r = receive() // DO NOT MERGE LINES, otherwise TCE will kick in
+ return r
+ }
+
+ private val iterations = 100_000 * stressTestMultiplier
+
+ class Leak(val i: Int)
+
+ @Test // Simplified version of #2564
+ fun testReusableContinuationLeak() = runTest {
+ val channel = produce(capacity = 1) { // from the main thread
+ (0 until iterations).forEach {
+ send(Leak(it))
+ }
+ }
+
+ launch(Dispatchers.Default) {
+ repeat (iterations) {
+ val value = channel.receiveBatch()
+ assertEquals(it, value.i)
+ }
+ (channel as Job).join()
+
+ FieldWalker.assertReachableCount(0, coroutineContext.job, false) { it is Leak }
+ }
+ }
+}
diff --git a/kotlinx-coroutines-core/jvm/test/ReusableCancellableContinuationTest.kt b/kotlinx-coroutines-core/jvm/test/ReusableCancellableContinuationTest.kt
index 56f1e283..06839f4a 100644
--- a/kotlinx-coroutines-core/jvm/test/ReusableCancellableContinuationTest.kt
+++ b/kotlinx-coroutines-core/jvm/test/ReusableCancellableContinuationTest.kt
@@ -39,7 +39,7 @@ class ReusableCancellableContinuationTest : TestBase() {
repeat(iterations) {
suspender {
- assertTrue(channel.offer(it))
+ assertTrue(channel.trySend(it).isSuccess)
}
}
channel.close()
diff --git a/kotlinx-coroutines-core/jvm/test/ReusableContinuationStressTest.kt b/kotlinx-coroutines-core/jvm/test/ReusableContinuationStressTest.kt
new file mode 100644
index 00000000..a256815d
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/ReusableContinuationStressTest.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines
+
+import kotlinx.coroutines.flow.*
+import org.junit.*
+
+class ReusableContinuationStressTest : TestBase() {
+
+ private val iterations = 1000 * stressTestMultiplierSqrt
+
+ @Test // Originally reported by @denis-bezrukov in #2736
+ fun testDebounceWithStateFlow() = runBlocking<Unit> {
+ withContext(Dispatchers.Default) {
+ repeat(iterations) {
+ launch { // <- load the dispatcher and OS scheduler
+ runStressTestOnce(1, 1)
+ }
+ }
+ }
+ }
+
+ private suspend fun runStressTestOnce(delay: Int, debounce: Int) = coroutineScope {
+ val stateFlow = MutableStateFlow(0)
+ val emitter = launch {
+ repeat(1000) { i ->
+ stateFlow.emit(i)
+ delay(delay.toLong())
+ }
+ }
+ var last = 0
+ stateFlow.debounce(debounce.toLong()).take(100).collect { i ->
+ if (i - last > 100) {
+ last = i
+ }
+ }
+ emitter.cancel()
+ }
+}
diff --git a/kotlinx-coroutines-core/jvm/test/RunBlockingTest.kt b/kotlinx-coroutines-core/jvm/test/RunBlockingTest.kt
index e20362ff..de38df6b 100644
--- a/kotlinx-coroutines-core/jvm/test/RunBlockingTest.kt
+++ b/kotlinx-coroutines-core/jvm/test/RunBlockingTest.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines
@@ -171,4 +171,15 @@ class RunBlockingTest : TestBase() {
}
rb.hashCode() // unused
}
+
+ @Test
+ fun testCancelledParent() {
+ val job = Job()
+ job.cancel()
+ assertFailsWith<CancellationException> {
+ runBlocking(job) {
+ expectUnreached()
+ }
+ }
+ }
}
diff --git a/kotlinx-coroutines-core/jvm/test/RunInterruptibleTest.kt b/kotlinx-coroutines-core/jvm/test/RunInterruptibleTest.kt
index e755b17d..49c93c7f 100644
--- a/kotlinx-coroutines-core/jvm/test/RunInterruptibleTest.kt
+++ b/kotlinx-coroutines-core/jvm/test/RunInterruptibleTest.kt
@@ -41,7 +41,7 @@ class RunInterruptibleTest : TestBase() {
val job = launch {
runInterruptible(Dispatchers.IO) {
expect(2)
- latch.offer(Unit)
+ latch.trySend(Unit)
try {
Thread.sleep(10_000L)
expectUnreached()
diff --git a/kotlinx-coroutines-core/jvm/test/TestBase.kt b/kotlinx-coroutines-core/jvm/test/TestBase.kt
index 17238e87..61a2c8b8 100644
--- a/kotlinx-coroutines-core/jvm/test/TestBase.kt
+++ b/kotlinx-coroutines-core/jvm/test/TestBase.kt
@@ -7,11 +7,10 @@ package kotlinx.coroutines
import kotlinx.coroutines.internal.*
import kotlinx.coroutines.scheduling.*
import org.junit.*
-import java.lang.Math.*
+import java.io.*
import java.util.*
import java.util.concurrent.atomic.*
import kotlin.coroutines.*
-import kotlin.math.*
import kotlin.test.*
private val VERBOSE = systemProp("test.verbose", false)
@@ -23,12 +22,15 @@ public actual val isStressTest = System.getProperty("stressTest")?.toBoolean() ?
public val stressTestMultiplierSqrt = if (isStressTest) 5 else 1
+private const val SHUTDOWN_TIMEOUT = 1_000L // 1s at most to wait per thread
+
/**
* Multiply various constants in stress tests by this factor, so that they run longer during nightly stress test.
*/
public actual val stressTestMultiplier = stressTestMultiplierSqrt * stressTestMultiplierSqrt
-public val stressTestMultiplierCbrt = cbrt(stressTestMultiplier.toDouble()).roundToInt()
+@Suppress("ACTUAL_WITHOUT_EXPECT")
+public actual typealias TestResult = Unit
/**
* Base class for tests, so that tests for predictable scheduling of actions in multiple coroutines sharing a single
@@ -49,7 +51,11 @@ public val stressTestMultiplierCbrt = cbrt(stressTestMultiplier.toDouble()).roun
* }
* ```
*/
-public actual open class TestBase actual constructor() {
+public actual open class TestBase(private var disableOutCheck: Boolean) {
+
+ actual constructor(): this(false)
+
+ public actual val isBoundByJsTestTimeout = false
private var actionIndex = AtomicInteger()
private var finished = AtomicBoolean()
private var error = AtomicReference<Throwable>()
@@ -58,9 +64,15 @@ public actual open class TestBase actual constructor() {
private lateinit var threadsBefore: Set<Thread>
private val uncaughtExceptions = Collections.synchronizedList(ArrayList<Throwable>())
private var originalUncaughtExceptionHandler: Thread.UncaughtExceptionHandler? = null
- private val SHUTDOWN_TIMEOUT = 1_000L // 1s at most to wait per thread
+ /*
+ * System.out that we redefine in order to catch any debugging/diagnostics
+ * 'println' from main source set.
+ * NB: We do rely on the name 'previousOut' in the FieldWalker in order to skip its
+ * processing
+ */
+ private lateinit var previousOut: PrintStream
- /**
+ /**
* Throws [IllegalStateException] like `error` in stdlib, but also ensures that the test will not
* complete successfully even if this exception is consumed somewhere in the test.
*/
@@ -113,7 +125,7 @@ public actual open class TestBase actual constructor() {
}
/**
- * Asserts that this it the last action in the test. It must be invoked by any test that used [expect].
+ * Asserts that this is the last action in the test. It must be invoked by any test that used [expect].
*/
public actual fun finish(index: Int) {
expect(index)
@@ -133,6 +145,16 @@ public actual open class TestBase actual constructor() {
finished.set(false)
}
+ private object TestOutputStream : PrintStream(object : OutputStream() {
+ override fun write(b: Int) {
+ error("Detected unexpected call to 'println' from source code")
+ }
+ })
+
+ fun println(message: Any?) {
+ previousOut.println(message)
+ }
+
@Before
fun before() {
initPoolsBeforeTest()
@@ -143,6 +165,10 @@ public actual open class TestBase actual constructor() {
e.printStackTrace()
uncaughtExceptions.add(e)
}
+ if (!disableOutCheck) {
+ previousOut = System.out
+ System.setOut(TestOutputStream)
+ }
}
@After
@@ -154,7 +180,7 @@ public actual open class TestBase actual constructor() {
}
// Shutdown all thread pools
shutdownPoolsAfterTest()
- // Check that that are now leftover threads
+ // Check that are now leftover threads
runCatching {
checkTestThreads(threadsBefore)
}.onFailure {
@@ -162,6 +188,9 @@ public actual open class TestBase actual constructor() {
}
// Restore original uncaught exception handler
Thread.setDefaultUncaughtExceptionHandler(originalUncaughtExceptionHandler)
+ if (!disableOutCheck) {
+ System.setOut(previousOut)
+ }
if (uncaughtExceptions.isNotEmpty()) {
makeError("Expected no uncaught exceptions, but got $uncaughtExceptions")
}
@@ -187,7 +216,7 @@ public actual open class TestBase actual constructor() {
expected: ((Throwable) -> Boolean)? = null,
unhandled: List<(Throwable) -> Boolean> = emptyList(),
block: suspend CoroutineScope.() -> Unit
- ) {
+ ): TestResult {
var exCount = 0
var ex: Throwable? = null
try {
diff --git a/kotlinx-coroutines-core/jvm/test/ThreadContextElementRestoreTest.kt b/kotlinx-coroutines-core/jvm/test/ThreadContextElementRestoreTest.kt
new file mode 100644
index 00000000..e2ab4d72
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/ThreadContextElementRestoreTest.kt
@@ -0,0 +1,198 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines
+
+import org.junit.Test
+import kotlin.coroutines.*
+import kotlin.test.*
+
+class ThreadContextElementRestoreTest : TestBase() {
+ private val tl = ThreadLocal<String?>()
+
+ // Checks that ThreadLocal context is properly restored after executing the given block inside
+ // withContext(tl.asContextElement("OK")) code running in different outer contexts
+ private inline fun check(crossinline block: suspend () -> Unit) = runTest {
+ val mainDispatcher = coroutineContext[ContinuationInterceptor] as CoroutineDispatcher
+ // Scenario #1: withContext(ThreadLocal) direct from runTest
+ withContext(tl.asContextElement("OK")) {
+ block()
+ assertEquals("OK", tl.get())
+ }
+ assertEquals(null, tl.get())
+ // Scenario #2: withContext(ThreadLocal) from coroutineScope
+ coroutineScope {
+ withContext(tl.asContextElement("OK")) {
+ block()
+ assertEquals("OK", tl.get())
+ }
+ assertEquals(null, tl.get())
+ }
+ // Scenario #3: withContext(ThreadLocal) from undispatched withContext
+ withContext(CoroutineName("NAME")) {
+ withContext(tl.asContextElement("OK")) {
+ block()
+ assertEquals("OK", tl.get())
+ }
+ assertEquals(null, tl.get())
+ }
+ // Scenario #4: withContext(ThreadLocal) from dispatched withContext
+ withContext(wrapperDispatcher()) {
+ withContext(tl.asContextElement("OK")) {
+ block()
+ assertEquals("OK", tl.get())
+ }
+ assertEquals(null, tl.get())
+ }
+ // Scenario #5: withContext(ThreadLocal) from withContext(ThreadLocal)
+ withContext(tl.asContextElement(null)) {
+ withContext(tl.asContextElement("OK")) {
+ block()
+ assertEquals("OK", tl.get())
+ }
+ assertEquals(null, tl.get())
+ }
+ // Scenario #6: withContext(ThreadLocal) from withTimeout
+ withTimeout(1000) {
+ withContext(tl.asContextElement("OK")) {
+ block()
+ assertEquals("OK", tl.get())
+ }
+ assertEquals(null, tl.get())
+ }
+ // Scenario #7: withContext(ThreadLocal) from withContext(Unconfined)
+ withContext(Dispatchers.Unconfined) {
+ withContext(tl.asContextElement("OK")) {
+ block()
+ assertEquals("OK", tl.get())
+ }
+ assertEquals(null, tl.get())
+ }
+ // Scenario #8: withContext(ThreadLocal) from withContext(Default)
+ withContext(Dispatchers.Default) {
+ withContext(tl.asContextElement("OK")) {
+ block()
+ assertEquals("OK", tl.get())
+ }
+ assertEquals(null, tl.get())
+ }
+ // Scenario #9: withContext(ThreadLocal) from withContext(mainDispatcher)
+ withContext(mainDispatcher) {
+ withContext(tl.asContextElement("OK")) {
+ block()
+ assertEquals("OK", tl.get())
+ }
+ assertEquals(null, tl.get())
+ }
+ }
+
+ @Test
+ fun testSimpleNoSuspend() =
+ check {}
+
+ @Test
+ fun testSimpleDelay() = check {
+ delay(1)
+ }
+
+ @Test
+ fun testSimpleYield() = check {
+ yield()
+ }
+
+ private suspend fun deepDelay() {
+ deepDelay2(); deepDelay2()
+ }
+
+ private suspend fun deepDelay2() {
+ delay(1); delay(1)
+ }
+
+ @Test
+ fun testDeepDelay() = check {
+ deepDelay()
+ }
+
+ private suspend fun deepYield() {
+ deepYield2(); deepYield2()
+ }
+
+ private suspend fun deepYield2() {
+ yield(); yield()
+ }
+
+ @Test
+ fun testDeepYield() = check {
+ deepYield()
+ }
+
+ @Test
+ fun testCoroutineScopeDelay() = check {
+ coroutineScope {
+ delay(1)
+ }
+ }
+
+ @Test
+ fun testCoroutineScopeYield() = check {
+ coroutineScope {
+ yield()
+ }
+ }
+
+ @Test
+ fun testWithContextUndispatchedDelay() = check {
+ withContext(CoroutineName("INNER")) {
+ delay(1)
+ }
+ }
+
+ @Test
+ fun testWithContextUndispatchedYield() = check {
+ withContext(CoroutineName("INNER")) {
+ yield()
+ }
+ }
+
+ @Test
+ fun testWithContextDispatchedDelay() = check {
+ withContext(wrapperDispatcher()) {
+ delay(1)
+ }
+ }
+
+ @Test
+ fun testWithContextDispatchedYield() = check {
+ withContext(wrapperDispatcher()) {
+ yield()
+ }
+ }
+
+ @Test
+ fun testWithTimeoutDelay() = check {
+ withTimeout(1000) {
+ delay(1)
+ }
+ }
+
+ @Test
+ fun testWithTimeoutYield() = check {
+ withTimeout(1000) {
+ yield()
+ }
+ }
+
+ @Test
+ fun testWithUnconfinedContextDelay() = check {
+ withContext(Dispatchers.Unconfined) {
+ delay(1)
+ }
+ }
+ @Test
+ fun testWithUnconfinedContextYield() = check {
+ withContext(Dispatchers.Unconfined) {
+ yield()
+ }
+ }
+}
diff --git a/kotlinx-coroutines-core/jvm/test/ThreadContextOrderTest.kt b/kotlinx-coroutines-core/jvm/test/ThreadContextOrderTest.kt
new file mode 100644
index 00000000..49f4a12e
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/ThreadContextOrderTest.kt
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines
+
+import kotlinx.coroutines.internal.*
+import org.junit.Test
+import kotlin.coroutines.*
+import kotlin.test.*
+
+class ThreadContextOrderTest : TestBase() {
+ /*
+ * The test verifies that two thread context elements are correctly nested:
+ * The restoration order is the reverse of update order.
+ */
+ private val transactionalContext = ThreadLocal<String>()
+ private val loggingContext = ThreadLocal<String>()
+
+ private val transactionalElement = object : ThreadContextElement<String> {
+ override val key = ThreadLocalKey(transactionalContext)
+
+ override fun updateThreadContext(context: CoroutineContext): String {
+ assertEquals("test", loggingContext.get())
+ val previous = transactionalContext.get()
+ transactionalContext.set("tr coroutine")
+ return previous
+ }
+
+ override fun restoreThreadContext(context: CoroutineContext, oldState: String) {
+ assertEquals("test", loggingContext.get())
+ assertEquals("tr coroutine", transactionalContext.get())
+ transactionalContext.set(oldState)
+ }
+ }
+
+ private val loggingElement = object : ThreadContextElement<String> {
+ override val key = ThreadLocalKey(loggingContext)
+
+ override fun updateThreadContext(context: CoroutineContext): String {
+ val previous = loggingContext.get()
+ loggingContext.set("log coroutine")
+ return previous
+ }
+
+ override fun restoreThreadContext(context: CoroutineContext, oldState: String) {
+ assertEquals("log coroutine", loggingContext.get())
+ assertEquals("tr coroutine", transactionalContext.get())
+ loggingContext.set(oldState)
+ }
+ }
+
+ @Test
+ fun testCorrectOrder() = runTest {
+ transactionalContext.set("test")
+ loggingContext.set("test")
+ launch(transactionalElement + loggingElement) {
+ assertEquals("log coroutine", loggingContext.get())
+ assertEquals("tr coroutine", transactionalContext.get())
+ }
+ assertEquals("test", loggingContext.get())
+ assertEquals("test", transactionalContext.get())
+
+ }
+}
diff --git a/kotlinx-coroutines-core/jvm/test/VirtualTimeSource.kt b/kotlinx-coroutines-core/jvm/test/VirtualTimeSource.kt
index ca399f53..bd9a185f 100644
--- a/kotlinx-coroutines-core/jvm/test/VirtualTimeSource.kt
+++ b/kotlinx-coroutines-core/jvm/test/VirtualTimeSource.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines
@@ -42,7 +42,7 @@ private const val REAL_PARK_NANOS = 10_000_000L // 10 ms -- park for a little to
@Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
internal class VirtualTimeSource(
private val log: PrintStream?
-) : TimeSource {
+) : AbstractTimeSource() {
private val mainThread: Thread = Thread.currentThread()
private var checkpointNanos: Long = System.nanoTime()
@@ -142,7 +142,7 @@ internal class VirtualTimeSource(
}
private fun minParkedTill(): Long =
- threads.values.map { if (it.permit) NOT_PARKED else it.parkedTill }.min() ?: NOT_PARKED
+ threads.values.map { if (it.permit) NOT_PARKED else it.parkedTill }.minOrNull() ?: NOT_PARKED
@Synchronized
fun shutdown() {
diff --git a/kotlinx-coroutines-core/jvm/test/channels/ActorLazyTest.kt b/kotlinx-coroutines-core/jvm/test/channels/ActorLazyTest.kt
index ae95e694..d3b2ff12 100644
--- a/kotlinx-coroutines-core/jvm/test/channels/ActorLazyTest.kt
+++ b/kotlinx-coroutines-core/jvm/test/channels/ActorLazyTest.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.channels
@@ -78,4 +78,14 @@ class ActorLazyTest : TestBase() {
job.join()
finish(5)
}
-} \ No newline at end of file
+
+ @Test
+ fun testCancelledParent() = runTest({ it is CancellationException }) {
+ cancel()
+ expect(1)
+ actor<Int>(start = CoroutineStart.LAZY) {
+ expectUnreached()
+ }
+ finish(2)
+ }
+}
diff --git a/kotlinx-coroutines-core/jvm/test/channels/ActorTest.kt b/kotlinx-coroutines-core/jvm/test/channels/ActorTest.kt
index bdca5039..5a2778d5 100644
--- a/kotlinx-coroutines-core/jvm/test/channels/ActorTest.kt
+++ b/kotlinx-coroutines-core/jvm/test/channels/ActorTest.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.channels
@@ -69,11 +69,11 @@ class ActorTest(private val capacity: Int) : TestBase() {
@Test
fun testCloseWithoutCause() = runTest {
val actor = actor<Int>(capacity = capacity) {
- val element = channel.receiveOrNull()
+ val element = channel.receive()
expect(2)
assertEquals(42, element)
- val next = channel.receiveOrNull()
- assertNull(next)
+ val next = channel.receiveCatching()
+ assertNull(next.exceptionOrNull())
expect(3)
}
@@ -88,11 +88,11 @@ class ActorTest(private val capacity: Int) : TestBase() {
@Test
fun testCloseWithCause() = runTest {
val actor = actor<Int>(capacity = capacity) {
- val element = channel.receiveOrNull()
+ val element = channel.receive()
expect(2)
- require(element!! == 42)
+ require(element == 42)
try {
- channel.receiveOrNull()
+ channel.receive()
} catch (e: IOException) {
expect(3)
}
@@ -111,7 +111,7 @@ class ActorTest(private val capacity: Int) : TestBase() {
val job = async {
actor<Int>(capacity = capacity) {
expect(1)
- channel.receiveOrNull()
+ channel.receive()
expectUnreached()
}
}
@@ -173,11 +173,24 @@ class ActorTest(private val capacity: Int) : TestBase() {
fun testCloseFreshActor() = runTest {
for (start in CoroutineStart.values()) {
val job = launch {
- val actor = actor<Int>(start = start) { for (i in channel) {} }
+ val actor = actor<Int>(start = start) {
+ for (i in channel) {
+ }
+ }
actor.close()
}
job.join()
}
}
+
+ @Test
+ fun testCancelledParent() = runTest({ it is CancellationException }) {
+ cancel()
+ expect(1)
+ actor<Int> {
+ expectUnreached()
+ }
+ finish(2)
+ }
}
diff --git a/kotlinx-coroutines-core/jvm/test/channels/BroadcastChannelMultiReceiveStressTest.kt b/kotlinx-coroutines-core/jvm/test/channels/BroadcastChannelMultiReceiveStressTest.kt
index 2e73b243..8c9777b4 100644
--- a/kotlinx-coroutines-core/jvm/test/channels/BroadcastChannelMultiReceiveStressTest.kt
+++ b/kotlinx-coroutines-core/jvm/test/channels/BroadcastChannelMultiReceiveStressTest.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.channels
@@ -67,10 +67,10 @@ class BroadcastChannelMultiReceiveStressTest(
val channel = broadcast.openSubscription()
when (receiverIndex % 5) {
0 -> doReceive(channel, receiverIndex)
- 1 -> doReceiveOrNull(channel, receiverIndex)
+ 1 -> doReceiveCatching(channel, receiverIndex)
2 -> doIterator(channel, receiverIndex)
3 -> doReceiveSelect(channel, receiverIndex)
- 4 -> doReceiveSelectOrNull(channel, receiverIndex)
+ 4 -> doReceiveCatchingSelect(channel, receiverIndex)
}
channel.cancel()
}
@@ -124,9 +124,9 @@ class BroadcastChannelMultiReceiveStressTest(
}
}
- private suspend fun doReceiveOrNull(channel: ReceiveChannel<Long>, receiverIndex: Int) {
+ private suspend fun doReceiveCatching(channel: ReceiveChannel<Long>, receiverIndex: Int) {
while (true) {
- val stop = doReceived(receiverIndex, channel.receiveOrNull() ?: break)
+ val stop = doReceived(receiverIndex, channel.receiveCatching().getOrNull() ?: break)
if (stop) break
}
}
@@ -148,11 +148,11 @@ class BroadcastChannelMultiReceiveStressTest(
}
}
- private suspend fun doReceiveSelectOrNull(channel: ReceiveChannel<Long>, receiverIndex: Int) {
+ private suspend fun doReceiveCatchingSelect(channel: ReceiveChannel<Long>, receiverIndex: Int) {
while (true) {
- val event = select<Long?> { channel.onReceiveOrNull { it } } ?: break
+ val event = select<Long?> { channel.onReceiveCatching { it.getOrNull() } } ?: break
val stop = doReceived(receiverIndex, event)
if (stop) break
}
}
-} \ No newline at end of file
+}
diff --git a/kotlinx-coroutines-core/jvm/test/channels/ChannelCancelUndeliveredElementStressTest.kt b/kotlinx-coroutines-core/jvm/test/channels/ChannelCancelUndeliveredElementStressTest.kt
index 76713aa1..86adfee0 100644
--- a/kotlinx-coroutines-core/jvm/test/channels/ChannelCancelUndeliveredElementStressTest.kt
+++ b/kotlinx-coroutines-core/jvm/test/channels/ChannelCancelUndeliveredElementStressTest.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.channels
@@ -15,7 +15,7 @@ class ChannelCancelUndeliveredElementStressTest : TestBase() {
// total counters
private var sendCnt = 0
- private var offerFailedCnt = 0
+ private var trySendFailedCnt = 0
private var receivedCnt = 0
private var undeliveredCnt = 0
@@ -23,7 +23,7 @@ class ChannelCancelUndeliveredElementStressTest : TestBase() {
private var lastReceived = 0
private var dSendCnt = 0
private var dSendExceptionCnt = 0
- private var dOfferFailedCnt = 0
+ private var dTrySendFailedCnt = 0
private var dReceivedCnt = 0
private val dUndeliveredCnt = AtomicInteger()
@@ -43,30 +43,30 @@ class ChannelCancelUndeliveredElementStressTest : TestBase() {
joinAll(j1, j2)
// All elements must be either received or undelivered (IN every run)
- if (dSendCnt - dOfferFailedCnt != dReceivedCnt + dUndeliveredCnt.get()) {
+ if (dSendCnt - dTrySendFailedCnt != dReceivedCnt + dUndeliveredCnt.get()) {
println(" Send: $dSendCnt")
- println("Send Exception: $dSendExceptionCnt")
- println(" Offer failed: $dOfferFailedCnt")
+ println("Send exception: $dSendExceptionCnt")
+ println("trySend failed: $dTrySendFailedCnt")
println(" Received: $dReceivedCnt")
println(" Undelivered: ${dUndeliveredCnt.get()}")
error("Failed")
}
- offerFailedCnt += dOfferFailedCnt
+ trySendFailedCnt += dTrySendFailedCnt
receivedCnt += dReceivedCnt
undeliveredCnt += dUndeliveredCnt.get()
// clear for next run
dSendCnt = 0
dSendExceptionCnt = 0
- dOfferFailedCnt = 0
+ dTrySendFailedCnt = 0
dReceivedCnt = 0
dUndeliveredCnt.set(0)
}
// Stats
- println(" Send: $sendCnt")
- println(" Offer failed: $offerFailedCnt")
- println(" Received: $receivedCnt")
- println(" Undelivered: $undeliveredCnt")
- assertEquals(sendCnt - offerFailedCnt, receivedCnt + undeliveredCnt)
+ println(" Send: $sendCnt")
+ println("trySend failed: $trySendFailedCnt")
+ println(" Received: $receivedCnt")
+ println(" Undelivered: $undeliveredCnt")
+ assertEquals(sendCnt - trySendFailedCnt, receivedCnt + undeliveredCnt)
}
private suspend fun sendOne(channel: Channel<Int>) {
@@ -75,11 +75,11 @@ class ChannelCancelUndeliveredElementStressTest : TestBase() {
try {
when (Random.nextInt(2)) {
0 -> channel.send(i)
- 1 -> if (!channel.offer(i)) {
- dOfferFailedCnt++
+ 1 -> if (!channel.trySend(i).isSuccess) {
+ dTrySendFailedCnt++
}
}
- } catch(e: Throwable) {
+ } catch (e: Throwable) {
assertTrue(e is CancellationException) // the only exception possible in this test
dSendExceptionCnt++
throw e
@@ -89,7 +89,7 @@ class ChannelCancelUndeliveredElementStressTest : TestBase() {
private suspend fun receiveOne(channel: Channel<Int>) {
val received = when (Random.nextInt(3)) {
0 -> channel.receive()
- 1 -> channel.receiveOrNull() ?: error("Cannot be closed yet")
+ 1 -> channel.receiveCatching().getOrElse { error("Cannot be closed yet") }
2 -> select {
channel.onReceive { it }
}
@@ -99,4 +99,4 @@ class ChannelCancelUndeliveredElementStressTest : TestBase() {
dReceivedCnt++
lastReceived = received
}
-} \ No newline at end of file
+}
diff --git a/kotlinx-coroutines-core/jvm/test/channels/ChannelLFStressTest.kt b/kotlinx-coroutines-core/jvm/test/channels/ChannelLFStressTest.kt
deleted file mode 100644
index 256ef621..00000000
--- a/kotlinx-coroutines-core/jvm/test/channels/ChannelLFStressTest.kt
+++ /dev/null
@@ -1,107 +0,0 @@
-/*
- * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
- */
-
-package kotlinx.coroutines.channels
-
-import kotlinx.atomicfu.*
-import kotlinx.coroutines.*
-import java.util.concurrent.atomic.AtomicLong
-import java.util.concurrent.atomic.AtomicLongArray
-import kotlin.math.*
-import kotlin.test.*
-
-/**
- * Tests lock-freedom of send and receive operations on rendezvous and conflated channels.
- * There is a single channel with two sender and two receiver threads.
- * When one sender or receiver gets suspended at most one other operation is allowed to cease having progress
- * (`allowSuspendedThreads = 1`).
- *
- * **Note**: In the current implementation buffered channels are not lock-free, so this test would fail
- * if channel is created with a buffer.
- */
-class ChannelLFStressTest : TestBase() {
- private val nSeconds = 5 * stressTestMultiplier
- private val env = LockFreedomTestEnvironment("ChannelLFStressTest", allowSuspendedThreads = 1)
- private lateinit var channel: Channel<Long>
-
- private val sendIndex = AtomicLong()
- private val receiveCount = AtomicLong()
- private val duplicateCount = AtomicLong()
-
- private val nCheckedSize = 10_000_000
- private val nChecked = (nCheckedSize * Long.SIZE_BITS).toLong()
- private val receivedBits = AtomicLongArray(nCheckedSize) // bit set of received values
-
- @Test
- fun testRendezvousLockFreedom() {
- channel = Channel()
- performLockFreedomTest()
- // ensure that all sent were received
- checkAllReceived()
- }
-
- private fun performLockFreedomTest() {
- env.onCompletion {
- // We must cancel the channel to abort both senders & receivers
- channel.cancel(TestCompleted())
- }
- repeat(2) { env.testThread("sender-$it") { sender() } }
- repeat(2) { env.testThread("receiver-$it") { receiver() } }
- env.performTest(nSeconds) {
- println("Sent: $sendIndex, Received: $receiveCount, dups: $duplicateCount")
- }
- // ensure no duplicates
- assertEquals(0L, duplicateCount.get())
- }
-
- private fun checkAllReceived() {
- for (i in 0 until min(sendIndex.get(), nChecked)) {
- assertTrue(isReceived(i))
- }
- }
-
- private suspend fun sender() {
- val value = sendIndex.getAndIncrement()
- try {
- channel.send(value)
- } catch (e: TestCompleted) {
- check(env.isCompleted) // expected when test was completed
- markReceived(value) // fake received (actually failed to send)
- }
- }
-
- private suspend fun receiver() {
- val value = try {
- channel.receive()
- } catch (e: TestCompleted) {
- check(env.isCompleted) // expected when test was completed
- return
- }
- receiveCount.incrementAndGet()
- markReceived(value)
- }
-
- private fun markReceived(value: Long) {
- if (value >= nChecked) return // too big
- val index = (value / Long.SIZE_BITS).toInt()
- val mask = 1L shl (value % Long.SIZE_BITS).toInt()
- while (true) {
- val bits = receivedBits.get(index)
- if (bits and mask != 0L) {
- duplicateCount.incrementAndGet()
- break
- }
- if (receivedBits.compareAndSet(index, bits, bits or mask)) break
- }
- }
-
- private fun isReceived(value: Long): Boolean {
- val index = (value / Long.SIZE_BITS).toInt()
- val mask = 1L shl (value % Long.SIZE_BITS).toInt()
- val bits = receivedBits.get(index)
- return bits and mask != 0L
- }
-
- private class TestCompleted : CancellationException()
-}
diff --git a/kotlinx-coroutines-core/jvm/test/channels/ChannelSendReceiveStressTest.kt b/kotlinx-coroutines-core/jvm/test/channels/ChannelSendReceiveStressTest.kt
index f414c333..a6345cc5 100644
--- a/kotlinx-coroutines-core/jvm/test/channels/ChannelSendReceiveStressTest.kt
+++ b/kotlinx-coroutines-core/jvm/test/channels/ChannelSendReceiveStressTest.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.channels
@@ -60,10 +60,10 @@ class ChannelSendReceiveStressTest(
launch(pool + CoroutineName("receiver$receiverIndex")) {
when (receiverIndex % 5) {
0 -> doReceive(receiverIndex)
- 1 -> doReceiveOrNull(receiverIndex)
+ 1 -> doReceiveCatching(receiverIndex)
2 -> doIterator(receiverIndex)
3 -> doReceiveSelect(receiverIndex)
- 4 -> doReceiveSelectOrNull(receiverIndex)
+ 4 -> doReceiveCatchingSelect(receiverIndex)
}
receiversCompleted.incrementAndGet()
}
@@ -152,9 +152,9 @@ class ChannelSendReceiveStressTest(
}
}
- private suspend fun doReceiveOrNull(receiverIndex: Int) {
+ private suspend fun doReceiveCatching(receiverIndex: Int) {
while (true) {
- doReceived(receiverIndex, channel.receiveOrNull() ?: break)
+ doReceived(receiverIndex, channel.receiveCatching().getOrNull() ?: break)
}
}
@@ -173,10 +173,10 @@ class ChannelSendReceiveStressTest(
}
}
- private suspend fun doReceiveSelectOrNull(receiverIndex: Int) {
+ private suspend fun doReceiveCatchingSelect(receiverIndex: Int) {
while (true) {
- val event = select<Int?> { channel.onReceiveOrNull { it } } ?: break
+ val event = select<Int?> { channel.onReceiveCatching { it.getOrNull() } } ?: break
doReceived(receiverIndex, event)
}
}
-} \ No newline at end of file
+}
diff --git a/kotlinx-coroutines-core/jvm/test/channels/ChannelUndeliveredElementStressTest.kt b/kotlinx-coroutines-core/jvm/test/channels/ChannelUndeliveredElementStressTest.kt
index 1188329a..12334326 100644
--- a/kotlinx-coroutines-core/jvm/test/channels/ChannelUndeliveredElementStressTest.kt
+++ b/kotlinx-coroutines-core/jvm/test/channels/ChannelUndeliveredElementStressTest.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.channels
@@ -68,7 +68,7 @@ class ChannelUndeliveredElementStressTest(private val kind: TestChannelKind) : T
try {
block()
} finally {
- if (!done.offer(true))
+ if (!done.trySend(true).isSuccess)
error(IllegalStateException("failed to offer to done channel"))
}
}
@@ -188,9 +188,9 @@ class ChannelUndeliveredElementStressTest(private val kind: TestChannelKind) : T
val receivedData = when (receiveMode) {
1 -> channel.receive()
2 -> select { channel.onReceive { it } }
- 3 -> channel.receiveOrNull() ?: error("Should not be closed")
- 4 -> select { channel.onReceiveOrNull { it ?: error("Should not be closed") } }
- 5 -> channel.receiveOrClosed().value
+ 3 -> channel.receiveCatching().getOrElse { error("Should not be closed") }
+ 4 -> select { channel.onReceiveCatching { it.getOrElse { error("Should not be closed") } } }
+ 5 -> channel.receiveCatching().getOrThrow()
6 -> {
val iterator = channel.iterator()
check(iterator.hasNext()) { "Should not be closed" }
diff --git a/kotlinx-coroutines-core/jvm/test/channels/ChannelsJvmTest.kt b/kotlinx-coroutines-core/jvm/test/channels/ChannelsJvmTest.kt
index da20f0c5..8512aebc 100644
--- a/kotlinx-coroutines-core/jvm/test/channels/ChannelsJvmTest.kt
+++ b/kotlinx-coroutines-core/jvm/test/channels/ChannelsJvmTest.kt
@@ -11,7 +11,7 @@ import kotlin.test.*
class ChannelsJvmTest : TestBase() {
@Test
- fun testBlocking() {
+ fun testTrySendBlocking() {
val ch = Channel<Int>()
val sum = GlobalScope.async {
var sum = 0
@@ -19,9 +19,36 @@ class ChannelsJvmTest : TestBase() {
sum
}
repeat(10) {
- ch.sendBlocking(it)
+ assertTrue(ch.trySendBlocking(it).isSuccess)
}
ch.close()
assertEquals(45, runBlocking { sum.await() })
}
+
+ @Test
+ fun testTrySendBlockingClosedChannel() {
+ run {
+ val channel = Channel<Unit>().also { it.close() }
+ channel.trySendBlocking(Unit)
+ .onSuccess { expectUnreached() }
+ .onFailure { assertTrue(it is ClosedSendChannelException) }
+ .also { assertTrue { it.isClosed } }
+ }
+
+ run {
+ val channel = Channel<Unit>().also { it.close(TestException()) }
+ channel.trySendBlocking(Unit)
+ .onSuccess { expectUnreached() }
+ .onFailure { assertTrue(it is TestException) }
+ .also { assertTrue { it.isClosed } }
+ }
+
+ run {
+ val channel = Channel<Unit>().also { it.cancel(TestCancellationException()) }
+ channel.trySendBlocking(Unit)
+ .onSuccess { expectUnreached() }
+ .onFailure { assertTrue(it is TestCancellationException) }
+ .also { assertTrue { it.isClosed } }
+ }
+ }
}
diff --git a/kotlinx-coroutines-core/jvm/test/channels/ConflatedBroadcastChannelNotifyStressTest.kt b/kotlinx-coroutines-core/jvm/test/channels/ConflatedBroadcastChannelNotifyStressTest.kt
index eb7be575..2b3c05bc 100644
--- a/kotlinx-coroutines-core/jvm/test/channels/ConflatedBroadcastChannelNotifyStressTest.kt
+++ b/kotlinx-coroutines-core/jvm/test/channels/ConflatedBroadcastChannelNotifyStressTest.kt
@@ -29,7 +29,7 @@ class ConflatedBroadcastChannelNotifyStressTest : TestBase() {
launch(Dispatchers.Default + CoroutineName("Sender$senderId")) {
repeat(nEvents) { i ->
if (i % nSenders == senderId) {
- broadcast.offer(i)
+ broadcast.trySend(i)
sentTotal.incrementAndGet()
yield()
}
@@ -63,7 +63,7 @@ class ConflatedBroadcastChannelNotifyStressTest : TestBase() {
try {
withTimeout(timeLimit) {
senders.forEach { it.join() }
- broadcast.offer(nEvents) // last event to signal receivers termination
+ broadcast.trySend(nEvents) // last event to signal receivers termination
receivers.forEach { it.join() }
}
} catch (e: CancellationException) {
@@ -86,4 +86,4 @@ class ConflatedBroadcastChannelNotifyStressTest : TestBase() {
cancel()
value
}
-} \ No newline at end of file
+}
diff --git a/kotlinx-coroutines-core/jvm/test/channels/ConflatedChannelCloseStressTest.kt b/kotlinx-coroutines-core/jvm/test/channels/ConflatedChannelCloseStressTest.kt
index 316b3785..793d7e44 100644
--- a/kotlinx-coroutines-core/jvm/test/channels/ConflatedChannelCloseStressTest.kt
+++ b/kotlinx-coroutines-core/jvm/test/channels/ConflatedChannelCloseStressTest.kt
@@ -1,15 +1,12 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.channels
import kotlinx.coroutines.*
-import org.junit.After
-import org.junit.Test
-import java.util.concurrent.atomic.AtomicInteger
-import java.util.concurrent.atomic.AtomicReference
-import kotlin.coroutines.*
+import org.junit.*
+import java.util.concurrent.atomic.*
class ConflatedChannelCloseStressTest : TestBase() {
@@ -37,12 +34,9 @@ class ConflatedChannelCloseStressTest : TestBase() {
var x = senderId
try {
while (isActive) {
- try {
- curChannel.get().offer(x)
+ curChannel.get().trySend(x).onSuccess {
x += nSenders
sent.incrementAndGet()
- } catch (e: ClosedSendChannelException) {
- // ignore
}
}
} finally {
@@ -64,7 +58,9 @@ class ConflatedChannelCloseStressTest : TestBase() {
}
val receiver = async(pool + NonCancellable) {
while (isActive) {
- curChannel.get().receiveOrNull()
+ curChannel.get().receiveCatching().getOrElse {
+ it?.let { throw it }
+ }
received.incrementAndGet()
}
}
@@ -110,4 +106,4 @@ class ConflatedChannelCloseStressTest : TestBase() {
}
class StopException : Exception()
-} \ No newline at end of file
+}
diff --git a/kotlinx-coroutines-core/jvm/test/channels/TickerChannelCommonTest.kt b/kotlinx-coroutines-core/jvm/test/channels/TickerChannelCommonTest.kt
index 51789078..fbc28a18 100644
--- a/kotlinx-coroutines-core/jvm/test/channels/TickerChannelCommonTest.kt
+++ b/kotlinx-coroutines-core/jvm/test/channels/TickerChannelCommonTest.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.channels
@@ -48,7 +48,7 @@ class TickerChannelCommonTest(private val channelFactory: Channel) : TestBase()
delayChannel.cancel()
delay(5100)
- assertFailsWith<CancellationException> { delayChannel.poll() }
+ assertFailsWith<CancellationException> { delayChannel.tryReceive().getOrThrow() }
}
}
@@ -112,13 +112,13 @@ class TickerChannelCommonTest(private val channelFactory: Channel) : TestBase()
var sum = 0
var n = 0
whileSelect {
- this@averageInTimeWindow.onReceiveOrClosed {
+ this@averageInTimeWindow.onReceiveCatching {
if (it.isClosed) {
// Send leftovers and bail out
if (n != 0) send(sum / n.toDouble())
false
} else {
- sum += it.value
+ sum += it.getOrThrow()
++n
true
}
@@ -159,9 +159,9 @@ class TickerChannelCommonTest(private val channelFactory: Channel) : TestBase()
}
}
-fun ReceiveChannel<Unit>.checkEmpty() = assertNull(poll())
+fun ReceiveChannel<Unit>.checkEmpty() = assertNull(tryReceive().getOrNull())
fun ReceiveChannel<Unit>.checkNotEmpty() {
- assertNotNull(poll())
- assertNull(poll())
+ assertNotNull(tryReceive().getOrNull())
+ assertNull(tryReceive().getOrNull())
}
diff --git a/kotlinx-coroutines-core/jvm/test/exceptions/StackTraceRecoveryChannelsTest.kt b/kotlinx-coroutines-core/jvm/test/exceptions/StackTraceRecoveryChannelsTest.kt
index f52f8b5b..2d8c0ebc 100644
--- a/kotlinx-coroutines-core/jvm/test/exceptions/StackTraceRecoveryChannelsTest.kt
+++ b/kotlinx-coroutines-core/jvm/test/exceptions/StackTraceRecoveryChannelsTest.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.exceptions
@@ -38,13 +38,6 @@ class StackTraceRecoveryChannelsTest : TestBase() {
}
@Test
- fun testReceiveOrNullFromClosedChannel() = runTest {
- val channel = Channel<Int>()
- channel.close(RecoverableTestException())
- channelReceiveOrNull(channel)
- }
-
- @Test
fun testSendToClosedChannel() = runTest {
val channel = Channel<Int>()
channel.close(RecoverableTestException())
@@ -67,7 +60,6 @@ class StackTraceRecoveryChannelsTest : TestBase() {
}
private suspend fun channelReceive(channel: Channel<Int>) = channelOp { channel.receive() }
- private suspend fun channelReceiveOrNull(channel: Channel<Int>) = channelOp { channel.receiveOrNull() }
private suspend inline fun channelOp(block: () -> Unit) {
try {
@@ -145,25 +137,6 @@ class StackTraceRecoveryChannelsTest : TestBase() {
deferred.await()
}
- // See https://github.com/Kotlin/kotlinx.coroutines/issues/950
- @Test
- fun testCancelledOffer() = runTest {
- expect(1)
- val job = Job()
- val actor = actor<Int>(job, Channel.UNLIMITED) {
- consumeEach {
- expectUnreached() // is cancelled before offer
- }
- }
- job.cancel()
- try {
- actor.offer(1)
- } catch (e: Exception) {
- verifyStackTrace("channels/${name.methodName}", e)
- finish(2)
- }
- }
-
private suspend fun Channel<Int>.sendWithContext(ctx: CoroutineContext) = withContext(ctx) {
sendInChannel()
yield() // TCE
@@ -177,4 +150,4 @@ class StackTraceRecoveryChannelsTest : TestBase() {
private suspend fun Channel<Int>.sendFromScope() = coroutineScope {
sendWithContext(wrapperDispatcher(coroutineContext))
}
-} \ No newline at end of file
+}
diff --git a/kotlinx-coroutines-core/jvm/test/exceptions/StackTraceRecoveryCustomExceptionsTest.kt b/kotlinx-coroutines-core/jvm/test/exceptions/StackTraceRecoveryCustomExceptionsTest.kt
index 70336659..dba738a8 100644
--- a/kotlinx-coroutines-core/jvm/test/exceptions/StackTraceRecoveryCustomExceptionsTest.kt
+++ b/kotlinx-coroutines-core/jvm/test/exceptions/StackTraceRecoveryCustomExceptionsTest.kt
@@ -5,6 +5,7 @@
package kotlinx.coroutines.exceptions
import kotlinx.coroutines.*
+import kotlinx.coroutines.channels.*
import org.junit.Test
import kotlin.test.*
@@ -71,4 +72,56 @@ class StackTraceRecoveryCustomExceptionsTest : TestBase() {
assertEquals("custom", cause.message)
}
}
+
+ class WrongMessageException(token: String) : RuntimeException("Token $token")
+
+ @Test
+ fun testWrongMessageException() = runTest {
+ val result = runCatching {
+ coroutineScope<Unit> {
+ throw WrongMessageException("OK")
+ }
+ }
+ val ex = result.exceptionOrNull() ?: error("Expected to fail")
+ assertTrue(ex is WrongMessageException)
+ assertEquals("Token OK", ex.message)
+ }
+
+ @Test
+ fun testWrongMessageExceptionInChannel() = runTest {
+ val result = produce<Unit>(SupervisorJob() + Dispatchers.Unconfined) {
+ throw WrongMessageException("OK")
+ }
+ val ex = runCatching {
+ @Suppress("ControlFlowWithEmptyBody")
+ for (unit in result) {
+ // Iterator has a special code path
+ }
+ }.exceptionOrNull() ?: error("Expected to fail")
+ assertTrue(ex is WrongMessageException)
+ assertEquals("Token OK", ex.message)
+ }
+
+ class CopyableWithCustomMessage(
+ message: String?,
+ cause: Throwable? = null
+ ) : RuntimeException(message, cause),
+ CopyableThrowable<CopyableWithCustomMessage> {
+
+ override fun createCopy(): CopyableWithCustomMessage {
+ return CopyableWithCustomMessage("Recovered: [$message]", cause)
+ }
+ }
+
+ @Test
+ fun testCustomCopyableMessage() = runTest {
+ val result = runCatching {
+ coroutineScope<Unit> {
+ throw CopyableWithCustomMessage("OK")
+ }
+ }
+ val ex = result.exceptionOrNull() ?: error("Expected to fail")
+ assertTrue(ex is CopyableWithCustomMessage)
+ assertEquals("Recovered: [OK]", ex.message)
+ }
}
diff --git a/kotlinx-coroutines-core/jvm/test/exceptions/StackTraceRecoveryNestedScopesTest.kt b/kotlinx-coroutines-core/jvm/test/exceptions/StackTraceRecoveryNestedScopesTest.kt
index bea18a43..a85bb7a2 100644
--- a/kotlinx-coroutines-core/jvm/test/exceptions/StackTraceRecoveryNestedScopesTest.kt
+++ b/kotlinx-coroutines-core/jvm/test/exceptions/StackTraceRecoveryNestedScopesTest.kt
@@ -86,7 +86,6 @@ class StackTraceRecoveryNestedScopesTest : TestBase() {
"\tat kotlinx.coroutines.exceptions.StackTraceRecoveryNestedScopesTest\$callWithTimeout\$2.invokeSuspend(StackTraceRecoveryNestedScopesTest.kt:37)\n" +
"\tat kotlinx.coroutines.exceptions.StackTraceRecoveryNestedScopesTest\$callCoroutineScope\$2.invokeSuspend(StackTraceRecoveryNestedScopesTest.kt:43)\n" +
"\tat kotlinx.coroutines.exceptions.StackTraceRecoveryNestedScopesTest\$testAwaitNestedScopes\$1\$deferred\$1.invokeSuspend(StackTraceRecoveryNestedScopesTest.kt:68)\n" +
- "\tat kotlinx.coroutines.DeferredCoroutine.await\$suspendImpl(Builders.common.kt:99)\n" +
"\tat kotlinx.coroutines.exceptions.StackTraceRecoveryNestedScopesTest.verifyAwait(StackTraceRecoveryNestedScopesTest.kt:76)\n" +
"\tat kotlinx.coroutines.exceptions.StackTraceRecoveryNestedScopesTest\$testAwaitNestedScopes\$1.invokeSuspend(StackTraceRecoveryNestedScopesTest.kt:71)\n" +
"Caused by: kotlinx.coroutines.RecoverableTestException\n" +
diff --git a/kotlinx-coroutines-core/jvm/test/exceptions/StackTraceRecoveryNestedTest.kt b/kotlinx-coroutines-core/jvm/test/exceptions/StackTraceRecoveryNestedTest.kt
index 5073b7fd..02607c03 100644
--- a/kotlinx-coroutines-core/jvm/test/exceptions/StackTraceRecoveryNestedTest.kt
+++ b/kotlinx-coroutines-core/jvm/test/exceptions/StackTraceRecoveryNestedTest.kt
@@ -58,7 +58,7 @@ class StackTraceRecoveryNestedTest : TestBase() {
try {
rootAsync.awaitRootLevel()
} catch (e: RecoverableTestException) {
- e.verifyException("await\$suspendImpl", "awaitRootLevel")
+ e.verifyException("awaitRootLevel")
finish(8)
}
}
diff --git a/kotlinx-coroutines-core/jvm/test/exceptions/StackTraceRecoverySelectTest.kt b/kotlinx-coroutines-core/jvm/test/exceptions/StackTraceRecoverySelectTest.kt
index 290420e4..0d7648c5 100644
--- a/kotlinx-coroutines-core/jvm/test/exceptions/StackTraceRecoverySelectTest.kt
+++ b/kotlinx-coroutines-core/jvm/test/exceptions/StackTraceRecoverySelectTest.kt
@@ -45,9 +45,9 @@ class StackTraceRecoverySelectTest : TestBase() {
private suspend fun doSelectAwait(deferred: Deferred<Unit>): Int {
return select {
deferred.onAwait {
- yield() // Hide the stackstrace
+ yield() // Hide the frame
42
}
}
}
-} \ No newline at end of file
+}
diff --git a/kotlinx-coroutines-core/jvm/test/exceptions/StackTraceRecoveryTest.kt b/kotlinx-coroutines-core/jvm/test/exceptions/StackTraceRecoveryTest.kt
index dbbd77c4..0a8b6530 100644
--- a/kotlinx-coroutines-core/jvm/test/exceptions/StackTraceRecoveryTest.kt
+++ b/kotlinx-coroutines-core/jvm/test/exceptions/StackTraceRecoveryTest.kt
@@ -34,14 +34,13 @@ class StackTraceRecoveryTest : TestBase() {
val deferred = createDeferred(3)
val traces = listOf(
"java.util.concurrent.ExecutionException\n" +
- "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryTest\$testAsync\$1\$1\$1.invokeSuspend(StackTraceRecoveryTest.kt:99)\n" +
+ "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryTest\$testAsync\$1\$createDeferred\$1.invokeSuspend(StackTraceRecoveryTest.kt:99)\n" +
"\t(Coroutine boundary)\n" +
- "\tat kotlinx.coroutines.DeferredCoroutine.await\$suspendImpl(Builders.common.kt:99)\n" +
"\tat kotlinx.coroutines.exceptions.StackTraceRecoveryTest.oneMoreNestedMethod(StackTraceRecoveryTest.kt:49)\n" +
"\tat kotlinx.coroutines.exceptions.StackTraceRecoveryTest.nestedMethod(StackTraceRecoveryTest.kt:44)\n" +
"\tat kotlinx.coroutines.exceptions.StackTraceRecoveryTest\$testAsync\$1.invokeSuspend(StackTraceRecoveryTest.kt:17)\n",
"Caused by: java.util.concurrent.ExecutionException\n" +
- "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryTest\$testAsync\$1\$1\$1.invokeSuspend(StackTraceRecoveryTest.kt:21)\n" +
+ "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryTest\$testAsync\$1\$createDeferred\$1.invokeSuspend(StackTraceRecoveryTest.kt:21)\n" +
"\tat kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:32)\n"
)
nestedMethod(deferred, *traces.toTypedArray())
@@ -59,7 +58,6 @@ class StackTraceRecoveryTest : TestBase() {
"java.util.concurrent.ExecutionException\n" +
"\tat kotlinx.coroutines.exceptions.StackTraceRecoveryTest\$testCompletedAsync\$1\$deferred\$1.invokeSuspend(StackTraceRecoveryTest.kt:44)\n" +
"\t(Coroutine boundary)\n" +
- "\tat kotlinx.coroutines.DeferredCoroutine.await\$suspendImpl(Builders.common.kt:99)\n" +
"\tat kotlinx.coroutines.exceptions.StackTraceRecoveryTest.oneMoreNestedMethod(StackTraceRecoveryTest.kt:81)\n" +
"\tat kotlinx.coroutines.exceptions.StackTraceRecoveryTest.nestedMethod(StackTraceRecoveryTest.kt:75)\n" +
"\tat kotlinx.coroutines.exceptions.StackTraceRecoveryTest\$testCompletedAsync\$1.invokeSuspend(StackTraceRecoveryTest.kt:71)",
@@ -94,7 +92,6 @@ class StackTraceRecoveryTest : TestBase() {
"kotlinx.coroutines.RecoverableTestException\n" +
"\tat kotlinx.coroutines.exceptions.StackTraceRecoveryTest\$testWithContext\$1\$deferred\$1.invokeSuspend(StackTraceRecoveryTest.kt:143)\n" +
"\t(Coroutine boundary)\n" +
- "\tat kotlinx.coroutines.DeferredCoroutine.await\$suspendImpl(Builders.common.kt:99)\n" +
"\tat kotlinx.coroutines.exceptions.StackTraceRecoveryTest.innerMethod(StackTraceRecoveryTest.kt:158)\n" +
"\tat kotlinx.coroutines.exceptions.StackTraceRecoveryTest\$outerMethod\$2.invokeSuspend(StackTraceRecoveryTest.kt:151)\n" +
"\tat kotlinx.coroutines.exceptions.StackTraceRecoveryTest.outerMethod(StackTraceRecoveryTest.kt:150)\n" +
@@ -132,7 +129,6 @@ class StackTraceRecoveryTest : TestBase() {
"kotlinx.coroutines.RecoverableTestException\n" +
"\tat kotlinx.coroutines.exceptions.StackTraceRecoveryTest\$testCoroutineScope\$1\$deferred\$1.invokeSuspend(StackTraceRecoveryTest.kt:143)\n" +
"\t(Coroutine boundary)\n" +
- "\tat kotlinx.coroutines.DeferredCoroutine.await\$suspendImpl(Builders.common.kt:99)\n" +
"\tat kotlinx.coroutines.exceptions.StackTraceRecoveryTest.innerMethod(StackTraceRecoveryTest.kt:158)\n" +
"\tat kotlinx.coroutines.exceptions.StackTraceRecoveryTest\$outerScopedMethod\$2\$1.invokeSuspend(StackTraceRecoveryTest.kt:193)\n" +
"\tat kotlinx.coroutines.exceptions.StackTraceRecoveryTest\$outerScopedMethod\$2.invokeSuspend(StackTraceRecoveryTest.kt:151)\n" +
@@ -228,13 +224,13 @@ class StackTraceRecoveryTest : TestBase() {
val e = exception
assertNotNull(e)
verifyStackTrace(e, "kotlinx.coroutines.RecoverableTestException\n" +
- "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryTest.throws(StackTraceRecoveryTest.kt:280)\n" +
- "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryTest\$throws\$1.invokeSuspend(StackTraceRecoveryTest.kt)\n" +
- "\t(Coroutine boundary)\n" +
- "\tat kotlinx.coroutines.DeferredCoroutine.await\$suspendImpl(Builders.common.kt:99)\n" +
- "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryTest.awaiter(StackTraceRecoveryTest.kt:285)\n" +
- "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryTest\$testNonDispatchedRecovery\$await\$1.invokeSuspend(StackTraceRecoveryTest.kt:291)\n" +
- "Caused by: kotlinx.coroutines.RecoverableTestException")
+ "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryTest.throws(StackTraceRecoveryTest.kt:280)\n" +
+ "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryTest.access\$throws(StackTraceRecoveryTest.kt:20)\n" +
+ "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryTest\$throws\$1.invokeSuspend(StackTraceRecoveryTest.kt)\n" +
+ "\t(Coroutine boundary)\n" +
+ "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryTest.awaiter(StackTraceRecoveryTest.kt:285)\n" +
+ "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryTest\$testNonDispatchedRecovery\$await\$1.invokeSuspend(StackTraceRecoveryTest.kt:291)\n" +
+ "Caused by: kotlinx.coroutines.RecoverableTestException")
}
private class Callback(val cont: CancellableContinuation<*>)
@@ -261,22 +257,8 @@ class StackTraceRecoveryTest : TestBase() {
private suspend fun awaitCallback(channel: Channel<Callback>) {
suspendCancellableCoroutine<Unit> { cont ->
- channel.offer(Callback(cont))
+ channel.trySend(Callback(cont))
}
yield() // nop to make sure it is not a tail call
}
-
- @Test
- fun testWrongMessageException() = runTest {
- val result = runCatching {
- coroutineScope<Unit> {
- throw WrongMessageException("OK")
- }
- }
- val ex = result.exceptionOrNull() ?: error("Expected to fail")
- assertTrue(ex is WrongMessageException)
- assertEquals("Token OK", ex.message)
- }
-
- public class WrongMessageException(token: String) : RuntimeException("Token $token")
}
diff --git a/kotlinx-coroutines-core/jvm/test/exceptions/SuppressionTests.kt b/kotlinx-coroutines-core/jvm/test/exceptions/SuppressionTests.kt
index 6034fccb..edce175d 100644
--- a/kotlinx-coroutines-core/jvm/test/exceptions/SuppressionTests.kt
+++ b/kotlinx-coroutines-core/jvm/test/exceptions/SuppressionTests.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.exceptions
@@ -15,8 +15,8 @@ class SuppressionTests : TestBase() {
@Test
fun testNotificationsWithException() = runTest {
expect(1)
- val coroutineContext = kotlin.coroutines.coroutineContext // workaround for KT-22984
- val coroutine = object : AbstractCoroutine<String>(coroutineContext, false) {
+ val coroutineContext = kotlin.coroutines.coroutineContext + NonCancellable // workaround for KT-22984
+ val coroutine = object : AbstractCoroutine<String>(coroutineContext, true, false) {
override fun onStart() {
expect(3)
}
@@ -82,4 +82,4 @@ class SuppressionTests : TestBase() {
assertTrue(e.cause!!.suppressed.isEmpty())
}
}
-} \ No newline at end of file
+}
diff --git a/kotlinx-coroutines-core/jvm/test/flow/CallbackFlowTest.kt b/kotlinx-coroutines-core/jvm/test/flow/CallbackFlowTest.kt
index e3db2626..f1be284c 100644
--- a/kotlinx-coroutines-core/jvm/test/flow/CallbackFlowTest.kt
+++ b/kotlinx-coroutines-core/jvm/test/flow/CallbackFlowTest.kt
@@ -36,7 +36,7 @@ class CallbackFlowTest : TestBase() {
fun testThrowingConsumer() = runTest {
var i = 0
val api = CallbackApi {
- runCatching { it.offer(++i) }
+ it.trySend(++i)
}
val flow = callbackFlow<Int> {
@@ -77,13 +77,13 @@ class CallbackFlowTest : TestBase() {
var i = 0
val api = CallbackApi {
if (i < 5) {
- it.offer(++i)
+ it.trySend(++i)
} else {
it.close(RuntimeException())
}
}
- val flow = callbackFlow<Int>() {
+ val flow = callbackFlow<Int> {
api.start(channel)
awaitClose {
api.stop()
diff --git a/kotlinx-coroutines-core/jvm/test/flow/SharingReferenceTest.kt b/kotlinx-coroutines-core/jvm/test/flow/SharingReferenceTest.kt
new file mode 100644
index 00000000..98240fc9
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/flow/SharingReferenceTest.kt
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.flow
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.internal.*
+import org.junit.*
+
+/**
+ * Tests that shared flows keep strong reference to their source flows.
+ * See https://github.com/Kotlin/kotlinx.coroutines/issues/2557
+ */
+@OptIn(DelicateCoroutinesApi::class)
+class SharingReferenceTest : TestBase() {
+ private val token = object {}
+
+ /*
+ * Single-threaded executor that we are using to ensure that the flow being sharing actually
+ * suspended (spilled its locals, attached to parent), so we can verify reachability.
+ * Without that, it's possible to have a situation where target flow is still
+ * being strongly referenced (by its dispatcher), but the test already tries to test reachability and fails.
+ */
+ @get:Rule
+ val executor = ExecutorRule(1)
+
+ private val weakEmitter = flow {
+ emit(null)
+ // suspend forever without keeping a strong reference to continuation -- this is a model of
+ // a callback API that does not keep a strong reference it is listeners, but works
+ suspendCancellableCoroutine<Unit> { }
+ // using the token here to make it easily traceable
+ emit(token)
+ }
+
+ @Test
+ fun testShareInReference() {
+ val flow = weakEmitter.shareIn(ContextScope(executor), SharingStarted.Eagerly, 0)
+ linearize()
+ FieldWalker.assertReachableCount(1, flow) { it === token }
+ }
+
+ @Test
+ fun testStateInReference() {
+ val flow = weakEmitter.stateIn(ContextScope(executor), SharingStarted.Eagerly, null)
+ linearize()
+ FieldWalker.assertReachableCount(1, flow) { it === token }
+ }
+
+ @Test
+ fun testStateInSuspendingReference() = runTest {
+ val flow = weakEmitter.stateIn(GlobalScope)
+ linearize()
+ FieldWalker.assertReachableCount(1, flow) { it === token }
+ }
+
+ private fun linearize() {
+ runBlocking(executor) { }
+ }
+}
diff --git a/kotlinx-coroutines-core/jvm/test/flow/StateFlowStressTest.kt b/kotlinx-coroutines-core/jvm/test/flow/StateFlowStressTest.kt
index dc3cd43c..e55eaad1 100644
--- a/kotlinx-coroutines-core/jvm/test/flow/StateFlowStressTest.kt
+++ b/kotlinx-coroutines-core/jvm/test/flow/StateFlowStressTest.kt
@@ -62,7 +62,7 @@ class StateFlowStressTest : TestBase() {
for (second in 1..nSeconds) {
delay(1000)
val cs = collected.map { it.sum() }
- println("$second: emitted=${emitted.sum()}, collected=${cs.min()}..${cs.max()}")
+ println("$second: emitted=${emitted.sum()}, collected=${cs.minOrNull()}..${cs.maxOrNull()}")
}
emitters.cancelAndJoin()
collectors.cancelAndJoin()
@@ -77,4 +77,4 @@ class StateFlowStressTest : TestBase() {
@Test
fun testTenEmittersAndCollectors() = stress(10, 10)
-} \ No newline at end of file
+}
diff --git a/kotlinx-coroutines-core/jvm/test/flow/StateFlowUpdateStressTest.kt b/kotlinx-coroutines-core/jvm/test/flow/StateFlowUpdateStressTest.kt
new file mode 100644
index 00000000..660ed0aa
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/flow/StateFlowUpdateStressTest.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.flow
+
+import kotlinx.coroutines.*
+import org.junit.*
+import kotlin.test.*
+import kotlin.test.Test
+
+class StateFlowUpdateStressTest : TestBase() {
+ private val iterations = 1_000_000 * stressTestMultiplier
+
+ @get:Rule
+ public val executor = ExecutorRule(2)
+
+ @Test
+ fun testUpdate() = doTest { update { it + 1 } }
+
+ @Test
+ fun testUpdateAndGet() = doTest { updateAndGet { it + 1 } }
+
+ @Test
+ fun testGetAndUpdate() = doTest { getAndUpdate { it + 1 } }
+
+ private fun doTest(increment: MutableStateFlow<Int>.() -> Unit) = runTest {
+ val flow = MutableStateFlow(0)
+ val j1 = launch(Dispatchers.Default) {
+ repeat(iterations / 2) {
+ flow.increment()
+ }
+ }
+
+ val j2 = launch(Dispatchers.Default) {
+ repeat(iterations / 2) {
+ flow.increment()
+ }
+ }
+
+ joinAll(j1, j2)
+ assertEquals(iterations, flow.value)
+ }
+}
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-basic-01.kt b/kotlinx-coroutines-core/jvm/test/guide/example-basic-01.kt
index 15e12614..529f8817 100644
--- a/kotlinx-coroutines-core/jvm/test/guide/example-basic-01.kt
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-basic-01.kt
@@ -2,16 +2,15 @@
* Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
-// This file was automatically generated from basics.md by Knit tool. Do not edit.
+// This file was automatically generated from coroutines-basics.md by Knit tool. Do not edit.
package kotlinx.coroutines.guide.exampleBasic01
import kotlinx.coroutines.*
-fun main() {
- GlobalScope.launch { // launch a new coroutine in background and continue
+fun main() = runBlocking { // this: CoroutineScope
+ launch { // launch a new coroutine and continue
delay(1000L) // non-blocking delay for 1 second (default time unit is ms)
println("World!") // print after delay
}
- println("Hello,") // main thread continues while coroutine is delayed
- Thread.sleep(2000L) // block main thread for 2 seconds to keep JVM alive
+ println("Hello") // main coroutine continues while a previous one is delayed
}
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-basic-02.kt b/kotlinx-coroutines-core/jvm/test/guide/example-basic-02.kt
index 4f178ca6..6bf2af4c 100644
--- a/kotlinx-coroutines-core/jvm/test/guide/example-basic-02.kt
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-basic-02.kt
@@ -2,18 +2,18 @@
* Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
-// This file was automatically generated from basics.md by Knit tool. Do not edit.
+// This file was automatically generated from coroutines-basics.md by Knit tool. Do not edit.
package kotlinx.coroutines.guide.exampleBasic02
import kotlinx.coroutines.*
-fun main() {
- GlobalScope.launch { // launch a new coroutine in background and continue
- delay(1000L)
- println("World!")
- }
- println("Hello,") // main thread continues here immediately
- runBlocking { // but this expression blocks the main thread
- delay(2000L) // ... while we delay for 2 seconds to keep JVM alive
- }
+fun main() = runBlocking { // this: CoroutineScope
+ launch { doWorld() }
+ println("Hello")
+}
+
+// this is your first suspending function
+suspend fun doWorld() {
+ delay(1000L)
+ println("World!")
}
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-basic-03.kt b/kotlinx-coroutines-core/jvm/test/guide/example-basic-03.kt
index f80113c5..67b6894a 100644
--- a/kotlinx-coroutines-core/jvm/test/guide/example-basic-03.kt
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-basic-03.kt
@@ -2,16 +2,19 @@
* Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
-// This file was automatically generated from basics.md by Knit tool. Do not edit.
+// This file was automatically generated from coroutines-basics.md by Knit tool. Do not edit.
package kotlinx.coroutines.guide.exampleBasic03
import kotlinx.coroutines.*
-fun main() = runBlocking<Unit> { // start main coroutine
- GlobalScope.launch { // launch a new coroutine in background and continue
+fun main() = runBlocking {
+ doWorld()
+}
+
+suspend fun doWorld() = coroutineScope { // this: CoroutineScope
+ launch {
delay(1000L)
println("World!")
}
- println("Hello,") // main coroutine continues here immediately
- delay(2000L) // delaying for 2 seconds to keep JVM alive
+ println("Hello")
}
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-basic-04.kt b/kotlinx-coroutines-core/jvm/test/guide/example-basic-04.kt
index 33c928a0..efac7085 100644
--- a/kotlinx-coroutines-core/jvm/test/guide/example-basic-04.kt
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-basic-04.kt
@@ -2,16 +2,26 @@
* Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
-// This file was automatically generated from basics.md by Knit tool. Do not edit.
+// This file was automatically generated from coroutines-basics.md by Knit tool. Do not edit.
package kotlinx.coroutines.guide.exampleBasic04
import kotlinx.coroutines.*
+// Sequentially executes doWorld followed by "Done"
fun main() = runBlocking {
- val job = GlobalScope.launch { // launch a new coroutine and keep a reference to its Job
+ doWorld()
+ println("Done")
+}
+
+// Concurrently executes both sections
+suspend fun doWorld() = coroutineScope { // this: CoroutineScope
+ launch {
+ delay(2000L)
+ println("World 2")
+ }
+ launch {
delay(1000L)
- println("World!")
+ println("World 1")
}
- println("Hello,")
- job.join() // wait until child coroutine completes
+ println("Hello")
}
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-basic-05.kt b/kotlinx-coroutines-core/jvm/test/guide/example-basic-05.kt
index 52b490d2..193f2cc3 100644
--- a/kotlinx-coroutines-core/jvm/test/guide/example-basic-05.kt
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-basic-05.kt
@@ -2,15 +2,17 @@
* Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
-// This file was automatically generated from basics.md by Knit tool. Do not edit.
+// This file was automatically generated from coroutines-basics.md by Knit tool. Do not edit.
package kotlinx.coroutines.guide.exampleBasic05
import kotlinx.coroutines.*
-fun main() = runBlocking { // this: CoroutineScope
- launch { // launch a new coroutine in the scope of runBlocking
+fun main() = runBlocking {
+ val job = launch { // launch a new coroutine and keep a reference to its Job
delay(1000L)
println("World!")
}
- println("Hello,")
+ println("Hello")
+ job.join() // wait until child coroutine completes
+ println("Done")
}
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-basic-06.kt b/kotlinx-coroutines-core/jvm/test/guide/example-basic-06.kt
index a1b5bc5f..24b890a0 100644
--- a/kotlinx-coroutines-core/jvm/test/guide/example-basic-06.kt
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-basic-06.kt
@@ -2,26 +2,16 @@
* Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
-// This file was automatically generated from basics.md by Knit tool. Do not edit.
+// This file was automatically generated from coroutines-basics.md by Knit tool. Do not edit.
package kotlinx.coroutines.guide.exampleBasic06
import kotlinx.coroutines.*
-fun main() = runBlocking { // this: CoroutineScope
- launch {
- delay(200L)
- println("Task from runBlocking")
- }
-
- coroutineScope { // Creates a coroutine scope
+fun main() = runBlocking {
+ repeat(100_000) { // launch a lot of coroutines
launch {
- delay(500L)
- println("Task from nested launch")
+ delay(5000L)
+ print(".")
}
-
- delay(100L)
- println("Task from coroutine scope") // This line will be printed before the nested launch
}
-
- println("Coroutine scope is over") // This line is not printed until the nested launch completes
}
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-basic-07.kt b/kotlinx-coroutines-core/jvm/test/guide/example-basic-07.kt
deleted file mode 100644
index 32c02b86..00000000
--- a/kotlinx-coroutines-core/jvm/test/guide/example-basic-07.kt
+++ /dev/null
@@ -1,19 +0,0 @@
-/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
- */
-
-// This file was automatically generated from basics.md by Knit tool. Do not edit.
-package kotlinx.coroutines.guide.exampleBasic07
-
-import kotlinx.coroutines.*
-
-fun main() = runBlocking {
- launch { doWorld() }
- println("Hello,")
-}
-
-// this is your first suspending function
-suspend fun doWorld() {
- delay(1000L)
- println("World!")
-}
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-basic-08.kt b/kotlinx-coroutines-core/jvm/test/guide/example-basic-08.kt
deleted file mode 100644
index bb7786f2..00000000
--- a/kotlinx-coroutines-core/jvm/test/guide/example-basic-08.kt
+++ /dev/null
@@ -1,17 +0,0 @@
-/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
- */
-
-// This file was automatically generated from basics.md by Knit tool. Do not edit.
-package kotlinx.coroutines.guide.exampleBasic08
-
-import kotlinx.coroutines.*
-
-fun main() = runBlocking {
- repeat(100_000) { // launch a lot of coroutines
- launch {
- delay(5000L)
- print(".")
- }
- }
-}
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-basic-09.kt b/kotlinx-coroutines-core/jvm/test/guide/example-basic-09.kt
deleted file mode 100644
index 9f998b52..00000000
--- a/kotlinx-coroutines-core/jvm/test/guide/example-basic-09.kt
+++ /dev/null
@@ -1,18 +0,0 @@
-/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
- */
-
-// This file was automatically generated from basics.md by Knit tool. Do not edit.
-package kotlinx.coroutines.guide.exampleBasic09
-
-import kotlinx.coroutines.*
-
-fun main() = runBlocking {
- GlobalScope.launch {
- repeat(1000) { i ->
- println("I'm sleeping $i ...")
- delay(500L)
- }
- }
- delay(1300L) // just quit after delay
-}
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-compose-04.kt b/kotlinx-coroutines-core/jvm/test/guide/example-compose-04.kt
index 312dc72b..35536a7d 100644
--- a/kotlinx-coroutines-core/jvm/test/guide/example-compose-04.kt
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-compose-04.kt
@@ -23,10 +23,12 @@ fun main() {
println("Completed in $time ms")
}
+@OptIn(DelicateCoroutinesApi::class)
fun somethingUsefulOneAsync() = GlobalScope.async {
doSomethingUsefulOne()
}
+@OptIn(DelicateCoroutinesApi::class)
fun somethingUsefulTwoAsync() = GlobalScope.async {
doSomethingUsefulTwo()
}
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-context-06.kt b/kotlinx-coroutines-core/jvm/test/guide/example-context-06.kt
index e23eaf25..c6ad4516 100644
--- a/kotlinx-coroutines-core/jvm/test/guide/example-context-06.kt
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-context-06.kt
@@ -10,9 +10,9 @@ import kotlinx.coroutines.*
fun main() = runBlocking<Unit> {
// launch a coroutine to process some kind of incoming request
val request = launch {
- // it spawns two other jobs, one with GlobalScope
- GlobalScope.launch {
- println("job1: I run in GlobalScope and execute independently!")
+ // it spawns two other jobs
+ launch(Job()) {
+ println("job1: I run in my own Job and execute independently!")
delay(1000)
println("job1: I am not affected by cancellation of the request")
}
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-exceptions-01.kt b/kotlinx-coroutines-core/jvm/test/guide/example-exceptions-01.kt
index e08ddd08..24cbabe0 100644
--- a/kotlinx-coroutines-core/jvm/test/guide/example-exceptions-01.kt
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-exceptions-01.kt
@@ -7,6 +7,7 @@ package kotlinx.coroutines.guide.exampleExceptions01
import kotlinx.coroutines.*
+@OptIn(DelicateCoroutinesApi::class)
fun main() = runBlocking {
val job = GlobalScope.launch { // root coroutine with launch
println("Throwing exception from launch")
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-exceptions-02.kt b/kotlinx-coroutines-core/jvm/test/guide/example-exceptions-02.kt
index 67fdaa71..c3ab68a5 100644
--- a/kotlinx-coroutines-core/jvm/test/guide/example-exceptions-02.kt
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-exceptions-02.kt
@@ -7,6 +7,7 @@ package kotlinx.coroutines.guide.exampleExceptions02
import kotlinx.coroutines.*
+@OptIn(DelicateCoroutinesApi::class)
fun main() = runBlocking {
val handler = CoroutineExceptionHandler { _, exception ->
println("CoroutineExceptionHandler got $exception")
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-exceptions-04.kt b/kotlinx-coroutines-core/jvm/test/guide/example-exceptions-04.kt
index 9c9b43d2..b966c1ea 100644
--- a/kotlinx-coroutines-core/jvm/test/guide/example-exceptions-04.kt
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-exceptions-04.kt
@@ -7,6 +7,7 @@ package kotlinx.coroutines.guide.exampleExceptions04
import kotlinx.coroutines.*
+@OptIn(DelicateCoroutinesApi::class)
fun main() = runBlocking {
val handler = CoroutineExceptionHandler { _, exception ->
println("CoroutineExceptionHandler got $exception")
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-exceptions-05.kt b/kotlinx-coroutines-core/jvm/test/guide/example-exceptions-05.kt
index 04f9385f..5f1f3d89 100644
--- a/kotlinx-coroutines-core/jvm/test/guide/example-exceptions-05.kt
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-exceptions-05.kt
@@ -10,6 +10,7 @@ import kotlinx.coroutines.exceptions.*
import kotlinx.coroutines.*
import java.io.*
+@OptIn(DelicateCoroutinesApi::class)
fun main() = runBlocking {
val handler = CoroutineExceptionHandler { _, exception ->
println("CoroutineExceptionHandler got $exception with suppressed ${exception.suppressed.contentToString()}")
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-exceptions-06.kt b/kotlinx-coroutines-core/jvm/test/guide/example-exceptions-06.kt
index 5a5b276b..bc9f77b9 100644
--- a/kotlinx-coroutines-core/jvm/test/guide/example-exceptions-06.kt
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-exceptions-06.kt
@@ -8,6 +8,7 @@ package kotlinx.coroutines.guide.exampleExceptions06
import kotlinx.coroutines.*
import java.io.*
+@OptIn(DelicateCoroutinesApi::class)
fun main() = runBlocking {
val handler = CoroutineExceptionHandler { _, exception ->
println("CoroutineExceptionHandler got $exception")
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-select-02.kt b/kotlinx-coroutines-core/jvm/test/guide/example-select-02.kt
index 57fe6382..22380d3a 100644
--- a/kotlinx-coroutines-core/jvm/test/guide/example-select-02.kt
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-select-02.kt
@@ -11,17 +11,21 @@ import kotlinx.coroutines.selects.*
suspend fun selectAorB(a: ReceiveChannel<String>, b: ReceiveChannel<String>): String =
select<String> {
- a.onReceiveOrNull { value ->
- if (value == null)
- "Channel 'a' is closed"
- else
+ a.onReceiveCatching { it ->
+ val value = it.getOrNull()
+ if (value != null) {
"a -> '$value'"
+ } else {
+ "Channel 'a' is closed"
+ }
}
- b.onReceiveOrNull { value ->
- if (value == null)
- "Channel 'b' is closed"
- else
+ b.onReceiveCatching { it ->
+ val value = it.getOrNull()
+ if (value != null) {
"b -> '$value'"
+ } else {
+ "Channel 'b' is closed"
+ }
}
}
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-select-05.kt b/kotlinx-coroutines-core/jvm/test/guide/example-select-05.kt
index 464e9b20..68b44564 100644
--- a/kotlinx-coroutines-core/jvm/test/guide/example-select-05.kt
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-select-05.kt
@@ -13,12 +13,12 @@ fun CoroutineScope.switchMapDeferreds(input: ReceiveChannel<Deferred<String>>) =
var current = input.receive() // start with first received deferred value
while (isActive) { // loop while not cancelled/closed
val next = select<Deferred<String>?> { // return next deferred value from this select or null
- input.onReceiveOrNull { update ->
- update // replaces next value to wait
+ input.onReceiveCatching { update ->
+ update.getOrNull()
}
- current.onAwait { value ->
+ current.onAwait { value ->
send(value) // send value that current deferred has produced
- input.receiveOrNull() // and use the next deferred from the input channel
+ input.receiveCatching().getOrNull() // and use the next deferred from the input channel
}
}
if (next == null) {
diff --git a/kotlinx-coroutines-core/jvm/test/guide/test/BasicsGuideTest.kt b/kotlinx-coroutines-core/jvm/test/guide/test/BasicsGuideTest.kt
index ea5003b0..7e54fb1d 100644
--- a/kotlinx-coroutines-core/jvm/test/guide/test/BasicsGuideTest.kt
+++ b/kotlinx-coroutines-core/jvm/test/guide/test/BasicsGuideTest.kt
@@ -2,7 +2,7 @@
* Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
-// This file was automatically generated from basics.md by Knit tool. Do not edit.
+// This file was automatically generated from coroutines-basics.md by Knit tool. Do not edit.
package kotlinx.coroutines.guide.test
import kotlinx.coroutines.knit.*
@@ -12,7 +12,7 @@ class BasicsGuideTest {
@Test
fun testExampleBasic01() {
test("ExampleBasic01") { kotlinx.coroutines.guide.exampleBasic01.main() }.verifyLines(
- "Hello,",
+ "Hello",
"World!"
)
}
@@ -20,7 +20,7 @@ class BasicsGuideTest {
@Test
fun testExampleBasic02() {
test("ExampleBasic02") { kotlinx.coroutines.guide.exampleBasic02.main() }.verifyLines(
- "Hello,",
+ "Hello",
"World!"
)
}
@@ -28,7 +28,7 @@ class BasicsGuideTest {
@Test
fun testExampleBasic03() {
test("ExampleBasic03") { kotlinx.coroutines.guide.exampleBasic03.main() }.verifyLines(
- "Hello,",
+ "Hello",
"World!"
)
}
@@ -36,50 +36,26 @@ class BasicsGuideTest {
@Test
fun testExampleBasic04() {
test("ExampleBasic04") { kotlinx.coroutines.guide.exampleBasic04.main() }.verifyLines(
- "Hello,",
- "World!"
+ "Hello",
+ "World 1",
+ "World 2",
+ "Done"
)
}
@Test
fun testExampleBasic05() {
test("ExampleBasic05") { kotlinx.coroutines.guide.exampleBasic05.main() }.verifyLines(
- "Hello,",
- "World!"
+ "Hello",
+ "World!",
+ "Done"
)
}
@Test
fun testExampleBasic06() {
- test("ExampleBasic06") { kotlinx.coroutines.guide.exampleBasic06.main() }.verifyLines(
- "Task from coroutine scope",
- "Task from runBlocking",
- "Task from nested launch",
- "Coroutine scope is over"
- )
- }
-
- @Test
- fun testExampleBasic07() {
- test("ExampleBasic07") { kotlinx.coroutines.guide.exampleBasic07.main() }.verifyLines(
- "Hello,",
- "World!"
- )
- }
-
- @Test
- fun testExampleBasic08() {
- test("ExampleBasic08") { kotlinx.coroutines.guide.exampleBasic08.main() }.also { lines ->
+ test("ExampleBasic06") { kotlinx.coroutines.guide.exampleBasic06.main() }.also { lines ->
check(lines.size == 1 && lines[0] == ".".repeat(100_000))
}
}
-
- @Test
- fun testExampleBasic09() {
- test("ExampleBasic09") { kotlinx.coroutines.guide.exampleBasic09.main() }.verifyLines(
- "I'm sleeping 0 ...",
- "I'm sleeping 1 ...",
- "I'm sleeping 2 ..."
- )
- }
}
diff --git a/kotlinx-coroutines-core/jvm/test/guide/test/DispatcherGuideTest.kt b/kotlinx-coroutines-core/jvm/test/guide/test/DispatcherGuideTest.kt
index d6f1c21d..1a84fb94 100644
--- a/kotlinx-coroutines-core/jvm/test/guide/test/DispatcherGuideTest.kt
+++ b/kotlinx-coroutines-core/jvm/test/guide/test/DispatcherGuideTest.kt
@@ -57,7 +57,7 @@ class DispatcherGuideTest {
@Test
fun testExampleContext06() {
test("ExampleContext06") { kotlinx.coroutines.guide.exampleContext06.main() }.verifyLines(
- "job1: I run in GlobalScope and execute independently!",
+ "job1: I run in my own Job and execute independently!",
"job2: I am a child of the request coroutine",
"job1: I am not affected by cancellation of the request",
"main: Who has survived request cancellation?"
diff --git a/kotlinx-coroutines-core/jvm/test/internal/LockFreeLinkedListAtomicLFStressTest.kt b/kotlinx-coroutines-core/jvm/test/internal/LockFreeLinkedListAtomicLFStressTest.kt
deleted file mode 100644
index 225b8481..00000000
--- a/kotlinx-coroutines-core/jvm/test/internal/LockFreeLinkedListAtomicLFStressTest.kt
+++ /dev/null
@@ -1,169 +0,0 @@
-/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
- */
-
-package kotlinx.coroutines.internal
-
-import kotlinx.atomicfu.LockFreedomTestEnvironment
-import kotlinx.coroutines.stressTestMultiplier
-import org.junit.Test
-import java.util.*
-import java.util.concurrent.atomic.AtomicLong
-import java.util.concurrent.atomic.AtomicReference
-import kotlin.test.*
-
-/**
- * This stress test has 4 threads adding randomly to the list and them immediately undoing
- * this addition by remove, and 4 threads trying to remove nodes from two lists simultaneously (atomically).
- */
-class LockFreeLinkedListAtomicLFStressTest {
- private val env = LockFreedomTestEnvironment("LockFreeLinkedListAtomicLFStressTest")
-
- private data class Node(val i: Long) : LockFreeLinkedListNode()
-
- private val nSeconds = 5 * stressTestMultiplier
-
- private val nLists = 4
- private val nAdderThreads = 4
- private val nRemoverThreads = 4
-
- private val lists = Array(nLists) { LockFreeLinkedListHead() }
-
- private val undone = AtomicLong()
- private val missed = AtomicLong()
- private val removed = AtomicLong()
- private val error = AtomicReference<Throwable>()
- private val index = AtomicLong()
-
- @Test
- fun testStress() {
- repeat(nAdderThreads) { threadId ->
- val rnd = Random()
- env.testThread(name = "adder-$threadId") {
- when (rnd.nextInt(4)) {
- 0 -> {
- val list = lists[rnd.nextInt(nLists)]
- val node = Node(index.incrementAndGet())
- addLastOp(list, node)
- randomSpinWaitIntermission()
- tryRemoveOp(node)
- }
- 1 -> {
- // just to test conditional add
- val list = lists[rnd.nextInt(nLists)]
- val node = Node(index.incrementAndGet())
- addLastIfTrueOp(list, node)
- randomSpinWaitIntermission()
- tryRemoveOp(node)
- }
- 2 -> {
- // just to test failed conditional add and burn some time
- val list = lists[rnd.nextInt(nLists)]
- val node = Node(index.incrementAndGet())
- addLastIfFalseOp(list, node)
- }
- 3 -> {
- // add two atomically
- val idx1 = rnd.nextInt(nLists - 1)
- val idx2 = idx1 + 1 + rnd.nextInt(nLists - idx1 - 1)
- check(idx1 < idx2) // that is our global order
- val list1 = lists[idx1]
- val list2 = lists[idx2]
- val node1 = Node(index.incrementAndGet())
- val node2 = Node(index.incrementAndGet())
- addTwoOp(list1, node1, list2, node2)
- randomSpinWaitIntermission()
- tryRemoveOp(node1)
- randomSpinWaitIntermission()
- tryRemoveOp(node2)
- }
- else -> error("Cannot happen")
- }
- }
- }
- repeat(nRemoverThreads) { threadId ->
- val rnd = Random()
- env.testThread(name = "remover-$threadId") {
- val idx1 = rnd.nextInt(nLists - 1)
- val idx2 = idx1 + 1 + rnd.nextInt(nLists - idx1 - 1)
- check(idx1 < idx2) // that is our global order
- val list1 = lists[idx1]
- val list2 = lists[idx2]
- removeTwoOp(list1, list2)
- }
- }
- env.performTest(nSeconds) {
- val undone = undone.get()
- val missed = missed.get()
- val removed = removed.get()
- println(" Adders undone $undone node additions")
- println(" Adders missed $missed nodes")
- println("Remover removed $removed nodes")
- }
- error.get()?.let { throw it }
- assertEquals(missed.get(), removed.get())
- assertTrue(undone.get() > 0)
- assertTrue(missed.get() > 0)
- lists.forEach { it.validate() }
- }
-
- private fun addLastOp(list: LockFreeLinkedListHead, node: Node) {
- list.addLast(node)
- }
-
- private fun addLastIfTrueOp(list: LockFreeLinkedListHead, node: Node) {
- assertTrue(list.addLastIf(node) { true })
- }
-
- private fun addLastIfFalseOp(list: LockFreeLinkedListHead, node: Node) {
- assertFalse(list.addLastIf(node) { false })
- }
-
- private fun addTwoOp(list1: LockFreeLinkedListHead, node1: Node, list2: LockFreeLinkedListHead, node2: Node) {
- val add1 = list1.describeAddLast(node1)
- val add2 = list2.describeAddLast(node2)
- val op = object : AtomicOp<Any?>() {
- init {
- add1.atomicOp = this
- add2.atomicOp = this
- }
- override fun prepare(affected: Any?): Any? =
- add1.prepare(this) ?:
- add2.prepare(this)
-
- override fun complete(affected: Any?, failure: Any?) {
- add1.complete(this, failure)
- add2.complete(this, failure)
- }
- }
- assertTrue(op.perform(null) == null)
- }
-
- private fun tryRemoveOp(node: Node) {
- if (node.remove())
- undone.incrementAndGet()
- else
- missed.incrementAndGet()
- }
-
- private fun removeTwoOp(list1: LockFreeLinkedListHead, list2: LockFreeLinkedListHead) {
- val remove1 = list1.describeRemoveFirst()
- val remove2 = list2.describeRemoveFirst()
- val op = object : AtomicOp<Any?>() {
- init {
- remove1.atomicOp = this
- remove2.atomicOp = this
- }
- override fun prepare(affected: Any?): Any? =
- remove1.prepare(this) ?:
- remove2.prepare(this)
-
- override fun complete(affected: Any?, failure: Any?) {
- remove1.complete(this, failure)
- remove2.complete(this, failure)
- }
- }
- val success = op.perform(null) == null
- if (success) removed.addAndGet(2)
- }
-} \ No newline at end of file
diff --git a/kotlinx-coroutines-core/jvm/test/knit/ClosedAfterGuideTestExecutor.kt b/kotlinx-coroutines-core/jvm/test/knit/ClosedAfterGuideTestExecutor.kt
new file mode 100644
index 00000000..30fbfee2
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/knit/ClosedAfterGuideTestExecutor.kt
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines // Trick to make guide tests use these declarations with executors that can be closed on our side implicitly
+
+import java.util.concurrent.*
+import java.util.concurrent.atomic.*
+import kotlin.coroutines.*
+
+internal fun newSingleThreadContext(name: String): ExecutorCoroutineDispatcher = ClosedAfterGuideTestDispatcher(1, name)
+
+internal fun newFixedThreadPoolContext(nThreads: Int, name: String): ExecutorCoroutineDispatcher =
+ ClosedAfterGuideTestDispatcher(nThreads, name)
+
+internal class PoolThread(
+ @JvmField val dispatcher: ExecutorCoroutineDispatcher, // for debugging & tests
+ target: Runnable, name: String
+) : Thread(target, name) {
+ init {
+ isDaemon = true
+ }
+}
+
+private class ClosedAfterGuideTestDispatcher(
+ private val nThreads: Int,
+ private val name: String
+) : ExecutorCoroutineDispatcher() {
+ private val threadNo = AtomicInteger()
+
+ override val executor: Executor =
+ Executors.newScheduledThreadPool(nThreads, object : ThreadFactory {
+ override fun newThread(target: java.lang.Runnable): Thread {
+ return PoolThread(
+ this@ClosedAfterGuideTestDispatcher,
+ target,
+ if (nThreads == 1) name else name + "-" + threadNo.incrementAndGet()
+ )
+ }
+ })
+
+ override fun dispatch(context: CoroutineContext, block: Runnable) {
+ executor.execute(wrapTask(block))
+ }
+
+ override fun close() {
+ (executor as ExecutorService).shutdown()
+ }
+
+ override fun toString(): String = "ThreadPoolDispatcher[$nThreads, $name]"
+}
diff --git a/kotlinx-coroutines-core/jvm/test/knit/TestUtil.kt b/kotlinx-coroutines-core/jvm/test/knit/TestUtil.kt
index 7eda9043..2e61ec6b 100644
--- a/kotlinx-coroutines-core/jvm/test/knit/TestUtil.kt
+++ b/kotlinx-coroutines-core/jvm/test/knit/TestUtil.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.knit
@@ -11,8 +11,6 @@ import kotlinx.knit.test.*
import java.util.concurrent.*
import kotlin.test.*
-fun wrapTask(block: Runnable) = kotlinx.coroutines.wrapTask(block)
-
// helper function to dump exception to stdout for ease of debugging failed tests
private inline fun <T> outputException(name: String, block: () -> T): T =
try { block() }
@@ -176,4 +174,4 @@ private inline fun List<String>.verify(verification: () -> Unit) {
}
throw t
}
-} \ No newline at end of file
+}
diff --git a/kotlinx-coroutines-core/jvm/test/linearizability/ChannelsLCStressTest.kt b/kotlinx-coroutines-core/jvm/test/lincheck/ChannelsLincheckTest.kt
index 8836fdc7..74cc1783 100644
--- a/kotlinx-coroutines-core/jvm/test/linearizability/ChannelsLCStressTest.kt
+++ b/kotlinx-coroutines-core/jvm/test/lincheck/ChannelsLincheckTest.kt
@@ -3,7 +3,7 @@
*/
@file:Suppress("unused")
-package kotlinx.coroutines.linearizability
+package kotlinx.coroutines.lincheck
import kotlinx.coroutines.*
import kotlinx.coroutines.channels.*
@@ -11,37 +11,37 @@ import kotlinx.coroutines.channels.Channel.Factory.CONFLATED
import kotlinx.coroutines.channels.Channel.Factory.RENDEZVOUS
import kotlinx.coroutines.channels.Channel.Factory.UNLIMITED
import kotlinx.coroutines.selects.*
+import org.jetbrains.kotlinx.lincheck.*
import org.jetbrains.kotlinx.lincheck.annotations.*
import org.jetbrains.kotlinx.lincheck.annotations.Operation
import org.jetbrains.kotlinx.lincheck.paramgen.*
import org.jetbrains.kotlinx.lincheck.verifier.*
-import org.junit.*
-class RendezvousChannelLCStressTest : ChannelLCStressTestBase(
+class RendezvousChannelLincheckTest : ChannelLincheckTestBase(
c = Channel(RENDEZVOUS),
sequentialSpecification = SequentialRendezvousChannel::class.java
)
class SequentialRendezvousChannel : SequentialIntChannelBase(RENDEZVOUS)
-class Array1ChannelLCStressTest : ChannelLCStressTestBase(
+class Array1ChannelLincheckTest : ChannelLincheckTestBase(
c = Channel(1),
sequentialSpecification = SequentialArray1RendezvousChannel::class.java
)
class SequentialArray1RendezvousChannel : SequentialIntChannelBase(1)
-class Array2ChannelLCStressTest : ChannelLCStressTestBase(
+class Array2ChannelLincheckTest : ChannelLincheckTestBase(
c = Channel(2),
sequentialSpecification = SequentialArray2RendezvousChannel::class.java
)
class SequentialArray2RendezvousChannel : SequentialIntChannelBase(2)
-class UnlimitedChannelLCStressTest : ChannelLCStressTestBase(
+class UnlimitedChannelLincheckTest : ChannelLincheckTestBase(
c = Channel(UNLIMITED),
sequentialSpecification = SequentialUnlimitedChannel::class.java
)
class SequentialUnlimitedChannel : SequentialIntChannelBase(UNLIMITED)
-class ConflatedChannelLCStressTest : ChannelLCStressTestBase(
+class ConflatedChannelLincheckTest : ChannelLincheckTestBase(
c = Channel(CONFLATED),
sequentialSpecification = SequentialConflatedChannel::class.java
)
@@ -51,8 +51,11 @@ class SequentialConflatedChannel : SequentialIntChannelBase(CONFLATED)
Param(name = "value", gen = IntGen::class, conf = "1:5"),
Param(name = "closeToken", gen = IntGen::class, conf = "1:3")
)
-abstract class ChannelLCStressTestBase(private val c: Channel<Int>, private val sequentialSpecification: Class<*>) {
- @Operation
+abstract class ChannelLincheckTestBase(
+ private val c: Channel<Int>,
+ private val sequentialSpecification: Class<*>
+) : AbstractLincheckTest() {
+ @Operation(promptCancellation = true)
suspend fun send(@Param(name = "value") value: Int): Any = try {
c.send(value)
} catch (e: NumberedCancellationException) {
@@ -60,11 +63,12 @@ abstract class ChannelLCStressTestBase(private val c: Channel<Int>, private val
}
@Operation
- fun offer(@Param(name = "value") value: Int): Any = try {
- c.offer(value)
- } catch (e: NumberedCancellationException) {
- e.testResult
- }
+ fun trySend(@Param(name = "value") value: Int): Any = c.trySend(value)
+ .onSuccess { return true }
+ .onFailure {
+ return if (it is NumberedCancellationException) it.testResult
+ else false
+ }
// TODO: this operation should be (and can be!) linearizable, but is not
// @Operation
@@ -74,7 +78,7 @@ abstract class ChannelLCStressTestBase(private val c: Channel<Int>, private val
e.testResult
}
- @Operation
+ @Operation(promptCancellation = true)
suspend fun receive(): Any = try {
c.receive()
} catch (e: NumberedCancellationException) {
@@ -82,11 +86,10 @@ abstract class ChannelLCStressTestBase(private val c: Channel<Int>, private val
}
@Operation
- fun poll(): Any? = try {
- c.poll()
- } catch (e: NumberedCancellationException) {
- e.testResult
- }
+ fun tryReceive(): Any? =
+ c.tryReceive()
+ .onSuccess { return it }
+ .onFailure { return if (it is NumberedCancellationException) it.testResult else null }
// TODO: this operation should be (and can be!) linearizable, but is not
// @Operation
@@ -96,7 +99,7 @@ abstract class ChannelLCStressTestBase(private val c: Channel<Int>, private val
e.testResult
}
- @Operation
+ @Operation(causesBlocking = true)
fun close(@Param(name = "closeToken") token: Int): Boolean = c.close(NumberedCancellationException(token))
// TODO: this operation should be (and can be!) linearizable, but is not
@@ -113,11 +116,8 @@ abstract class ChannelLCStressTestBase(private val c: Channel<Int>, private val
// @Operation
fun isEmpty() = c.isEmpty
- @Test
- fun test() = LCStressOptionsDefault()
- .actorsBefore(0)
- .sequentialSpecification(sequentialSpecification)
- .check(this::class)
+ override fun <O : Options<O, *>> O.customize(isStressTest: Boolean): O =
+ actorsBefore(0).sequentialSpecification(sequentialSpecification)
}
private class NumberedCancellationException(number: Int) : CancellationException() {
@@ -131,7 +131,7 @@ abstract class SequentialIntChannelBase(private val capacity: Int) : VerifierSta
private val buffer = ArrayList<Int>()
private var closedMessage: String? = null
- suspend fun send(x: Int): Any = when (val offerRes = offer(x)) {
+ suspend fun send(x: Int): Any = when (val offerRes = trySend(x)) {
true -> Unit
false -> suspendCancellableCoroutine { cont ->
senders.add(cont to x)
@@ -139,7 +139,7 @@ abstract class SequentialIntChannelBase(private val capacity: Int) : VerifierSta
else -> offerRes
}
- fun offer(element: Int): Any {
+ fun trySend(element: Int): Any {
if (closedMessage !== null) return closedMessage!!
if (capacity == CONFLATED) {
if (resumeFirstReceiver(element)) return true
@@ -163,11 +163,11 @@ abstract class SequentialIntChannelBase(private val capacity: Int) : VerifierSta
return false
}
- suspend fun receive(): Any = poll() ?: suspendCancellableCoroutine { cont ->
+ suspend fun receive(): Any = tryReceive() ?: suspendCancellableCoroutine { cont ->
receivers.add(cont)
}
- fun poll(): Any? {
+ fun tryReceive(): Any? {
if (buffer.isNotEmpty()) {
val el = buffer.removeAt(0)
resumeFirstSender().also {
@@ -221,4 +221,4 @@ private fun <T> CancellableContinuation<T>.resume(res: T): Boolean {
val token = tryResume(res) ?: return false
completeResume(token)
return true
-} \ No newline at end of file
+}
diff --git a/kotlinx-coroutines-core/jvm/test/linearizability/LockFreeListLCStressTest.kt b/kotlinx-coroutines-core/jvm/test/lincheck/LockFreeListLincheckTest.kt
index 5f91c640..4f1bb6ad 100644
--- a/kotlinx-coroutines-core/jvm/test/linearizability/LockFreeListLCStressTest.kt
+++ b/kotlinx-coroutines-core/jvm/test/lincheck/LockFreeListLincheckTest.kt
@@ -3,18 +3,17 @@
*/
@file:Suppress("unused")
-package kotlinx.coroutines.linearizability
+package kotlinx.coroutines.lincheck
import kotlinx.coroutines.*
import kotlinx.coroutines.internal.*
import org.jetbrains.kotlinx.lincheck.annotations.*
import org.jetbrains.kotlinx.lincheck.annotations.Operation
import org.jetbrains.kotlinx.lincheck.paramgen.*
-import org.jetbrains.kotlinx.lincheck.verifier.*
-import kotlin.test.*
+import org.jetbrains.kotlinx.lincheck.strategy.managed.modelchecking.*
@Param(name = "value", gen = IntGen::class, conf = "1:5")
-class LockFreeListLCStressTest : VerifierState() {
+class LockFreeListLincheckTest : AbstractLincheckTest() {
class Node(val value: Int): LockFreeLinkedListNode()
private val q: LockFreeLinkedListHead = LockFreeLinkedListHead()
@@ -43,12 +42,12 @@ class LockFreeListLCStressTest : VerifierState() {
private fun Any.isSame(value: Int) = this is Node && this.value == value
- @Test
- fun testAddRemoveLinearizability() = LCStressOptionsDefault().check(this::class)
-
override fun extractState(): Any {
val elements = ArrayList<Int>()
q.forEach<Node> { elements.add(it.value) }
return elements
}
+
+ override fun ModelCheckingOptions.customize(isStressTest: Boolean) =
+ checkObstructionFreedom()
} \ No newline at end of file
diff --git a/kotlinx-coroutines-core/jvm/test/linearizability/LockFreeTaskQueueLCStressTest.kt b/kotlinx-coroutines-core/jvm/test/lincheck/LockFreeTaskQueueLincheckTest.kt
index de494cc1..2a9164e1 100644
--- a/kotlinx-coroutines-core/jvm/test/linearizability/LockFreeTaskQueueLCStressTest.kt
+++ b/kotlinx-coroutines-core/jvm/test/lincheck/LockFreeTaskQueueLincheckTest.kt
@@ -3,19 +3,21 @@
*/
@file:Suppress("unused")
-package kotlinx.coroutines.linearizability
+package kotlinx.coroutines.lincheck
import kotlinx.coroutines.*
import kotlinx.coroutines.internal.*
+import org.jetbrains.kotlinx.lincheck.*
import org.jetbrains.kotlinx.lincheck.annotations.*
import org.jetbrains.kotlinx.lincheck.annotations.Operation
import org.jetbrains.kotlinx.lincheck.paramgen.*
-import org.jetbrains.kotlinx.lincheck.verifier.*
+import org.jetbrains.kotlinx.lincheck.strategy.managed.modelchecking.*
import org.jetbrains.kotlinx.lincheck.verifier.quiescent.*
-import kotlin.test.*
@Param(name = "value", gen = IntGen::class, conf = "1:3")
-internal abstract class AbstractLockFreeTaskQueueWithoutRemoveLCStressTest protected constructor(singleConsumer: Boolean) : VerifierState() {
+internal abstract class AbstractLockFreeTaskQueueWithoutRemoveLincheckTest(
+ val singleConsumer: Boolean
+) : AbstractLincheckTest() {
@JvmField
protected val q = LockFreeTaskQueue<Int>(singleConsumer = singleConsumer)
@@ -25,20 +27,24 @@ internal abstract class AbstractLockFreeTaskQueueWithoutRemoveLCStressTest prote
@Operation
fun addLast(@Param(name = "value") value: Int) = q.addLast(value)
- @QuiescentConsistent
- @Operation(group = "consumer")
- fun removeFirstOrNull() = q.removeFirstOrNull()
+ override fun <O : Options<O, *>> O.customize(isStressTest: Boolean): O =
+ verifier(QuiescentConsistencyVerifier::class.java)
override fun extractState() = q.map { it } to q.isClosed()
- @Test
- fun testWithRemoveForQuiescentConsistency() = LCStressOptionsDefault()
- .verifier(QuiescentConsistencyVerifier::class.java)
- .check(this::class)
+ override fun ModelCheckingOptions.customize(isStressTest: Boolean) =
+ checkObstructionFreedom()
}
-@OpGroupConfig(name = "consumer", nonParallel = false)
-internal class MCLockFreeTaskQueueWithRemoveLCStressTest : AbstractLockFreeTaskQueueWithoutRemoveLCStressTest(singleConsumer = false)
+internal class MCLockFreeTaskQueueWithRemoveLincheckTest : AbstractLockFreeTaskQueueWithoutRemoveLincheckTest(singleConsumer = false) {
+ @QuiescentConsistent
+ @Operation(blocking = true)
+ fun removeFirstOrNull() = q.removeFirstOrNull()
+}
@OpGroupConfig(name = "consumer", nonParallel = true)
-internal class SCLockFreeTaskQueueWithRemoveLCStressTest : AbstractLockFreeTaskQueueWithoutRemoveLCStressTest(singleConsumer = true) \ No newline at end of file
+internal class SCLockFreeTaskQueueWithRemoveLincheckTest : AbstractLockFreeTaskQueueWithoutRemoveLincheckTest(singleConsumer = true) {
+ @QuiescentConsistent
+ @Operation(group = "consumer")
+ fun removeFirstOrNull() = q.removeFirstOrNull()
+} \ No newline at end of file
diff --git a/kotlinx-coroutines-core/jvm/test/lincheck/MutexLincheckTest.kt b/kotlinx-coroutines-core/jvm/test/lincheck/MutexLincheckTest.kt
new file mode 100644
index 00000000..a278985f
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/lincheck/MutexLincheckTest.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+@file:Suppress("unused")
+package kotlinx.coroutines.lincheck
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.sync.*
+import org.jetbrains.kotlinx.lincheck.*
+import org.jetbrains.kotlinx.lincheck.annotations.Operation
+import org.jetbrains.kotlinx.lincheck.strategy.managed.modelchecking.*
+
+class MutexLincheckTest : AbstractLincheckTest() {
+ private val mutex = Mutex()
+
+ @Operation
+ fun tryLock() = mutex.tryLock()
+
+ @Operation(promptCancellation = true)
+ suspend fun lock() = mutex.lock()
+
+ @Operation(handleExceptionsAsResult = [IllegalStateException::class])
+ fun unlock() = mutex.unlock()
+
+ override fun <O : Options<O, *>> O.customize(isStressTest: Boolean): O =
+ actorsBefore(0)
+
+ override fun ModelCheckingOptions.customize(isStressTest: Boolean) =
+ checkObstructionFreedom()
+
+ override fun extractState() = mutex.isLocked
+}
diff --git a/kotlinx-coroutines-core/jvm/test/linearizability/SegmentListRemoveLCStressTest.kt b/kotlinx-coroutines-core/jvm/test/lincheck/SegmentListRemoveLincheckTest.kt
index 5daed998..5a8d7b47 100644
--- a/kotlinx-coroutines-core/jvm/test/linearizability/SegmentListRemoveLCStressTest.kt
+++ b/kotlinx-coroutines-core/jvm/test/lincheck/SegmentListRemoveLincheckTest.kt
@@ -4,18 +4,16 @@
@file:Suppress("unused")
-package kotlinx.coroutines.linearizability
+package kotlinx.coroutines.lincheck
import kotlinx.coroutines.*
import kotlinx.coroutines.internal.*
+import org.jetbrains.kotlinx.lincheck.*
import org.jetbrains.kotlinx.lincheck.annotations.*
-import org.jetbrains.kotlinx.lincheck.annotations.Operation
import org.jetbrains.kotlinx.lincheck.paramgen.*
-import org.jetbrains.kotlinx.lincheck.verifier.*
-import org.junit.*
+import org.jetbrains.kotlinx.lincheck.strategy.managed.modelchecking.*
-
-class SegmentListRemoveLCStressTest : VerifierState() {
+class SegmentListRemoveLincheckTest : AbstractLincheckTest() {
private val q = SegmentBasedQueue<Int>()
private val segments: Array<OneElementSegment<Int>>
@@ -29,6 +27,9 @@ class SegmentListRemoveLCStressTest : VerifierState() {
segments[index].removeSegment()
}
+ override fun <O : Options<O, *>> O.customize(isStressTest: Boolean): O = this
+ .actorsBefore(0).actorsAfter(0)
+
override fun extractState() = segments.map { it.logicallyRemoved }
@Validate
@@ -37,9 +38,6 @@ class SegmentListRemoveLCStressTest : VerifierState() {
q.checkAllSegmentsAreNotLogicallyRemoved()
}
- @Test
- fun test() = LCStressOptionsDefault()
- .actorsBefore(0)
- .actorsAfter(0)
- .check(this::class)
+ override fun ModelCheckingOptions.customize(isStressTest: Boolean) =
+ checkObstructionFreedom()
} \ No newline at end of file
diff --git a/kotlinx-coroutines-core/jvm/test/linearizability/SegmentQueueLCStressTest.kt b/kotlinx-coroutines-core/jvm/test/lincheck/SegmentQueueLincheckTest.kt
index 89bf8dfa..76a59e39 100644
--- a/kotlinx-coroutines-core/jvm/test/linearizability/SegmentQueueLCStressTest.kt
+++ b/kotlinx-coroutines-core/jvm/test/lincheck/SegmentQueueLincheckTest.kt
@@ -3,18 +3,17 @@
*/
@file:Suppress("unused")
-package kotlinx.coroutines.linearizability
+package kotlinx.coroutines.lincheck
import kotlinx.coroutines.*
import kotlinx.coroutines.internal.SegmentBasedQueue
import org.jetbrains.kotlinx.lincheck.annotations.*
import org.jetbrains.kotlinx.lincheck.annotations.Operation
import org.jetbrains.kotlinx.lincheck.paramgen.*
-import org.jetbrains.kotlinx.lincheck.verifier.*
-import org.junit.*
+import org.jetbrains.kotlinx.lincheck.strategy.managed.modelchecking.*
@Param(name = "value", gen = IntGen::class, conf = "1:5")
-class SegmentQueueLCStressTest : VerifierState() {
+class SegmentQueueLincheckTest : AbstractLincheckTest() {
private val q = SegmentBasedQueue<Int>()
@Operation
@@ -40,6 +39,6 @@ class SegmentQueueLCStressTest : VerifierState() {
return elements to closed
}
- @Test
- fun test() = LCStressOptionsDefault().check(this::class)
+ override fun ModelCheckingOptions.customize(isStressTest: Boolean) =
+ checkObstructionFreedom()
} \ No newline at end of file
diff --git a/kotlinx-coroutines-core/jvm/test/lincheck/SemaphoreLincheckTest.kt b/kotlinx-coroutines-core/jvm/test/lincheck/SemaphoreLincheckTest.kt
new file mode 100644
index 00000000..2b471d7f
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/lincheck/SemaphoreLincheckTest.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+@file:Suppress("unused")
+package kotlinx.coroutines.lincheck
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.sync.*
+import org.jetbrains.kotlinx.lincheck.*
+import org.jetbrains.kotlinx.lincheck.annotations.Operation
+import org.jetbrains.kotlinx.lincheck.strategy.managed.modelchecking.*
+
+abstract class SemaphoreLincheckTestBase(permits: Int) : AbstractLincheckTest() {
+ private val semaphore = Semaphore(permits)
+
+ @Operation
+ fun tryAcquire() = semaphore.tryAcquire()
+
+ @Operation(promptCancellation = true, allowExtraSuspension = true)
+ suspend fun acquire() = semaphore.acquire()
+
+ @Operation(handleExceptionsAsResult = [IllegalStateException::class])
+ fun release() = semaphore.release()
+
+ override fun <O : Options<O, *>> O.customize(isStressTest: Boolean): O =
+ actorsBefore(0)
+
+ override fun extractState() = semaphore.availablePermits
+
+ override fun ModelCheckingOptions.customize(isStressTest: Boolean) =
+ checkObstructionFreedom()
+}
+
+class Semaphore1LincheckTest : SemaphoreLincheckTestBase(1)
+class Semaphore2LincheckTest : SemaphoreLincheckTestBase(2) \ No newline at end of file
diff --git a/kotlinx-coroutines-core/jvm/test/linearizability/MutexLCStressTest.kt b/kotlinx-coroutines-core/jvm/test/linearizability/MutexLCStressTest.kt
deleted file mode 100644
index 9542b5d8..00000000
--- a/kotlinx-coroutines-core/jvm/test/linearizability/MutexLCStressTest.kt
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
- */
-@file:Suppress("unused")
-package kotlinx.coroutines.linearizability
-
-import kotlinx.coroutines.*
-import kotlinx.coroutines.sync.*
-import org.jetbrains.kotlinx.lincheck.annotations.Operation
-import org.jetbrains.kotlinx.lincheck.verifier.*
-import org.junit.*
-
-class MutexLCStressTest : VerifierState() {
- private val mutex = Mutex()
-
- @Operation
- fun tryLock() = mutex.tryLock()
-
- @Operation
- suspend fun lock() = mutex.lock()
-
- @Operation(handleExceptionsAsResult = [IllegalStateException::class])
- fun unlock() = mutex.unlock()
-
- @Test
- fun test() = LCStressOptionsDefault()
- .actorsBefore(0)
- .check(this::class)
-
- override fun extractState() = mutex.isLocked
-} \ No newline at end of file
diff --git a/kotlinx-coroutines-core/jvm/test/linearizability/SemaphoreLCStressTest.kt b/kotlinx-coroutines-core/jvm/test/linearizability/SemaphoreLCStressTest.kt
deleted file mode 100644
index 52902f49..00000000
--- a/kotlinx-coroutines-core/jvm/test/linearizability/SemaphoreLCStressTest.kt
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
- */
-@file:Suppress("unused")
-package kotlinx.coroutines.linearizability
-
-import kotlinx.coroutines.*
-import kotlinx.coroutines.sync.*
-import org.jetbrains.kotlinx.lincheck.annotations.Operation
-import org.jetbrains.kotlinx.lincheck.verifier.*
-import org.junit.*
-
-abstract class SemaphoreLCStressTestBase(permits: Int) : VerifierState() {
- private val semaphore = Semaphore(permits)
-
- @Operation
- fun tryAcquire() = semaphore.tryAcquire()
-
- @Operation
- suspend fun acquire() = semaphore.acquire()
-
- @Operation(handleExceptionsAsResult = [IllegalStateException::class])
- fun release() = semaphore.release()
-
- @Test
- fun test() = LCStressOptionsDefault()
- .actorsBefore(0)
- .check(this::class)
-
- override fun extractState() = semaphore.availablePermits
-}
-
-class Semaphore1LCStressTest : SemaphoreLCStressTestBase(1)
-class Semaphore2LCStressTest : SemaphoreLCStressTestBase(2) \ No newline at end of file
diff --git a/kotlinx-coroutines-core/jvm/test/scheduling/BlockingCoroutineDispatcherMixedStealingStressTest.kt b/kotlinx-coroutines-core/jvm/test/scheduling/BlockingCoroutineDispatcherMixedStealingStressTest.kt
index 1fe0d838..3a55f8c4 100644
--- a/kotlinx-coroutines-core/jvm/test/scheduling/BlockingCoroutineDispatcherMixedStealingStressTest.kt
+++ b/kotlinx-coroutines-core/jvm/test/scheduling/BlockingCoroutineDispatcherMixedStealingStressTest.kt
@@ -77,4 +77,4 @@ class BlockingCoroutineDispatcherMixedStealingStressTest : SchedulerTestBase() {
cpuBlocker.await()
}
}
-} \ No newline at end of file
+}
diff --git a/kotlinx-coroutines-core/jvm/test/scheduling/BlockingCoroutineDispatcherTest.kt b/kotlinx-coroutines-core/jvm/test/scheduling/BlockingCoroutineDispatcherTest.kt
index f31752c8..fe09440f 100644
--- a/kotlinx-coroutines-core/jvm/test/scheduling/BlockingCoroutineDispatcherTest.kt
+++ b/kotlinx-coroutines-core/jvm/test/scheduling/BlockingCoroutineDispatcherTest.kt
@@ -101,7 +101,7 @@ class BlockingCoroutineDispatcherTest : SchedulerTestBase() {
firstBarrier.await()
secondBarrier.await()
blockingTasks.joinAll()
- checkPoolThreadsCreated(21..22)
+ checkPoolThreadsCreated(21 /* blocking tasks + 1 for CPU */..20 + CORES_COUNT)
}
@Test
@@ -122,7 +122,7 @@ class BlockingCoroutineDispatcherTest : SchedulerTestBase() {
barrier.await()
blockingTasks.joinAll()
// There may be race when multiple CPU threads are trying to lazily created one more
- checkPoolThreadsCreated(104..120)
+ checkPoolThreadsCreated(101..100 + CORES_COUNT)
}
@Test
diff --git a/kotlinx-coroutines-core/jvm/test/scheduling/SchedulerTestBase.kt b/kotlinx-coroutines-core/jvm/test/scheduling/SchedulerTestBase.kt
index bfabf5b2..dd969bdd 100644
--- a/kotlinx-coroutines-core/jvm/test/scheduling/SchedulerTestBase.kt
+++ b/kotlinx-coroutines-core/jvm/test/scheduling/SchedulerTestBase.kt
@@ -39,17 +39,9 @@ abstract class SchedulerTestBase : TestBase() {
)
}
- /**
- * Asserts that any number of pool worker threads in [range] exists at the time of method invocation
- */
- fun checkPoolThreadsExist(range: IntRange) {
- val threads = Thread.getAllStackTraces().keys.asSequence().filter { it is CoroutineScheduler.Worker }.count()
- assertTrue(threads in range, "Expected threads in $range interval, but has $threads")
- }
-
private fun maxSequenceNumber(): Int? {
return Thread.getAllStackTraces().keys.asSequence().filter { it is CoroutineScheduler.Worker }
- .map { sequenceNumber(it.name) }.max()
+ .map { sequenceNumber(it.name) }.maxOrNull()
}
private fun sequenceNumber(threadName: String): Int {
@@ -105,4 +97,4 @@ abstract class SchedulerTestBase : TestBase() {
}
}
}
-} \ No newline at end of file
+}
diff --git a/kotlinx-coroutines-core/jvm/test/scheduling/TestTimeSource.kt b/kotlinx-coroutines-core/jvm/test/scheduling/TestTimeSource.kt
index a5c83d32..233e4420 100644
--- a/kotlinx-coroutines-core/jvm/test/scheduling/TestTimeSource.kt
+++ b/kotlinx-coroutines-core/jvm/test/scheduling/TestTimeSource.kt
@@ -1,11 +1,11 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.scheduling
-internal class TestTimeSource(var time: Long) : TimeSource() {
+internal class TestTimeSource(var time: Long) : SchedulerTimeSource() {
override fun nanoTime() = time
diff --git a/kotlinx-coroutines-core/jvm/test/selects/SelectDeadlockLFStressTest.kt b/kotlinx-coroutines-core/jvm/test/selects/SelectDeadlockLFStressTest.kt
deleted file mode 100644
index 4497bec5..00000000
--- a/kotlinx-coroutines-core/jvm/test/selects/SelectDeadlockLFStressTest.kt
+++ /dev/null
@@ -1,101 +0,0 @@
-/*
- * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
- */
-
-package kotlinx.coroutines.selects
-
-import kotlinx.atomicfu.*
-import kotlinx.coroutines.*
-import kotlinx.coroutines.channels.*
-import org.junit.*
-import org.junit.Ignore
-import org.junit.Test
-import kotlin.math.*
-import kotlin.test.*
-
-/**
- * A stress-test on lock-freedom of select sending/receiving into opposite channels.
- */
-class SelectDeadlockLFStressTest : TestBase() {
- private val env = LockFreedomTestEnvironment("SelectDeadlockLFStressTest", allowSuspendedThreads = 1)
- private val nSeconds = 5 * stressTestMultiplier
-
- private val c1 = Channel<Long>()
- private val c2 = Channel<Long>()
-
- @Test
- fun testLockFreedom() = testScenarios(
- "s1r2",
- "s2r1",
- "r1s2",
- "r2s1"
- )
-
- private fun testScenarios(vararg scenarios: String) {
- env.onCompletion {
- c1.cancel(TestCompleted())
- c2.cancel(TestCompleted())
- }
- val t = scenarios.mapIndexed { i, scenario ->
- val idx = i + 1L
- TestDef(idx, "$idx [$scenario]", scenario)
- }
- t.forEach { it.test() }
- env.performTest(nSeconds) {
- t.forEach { println(it) }
- }
- }
-
- private inner class TestDef(
- var sendIndex: Long = 0L,
- val name: String,
- scenario: String
- ) {
- var receiveIndex = 0L
-
- val clauses: List<SelectBuilder<Unit>.() -> Unit> = ArrayList<SelectBuilder<Unit>.() -> Unit>().apply {
- require(scenario.length % 2 == 0)
- for (i in scenario.indices step 2) {
- val ch = when (val c = scenario[i + 1]) {
- '1' -> c1
- '2' -> c2
- else -> error("Channel '$c'")
- }
- val clause = when (val op = scenario[i]) {
- 's' -> fun SelectBuilder<Unit>.() { sendClause(ch) }
- 'r' -> fun SelectBuilder<Unit>.() { receiveClause(ch) }
- else -> error("Operation '$op'")
- }
- add(clause)
- }
- }
-
- fun test() = env.testThread(name) {
- doSendReceive()
- }
-
- private suspend fun doSendReceive() {
- try {
- select<Unit> {
- for (clause in clauses) clause()
- }
- } catch (e: TestCompleted) {
- assertTrue(env.isCompleted)
- }
- }
-
- private fun SelectBuilder<Unit>.sendClause(c: Channel<Long>) =
- c.onSend(sendIndex) {
- sendIndex += 4L
- }
-
- private fun SelectBuilder<Unit>.receiveClause(c: Channel<Long>) =
- c.onReceive { i ->
- receiveIndex = max(i, receiveIndex)
- }
-
- override fun toString(): String = "$name: send=$sendIndex, received=$receiveIndex"
- }
-
- private class TestCompleted : CancellationException()
-} \ No newline at end of file
diff --git a/kotlinx-coroutines-core/jvm/test/sync/MutexStressTest.kt b/kotlinx-coroutines-core/jvm/test/sync/MutexStressTest.kt
index bb713b25..027f3c51 100644
--- a/kotlinx-coroutines-core/jvm/test/sync/MutexStressTest.kt
+++ b/kotlinx-coroutines-core/jvm/test/sync/MutexStressTest.kt
@@ -90,4 +90,21 @@ class MutexStressTest : TestBase() {
}
}
}
-} \ No newline at end of file
+
+ @Test
+ fun testShouldBeUnlockedOnCancellation() = runTest {
+ val mutex = Mutex()
+ val n = 1000 * stressTestMultiplier
+ repeat(n) {
+ val job = launch(Dispatchers.Default) {
+ mutex.lock()
+ mutex.unlock()
+ }
+ mutex.withLock {
+ job.cancel()
+ }
+ job.join()
+ assertFalse { mutex.isLocked }
+ }
+ }
+}
diff --git a/kotlinx-coroutines-core/jvm/test/sync/SemaphoreStressTest.kt b/kotlinx-coroutines-core/jvm/test/sync/SemaphoreStressTest.kt
index 374a1e3d..2ceed64b 100644
--- a/kotlinx-coroutines-core/jvm/test/sync/SemaphoreStressTest.kt
+++ b/kotlinx-coroutines-core/jvm/test/sync/SemaphoreStressTest.kt
@@ -2,7 +2,7 @@ package kotlinx.coroutines.sync
import kotlinx.coroutines.*
import org.junit.Test
-import kotlin.test.assertEquals
+import kotlin.test.*
class SemaphoreStressTest : TestBase() {
@Test
@@ -90,4 +90,21 @@ class SemaphoreStressTest : TestBase() {
}
}
}
+
+ @Test
+ fun testShouldBeUnlockedOnCancellation() = runTest {
+ val semaphore = Semaphore(1)
+ val n = 1000 * stressTestMultiplier
+ repeat(n) {
+ val job = launch(Dispatchers.Default) {
+ semaphore.acquire()
+ semaphore.release()
+ }
+ semaphore.withPermit {
+ job.cancel()
+ }
+ job.join()
+ assertTrue { semaphore.availablePermits == 1 }
+ }
+ }
}
diff --git a/kotlinx-coroutines-core/knit.properties b/kotlinx-coroutines-core/knit.properties
index 93ce87db..5b57398b 100644
--- a/kotlinx-coroutines-core/knit.properties
+++ b/kotlinx-coroutines-core/knit.properties
@@ -1,5 +1,5 @@
#
-# Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+# Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
#
knit.package=kotlinx.coroutines.examples
diff --git a/kotlinx-coroutines-core/native/src/Builders.kt b/kotlinx-coroutines-core/native/src/Builders.kt
index 3554dc60..30c187fa 100644
--- a/kotlinx-coroutines-core/native/src/Builders.kt
+++ b/kotlinx-coroutines-core/native/src/Builders.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines
@@ -53,7 +53,7 @@ public fun <T> runBlocking(context: CoroutineContext = EmptyCoroutineContext, bl
private class BlockingCoroutine<T>(
parentContext: CoroutineContext,
private val eventLoop: EventLoop?
-) : AbstractCoroutine<T>(parentContext, true) {
+) : AbstractCoroutine<T>(parentContext, true, true) {
override val isScopedCoroutine: Boolean get() = true
@Suppress("UNCHECKED_CAST")
diff --git a/kotlinx-coroutines-core/native/src/CompletionHandler.kt b/kotlinx-coroutines-core/native/src/CompletionHandler.kt
index 706f6c49..4835f796 100644
--- a/kotlinx-coroutines-core/native/src/CompletionHandler.kt
+++ b/kotlinx-coroutines-core/native/src/CompletionHandler.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines
diff --git a/kotlinx-coroutines-core/native/src/CoroutineContext.kt b/kotlinx-coroutines-core/native/src/CoroutineContext.kt
index 4ec1289e..47afd8ad 100644
--- a/kotlinx-coroutines-core/native/src/CoroutineContext.kt
+++ b/kotlinx-coroutines-core/native/src/CoroutineContext.kt
@@ -1,9 +1,10 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines
+import kotlinx.coroutines.internal.*
import kotlin.coroutines.*
import kotlin.native.concurrent.*
@@ -38,5 +39,13 @@ public actual fun CoroutineScope.newCoroutineContext(context: CoroutineContext):
// No debugging facilities on native
internal actual inline fun <T> withCoroutineContext(context: CoroutineContext, countOrElement: Any?, block: () -> T): T = block()
+internal actual inline fun <T> withContinuationContext(continuation: Continuation<*>, countOrElement: Any?, block: () -> T): T = block()
internal actual fun Continuation<*>.toDebugString(): String = toString()
internal actual val CoroutineContext.coroutineName: String? get() = null // not supported on native
+
+internal actual class UndispatchedCoroutine<in T> actual constructor(
+ context: CoroutineContext,
+ uCont: Continuation<T>
+) : ScopeCoroutine<T>(context, uCont) {
+ override fun afterResume(state: Any?) = uCont.resumeWith(recoverResult(state, uCont))
+}
diff --git a/kotlinx-coroutines-core/native/src/CoroutineExceptionHandlerImpl.kt b/kotlinx-coroutines-core/native/src/CoroutineExceptionHandlerImpl.kt
index dff845b2..b0aa8633 100644
--- a/kotlinx-coroutines-core/native/src/CoroutineExceptionHandlerImpl.kt
+++ b/kotlinx-coroutines-core/native/src/CoroutineExceptionHandlerImpl.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines
diff --git a/kotlinx-coroutines-core/native/src/Debug.kt b/kotlinx-coroutines-core/native/src/Debug.kt
index 1fa0ec7f..f17c2ed7 100644
--- a/kotlinx-coroutines-core/native/src/Debug.kt
+++ b/kotlinx-coroutines-core/native/src/Debug.kt
@@ -1,18 +1,16 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines
import kotlin.math.*
+import kotlin.native.*
internal actual val DEBUG: Boolean = false
-internal actual val Any.hexAddress: String get() = abs(id().let { if (it == Int.MIN_VALUE) 0 else it }).toString(16)
+internal actual val Any.hexAddress: String get() = identityHashCode().toUInt().toString(16)
internal actual val Any.classSimpleName: String get() = this::class.simpleName ?: "Unknown"
-@SymbolName("Kotlin_Any_hashCode")
-public external fun Any.id(): Int // Note: can return negative value on K/N
-
internal actual inline fun assert(value: () -> Boolean) {}
diff --git a/kotlinx-coroutines-core/native/src/Dispatchers.kt b/kotlinx-coroutines-core/native/src/Dispatchers.kt
index c06b7c2f..4e5facfe 100644
--- a/kotlinx-coroutines-core/native/src/Dispatchers.kt
+++ b/kotlinx-coroutines-core/native/src/Dispatchers.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines
diff --git a/kotlinx-coroutines-core/native/src/EventLoop.kt b/kotlinx-coroutines-core/native/src/EventLoop.kt
index b397d6f1..925cbe99 100644
--- a/kotlinx-coroutines-core/native/src/EventLoop.kt
+++ b/kotlinx-coroutines-core/native/src/EventLoop.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines
diff --git a/kotlinx-coroutines-core/native/src/Exceptions.kt b/kotlinx-coroutines-core/native/src/Exceptions.kt
index c82199a4..da9979b6 100644
--- a/kotlinx-coroutines-core/native/src/Exceptions.kt
+++ b/kotlinx-coroutines-core/native/src/Exceptions.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines
@@ -10,12 +10,7 @@ package kotlinx.coroutines
* **It is not printed to console/log by default uncaught exception handler**.
* (see [CoroutineExceptionHandler]).
*/
-public actual open class CancellationException(
- message: String?,
- cause: Throwable?
-) : IllegalStateException(message, cause) {
- public actual constructor(message: String?) : this(message, null)
-}
+public actual typealias CancellationException = kotlin.coroutines.cancellation.CancellationException
/**
* Thrown by cancellable suspending functions if the [Job] of the coroutine is cancelled or completed
diff --git a/kotlinx-coroutines-core/native/src/Runnable.kt b/kotlinx-coroutines-core/native/src/Runnable.kt
index 19710f97..b8e6980b 100644
--- a/kotlinx-coroutines-core/native/src/Runnable.kt
+++ b/kotlinx-coroutines-core/native/src/Runnable.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines
diff --git a/kotlinx-coroutines-core/native/src/SchedulerTask.kt b/kotlinx-coroutines-core/native/src/SchedulerTask.kt
index 4154d53e..781e3221 100644
--- a/kotlinx-coroutines-core/native/src/SchedulerTask.kt
+++ b/kotlinx-coroutines-core/native/src/SchedulerTask.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines
diff --git a/kotlinx-coroutines-core/native/src/WorkerMain.native.kt b/kotlinx-coroutines-core/native/src/WorkerMain.native.kt
index 84cc9f42..bf8fe6ae 100644
--- a/kotlinx-coroutines-core/native/src/WorkerMain.native.kt
+++ b/kotlinx-coroutines-core/native/src/WorkerMain.native.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines
diff --git a/kotlinx-coroutines-core/native/src/flow/internal/FlowExceptions.kt b/kotlinx-coroutines-core/native/src/flow/internal/FlowExceptions.kt
index 47054719..bc7a1ffc 100644
--- a/kotlinx-coroutines-core/native/src/flow/internal/FlowExceptions.kt
+++ b/kotlinx-coroutines-core/native/src/flow/internal/FlowExceptions.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.flow.internal
diff --git a/kotlinx-coroutines-core/native/src/flow/internal/SafeCollector.kt b/kotlinx-coroutines-core/native/src/flow/internal/SafeCollector.kt
index ace633cc..b74b547d 100644
--- a/kotlinx-coroutines-core/native/src/flow/internal/SafeCollector.kt
+++ b/kotlinx-coroutines-core/native/src/flow/internal/SafeCollector.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.flow.internal
diff --git a/kotlinx-coroutines-core/native/src/internal/Concurrent.kt b/kotlinx-coroutines-core/native/src/internal/Concurrent.kt
index 486dc8f0..b379c6ac 100644
--- a/kotlinx-coroutines-core/native/src/internal/Concurrent.kt
+++ b/kotlinx-coroutines-core/native/src/internal/Concurrent.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.internal
diff --git a/kotlinx-coroutines-core/native/src/internal/CopyOnWriteList.kt b/kotlinx-coroutines-core/native/src/internal/CopyOnWriteList.kt
index b925317b..30f063a5 100644
--- a/kotlinx-coroutines-core/native/src/internal/CopyOnWriteList.kt
+++ b/kotlinx-coroutines-core/native/src/internal/CopyOnWriteList.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.internal
diff --git a/kotlinx-coroutines-core/native/src/internal/LinkedList.kt b/kotlinx-coroutines-core/native/src/internal/LinkedList.kt
index 99ab042f..a8aed044 100644
--- a/kotlinx-coroutines-core/native/src/internal/LinkedList.kt
+++ b/kotlinx-coroutines-core/native/src/internal/LinkedList.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
@file:Suppress("NO_EXPLICIT_RETURN_TYPE_IN_API_MODE", "NO_EXPLICIT_VISIBILITY_IN_API_MODE")
diff --git a/kotlinx-coroutines-core/native/src/internal/LocalAtomics.kt b/kotlinx-coroutines-core/native/src/internal/LocalAtomics.kt
index 398cb63b..c8c6fffe 100644
--- a/kotlinx-coroutines-core/native/src/internal/LocalAtomics.kt
+++ b/kotlinx-coroutines-core/native/src/internal/LocalAtomics.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.internal
diff --git a/kotlinx-coroutines-core/native/src/internal/ProbesSupport.kt b/kotlinx-coroutines-core/native/src/internal/ProbesSupport.kt
index a13a141f..47d3fdba 100644
--- a/kotlinx-coroutines-core/native/src/internal/ProbesSupport.kt
+++ b/kotlinx-coroutines-core/native/src/internal/ProbesSupport.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.internal
diff --git a/kotlinx-coroutines-core/native/src/internal/StackTraceRecovery.kt b/kotlinx-coroutines-core/native/src/internal/StackTraceRecovery.kt
index 49f043da..d93af9f4 100644
--- a/kotlinx-coroutines-core/native/src/internal/StackTraceRecovery.kt
+++ b/kotlinx-coroutines-core/native/src/internal/StackTraceRecovery.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.internal
diff --git a/kotlinx-coroutines-core/native/src/internal/Synchronized.kt b/kotlinx-coroutines-core/native/src/internal/Synchronized.kt
index 0911dbe1..dcbb2021 100644
--- a/kotlinx-coroutines-core/native/src/internal/Synchronized.kt
+++ b/kotlinx-coroutines-core/native/src/internal/Synchronized.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.internal
diff --git a/kotlinx-coroutines-core/native/src/internal/SystemProps.kt b/kotlinx-coroutines-core/native/src/internal/SystemProps.kt
index 564630f6..4fb334ed 100644
--- a/kotlinx-coroutines-core/native/src/internal/SystemProps.kt
+++ b/kotlinx-coroutines-core/native/src/internal/SystemProps.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.internal
diff --git a/kotlinx-coroutines-core/native/src/internal/ThreadContext.kt b/kotlinx-coroutines-core/native/src/internal/ThreadContext.kt
index 4a9513ab..2370e42f 100644
--- a/kotlinx-coroutines-core/native/src/internal/ThreadContext.kt
+++ b/kotlinx-coroutines-core/native/src/internal/ThreadContext.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.internal
diff --git a/kotlinx-coroutines-core/native/src/internal/ThreadLocal.kt b/kotlinx-coroutines-core/native/src/internal/ThreadLocal.kt
index 09f501a4..e1825d67 100644
--- a/kotlinx-coroutines-core/native/src/internal/ThreadLocal.kt
+++ b/kotlinx-coroutines-core/native/src/internal/ThreadLocal.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.internal
diff --git a/kotlinx-coroutines-core/native/test/TestBase.kt b/kotlinx-coroutines-core/native/test/TestBase.kt
index 890f029c..4ffa6c0b 100644
--- a/kotlinx-coroutines-core/native/test/TestBase.kt
+++ b/kotlinx-coroutines-core/native/test/TestBase.kt
@@ -7,7 +7,11 @@ package kotlinx.coroutines
public actual val isStressTest: Boolean = false
public actual val stressTestMultiplier: Int = 1
+@Suppress("ACTUAL_WITHOUT_EXPECT")
+public actual typealias TestResult = Unit
+
public actual open class TestBase actual constructor() {
+ public actual val isBoundByJsTestTimeout = false
private var actionIndex = 0
private var finished = false
private var error: Throwable? = null
@@ -70,7 +74,7 @@ public actual open class TestBase actual constructor() {
expected: ((Throwable) -> Boolean)? = null,
unhandled: List<(Throwable) -> Boolean> = emptyList(),
block: suspend CoroutineScope.() -> Unit
- ) {
+ ): TestResult {
var exCount = 0
var ex: Throwable? = null
try {
diff --git a/kotlinx-coroutines-core/nativeDarwin/src/WorkerMain.kt b/kotlinx-coroutines-core/nativeDarwin/src/WorkerMain.kt
index 3445cb98..010bd030 100644
--- a/kotlinx-coroutines-core/nativeDarwin/src/WorkerMain.kt
+++ b/kotlinx-coroutines-core/nativeDarwin/src/WorkerMain.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines
diff --git a/kotlinx-coroutines-core/nativeOther/src/WorkerMain.kt b/kotlinx-coroutines-core/nativeOther/src/WorkerMain.kt
index cac0530e..f3bd99a4 100644
--- a/kotlinx-coroutines-core/nativeOther/src/WorkerMain.kt
+++ b/kotlinx-coroutines-core/nativeOther/src/WorkerMain.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines
diff --git a/kotlinx-coroutines-debug/README.md b/kotlinx-coroutines-debug/README.md
index 5525f912..cd71f580 100644
--- a/kotlinx-coroutines-debug/README.md
+++ b/kotlinx-coroutines-debug/README.md
@@ -61,7 +61,7 @@ stacktraces will be dumped to the console.
### Using as JVM agent
Debug module can also be used as a standalone JVM agent to enable debug probes on the application startup.
-You can run your application with an additional argument: `-javaagent:kotlinx-coroutines-debug-1.4.1.jar`.
+You can run your application with an additional argument: `-javaagent:kotlinx-coroutines-debug-1.5.2.jar`.
Additionally, on Linux and Mac OS X you can use `kill -5 $pid` command in order to force your application to print all alive coroutines.
When used as Java agent, `"kotlinx.coroutines.debug.enable.creation.stack.trace"` system property can be used to control
[DebugProbes.enableCreationStackTraces] along with agent startup.
@@ -138,8 +138,8 @@ Coroutine "coroutine#2":DeferredCoroutine{Active}@289d1c02, state: SUSPENDED
Dumping only deferred
"coroutine#2":DeferredCoroutine{Active}, continuation is SUSPENDED at line kotlinx.coroutines.DeferredCoroutine.await$suspendImpl(Builders.common.kt:99)
- "coroutine#3":DeferredCoroutine{Active}, continuation is SUSPENDED at line ExampleKt.computeOne(Example.kt:14)
- "coroutine#4":DeferredCoroutine{Active}, continuation is SUSPENDED at line ExampleKt.computeTwo(Example.kt:19)
+ "coroutine#3":DeferredCoroutine{Active}, continuation is SUSPENDED at line ExampleKt.computeOne(Example.kt:14)
+ "coroutine#4":DeferredCoroutine{Active}, continuation is SUSPENDED at line ExampleKt.computeTwo(Example.kt:19)
```
### Status of the API
@@ -264,10 +264,13 @@ More than one file was found with OS independent path 'win32-x86-64/attach_hotsp
-->
<!--- MODULE kotlinx-coroutines-core -->
<!--- INDEX kotlinx.coroutines -->
+
[Job]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job/index.html
[CoroutineScope]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-scope/index.html
+
<!--- MODULE kotlinx-coroutines-debug -->
<!--- INDEX kotlinx.coroutines.debug -->
+
[DebugProbes]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-debug/kotlinx.coroutines.debug/-debug-probes/index.html
[DebugProbes.install]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-debug/kotlinx.coroutines.debug/-debug-probes/install.html
[DebugProbes.dumpCoroutines]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-debug/kotlinx.coroutines.debug/-debug-probes/dump-coroutines.html
@@ -275,6 +278,9 @@ More than one file was found with OS independent path 'win32-x86-64/attach_hotsp
[DebugProbes.printJob]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-debug/kotlinx.coroutines.debug/-debug-probes/print-job.html
[DebugProbes.printScope]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-debug/kotlinx.coroutines.debug/-debug-probes/print-scope.html
[DebugProbes.enableCreationStackTraces]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-debug/kotlinx.coroutines.debug/-debug-probes/enable-creation-stack-traces.html
+
<!--- INDEX kotlinx.coroutines.debug.junit4 -->
+
[CoroutinesTimeout]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-debug/kotlinx.coroutines.debug.junit4/-coroutines-timeout/index.html
+
<!--- END -->
diff --git a/kotlinx-coroutines-debug/api/kotlinx-coroutines-debug.api b/kotlinx-coroutines-debug/api/kotlinx-coroutines-debug.api
index b6056c41..5bf70626 100644
--- a/kotlinx-coroutines-debug/api/kotlinx-coroutines-debug.api
+++ b/kotlinx-coroutines-debug/api/kotlinx-coroutines-debug.api
@@ -61,3 +61,8 @@ public final class kotlinx/coroutines/debug/junit4/CoroutinesTimeout$Companion {
public static synthetic fun seconds$default (Lkotlinx/coroutines/debug/junit4/CoroutinesTimeout$Companion;JZZILjava/lang/Object;)Lkotlinx/coroutines/debug/junit4/CoroutinesTimeout;
}
+public abstract interface annotation class kotlinx/coroutines/debug/junit5/CoroutinesTimeout : java/lang/annotation/Annotation {
+ public abstract fun cancelOnTimeout ()Z
+ public abstract fun testTimeoutMs ()J
+}
+
diff --git a/kotlinx-coroutines-debug/build.gradle b/kotlinx-coroutines-debug/build.gradle
index ab7f28c6..43d94d18 100644
--- a/kotlinx-coroutines-debug/build.gradle
+++ b/kotlinx-coroutines-debug/build.gradle
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
apply plugin: "com.github.johnrengelman.shadow"
@@ -20,14 +20,23 @@ configurations {
dependencies {
compileOnly "junit:junit:$junit_version"
+ compileOnly "org.junit.jupiter:junit-jupiter-api:$junit5_version"
+ testCompile "org.junit.jupiter:junit-jupiter-engine:$junit5_version"
+ testCompile "org.junit.platform:junit-platform-testkit:1.7.0"
shadowDeps "net.bytebuddy:byte-buddy:$byte_buddy_version"
shadowDeps "net.bytebuddy:byte-buddy-agent:$byte_buddy_version"
compileOnly "io.projectreactor.tools:blockhound:$blockhound_version"
- testCompile "io.projectreactor.tools:blockhound:$blockhound_version"
+ testImplementation "io.projectreactor.tools:blockhound:$blockhound_version"
api "net.java.dev.jna:jna:$jna_version"
api "net.java.dev.jna:jna-platform:$jna_version"
}
+java {
+ /* This is needed to be able to run JUnit5 tests. Otherwise, Gradle complains that it can't find the
+ JVM1.6-compatible version of the `junit-jupiter-api` artifact. */
+ disableAutoTargetJvm()
+}
+
jar {
manifest {
attributes "Premain-Class": "kotlinx.coroutines.debug.AgentPremain"
diff --git a/kotlinx-coroutines-debug/src/CoroutineInfo.kt b/kotlinx-coroutines-debug/src/CoroutineInfo.kt
index ce1478ad..62728ada 100644
--- a/kotlinx-coroutines-debug/src/CoroutineInfo.kt
+++ b/kotlinx-coroutines-debug/src/CoroutineInfo.kt
@@ -1,7 +1,7 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
-@file:Suppress("NO_EXPLICIT_VISIBILITY_IN_API_MODE", "INVISIBLE_MEMBER", "INVISIBLE_REFERENCE", "UNUSED")
+@file:Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE", "UNUSED")
package kotlinx.coroutines.debug
import kotlinx.coroutines.*
diff --git a/kotlinx-coroutines-debug/src/CoroutinesBlockHoundIntegration.kt b/kotlinx-coroutines-debug/src/CoroutinesBlockHoundIntegration.kt
index 091e8eb1..190476c4 100644
--- a/kotlinx-coroutines-debug/src/CoroutinesBlockHoundIntegration.kt
+++ b/kotlinx-coroutines-debug/src/CoroutinesBlockHoundIntegration.kt
@@ -1,3 +1,7 @@
+/*
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
@file:Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER")
package kotlinx.coroutines.debug
@@ -14,6 +18,7 @@ public class CoroutinesBlockHoundIntegration : BlockHoundIntegration {
allowBlockingWhenEnqueuingTasks()
allowServiceLoaderInvocationsOnInit()
allowBlockingCallsInReflectionImpl()
+ allowBlockingCallsInDebugProbes()
/* The predicates that define that BlockHound should only report blocking calls from threads that are part of
the coroutine thread pool and currently execute a CPU-bound coroutine computation. */
addDynamicThreadPredicate { isSchedulerWorker(it) }
@@ -45,6 +50,17 @@ public class CoroutinesBlockHoundIntegration : BlockHoundIntegration {
}
/**
+ * Allow blocking calls inside [kotlinx.coroutines.debug.internal.DebugProbesImpl].
+ */
+ private fun BlockHound.Builder.allowBlockingCallsInDebugProbes() {
+ for (method in listOf("install", "uninstall", "hierarchyToString", "dumpCoroutinesInfo", "dumpDebuggerInfo",
+ "dumpCoroutinesSynchronized", "updateRunningState", "updateState"))
+ {
+ allowBlockingCallsInside("kotlinx.coroutines.debug.internal.DebugProbesImpl", method)
+ }
+ }
+
+ /**
* Allows blocking inside [kotlinx.coroutines.internal.ThreadSafeHeap].
*/
private fun BlockHound.Builder.allowBlockingCallsInThreadSafeHeap() {
diff --git a/kotlinx-coroutines-debug/src/DebugProbes.kt b/kotlinx-coroutines-debug/src/DebugProbes.kt
index 254385f9..ed346d81 100644
--- a/kotlinx-coroutines-debug/src/DebugProbes.kt
+++ b/kotlinx-coroutines-debug/src/DebugProbes.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
@file:Suppress("UNUSED", "INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
@@ -143,10 +143,3 @@ public object DebugProbes {
*/
public fun dumpCoroutines(out: PrintStream = System.out): Unit = DebugProbesImpl.dumpCoroutines(out)
}
-
-// Stubs which are injected as coroutine probes. Require direct match of signatures
-internal fun probeCoroutineResumed(frame: Continuation<*>) = DebugProbesImpl.probeCoroutineResumed(frame)
-
-internal fun probeCoroutineSuspended(frame: Continuation<*>) = DebugProbesImpl.probeCoroutineSuspended(frame)
-internal fun <T> probeCoroutineCreated(completion: Continuation<T>): Continuation<T> =
- DebugProbesImpl.probeCoroutineCreated(completion)
diff --git a/kotlinx-coroutines-debug/src/internal/Attach.kt b/kotlinx-coroutines-debug/src/internal/Attach.kt
index cd4cc2a5..f1cc96e6 100644
--- a/kotlinx-coroutines-debug/src/internal/Attach.kt
+++ b/kotlinx-coroutines-debug/src/internal/Attach.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
@file:Suppress("unused")
package kotlinx.coroutines.debug.internal
@@ -20,7 +20,7 @@ internal class ByteBuddyDynamicAttach : Function1<Boolean, Unit> {
private fun attach() {
ByteBuddyAgent.install(ByteBuddyAgent.AttachmentProvider.ForEmulatedAttachment.INSTANCE)
val cl = Class.forName("kotlin.coroutines.jvm.internal.DebugProbesKt")
- val cl2 = Class.forName("kotlinx.coroutines.debug.DebugProbesKt")
+ val cl2 = Class.forName("kotlinx.coroutines.debug.internal.DebugProbesKt")
ByteBuddy()
.redefine(cl2)
diff --git a/kotlinx-coroutines-debug/src/internal/NoOpProbes.kt b/kotlinx-coroutines-debug/src/internal/NoOpProbes.kt
index 4854f5d7..34f1afec 100644
--- a/kotlinx-coroutines-debug/src/internal/NoOpProbes.kt
+++ b/kotlinx-coroutines-debug/src/internal/NoOpProbes.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
@file:Suppress("unused", "UNUSED_PARAMETER")
diff --git a/kotlinx-coroutines-debug/src/junit/CoroutinesTimeoutImpl.kt b/kotlinx-coroutines-debug/src/junit/CoroutinesTimeoutImpl.kt
new file mode 100644
index 00000000..06a84a5b
--- /dev/null
+++ b/kotlinx-coroutines-debug/src/junit/CoroutinesTimeoutImpl.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.debug
+
+import java.util.concurrent.*
+
+/**
+ * Run [invocation] in a separate thread with the given timeout in ms, after which the coroutines info is dumped and, if
+ * [cancelOnTimeout] is set, the execution is interrupted.
+ *
+ * Assumes that [DebugProbes] are installed. Does not deinstall them.
+ */
+internal inline fun <T : Any?> runWithTimeoutDumpingCoroutines(
+ methodName: String,
+ testTimeoutMs: Long,
+ cancelOnTimeout: Boolean,
+ initCancellationException: () -> Throwable,
+ crossinline invocation: () -> T
+): T {
+ val testStartedLatch = CountDownLatch(1)
+ val testResult = FutureTask {
+ testStartedLatch.countDown()
+ invocation()
+ }
+ /*
+ * We are using hand-rolled thread instead of single thread executor
+ * in order to be able to safely interrupt thread in the end of a test
+ */
+ val testThread = Thread(testResult, "Timeout test thread").apply { isDaemon = true }
+ try {
+ testThread.start()
+ // Await until test is started to take only test execution time into account
+ testStartedLatch.await()
+ return testResult.get(testTimeoutMs, TimeUnit.MILLISECONDS)
+ } catch (e: TimeoutException) {
+ handleTimeout(testThread, methodName, testTimeoutMs, cancelOnTimeout, initCancellationException())
+ } catch (e: ExecutionException) {
+ throw e.cause ?: e
+ }
+}
+
+private fun handleTimeout(testThread: Thread, methodName: String, testTimeoutMs: Long, cancelOnTimeout: Boolean,
+ cancellationException: Throwable): Nothing {
+ val units =
+ if (testTimeoutMs % 1000 == 0L)
+ "${testTimeoutMs / 1000} seconds"
+ else "$testTimeoutMs milliseconds"
+
+ System.err.println("\nTest $methodName timed out after $units\n")
+ System.err.flush()
+
+ DebugProbes.dumpCoroutines()
+ System.out.flush() // Synchronize serr/sout
+
+ /*
+ * Order is important:
+ * 1) Create exception with a stacktrace of hang test
+ * 2) Cancel all coroutines via debug agent API (changing system state!)
+ * 3) Throw created exception
+ */
+ cancellationException.attachStacktraceFrom(testThread)
+ testThread.interrupt()
+ cancelIfNecessary(cancelOnTimeout)
+ // If timed out test throws an exception, we can't do much except ignoring it
+ throw cancellationException
+}
+
+private fun cancelIfNecessary(cancelOnTimeout: Boolean) {
+ if (cancelOnTimeout) {
+ DebugProbes.dumpCoroutinesInfo().forEach {
+ it.job?.cancel()
+ }
+ }
+}
+
+private fun Throwable.attachStacktraceFrom(thread: Thread) {
+ val stackTrace = thread.stackTrace
+ this.stackTrace = stackTrace
+}
diff --git a/kotlinx-coroutines-debug/src/junit4/CoroutinesTimeout.kt b/kotlinx-coroutines-debug/src/junit/junit4/CoroutinesTimeout.kt
index 0510764a..12bc9475 100644
--- a/kotlinx-coroutines-debug/src/junit4/CoroutinesTimeout.kt
+++ b/kotlinx-coroutines-debug/src/junit/junit4/CoroutinesTimeout.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.debug.junit4
diff --git a/kotlinx-coroutines-debug/src/junit/junit4/CoroutinesTimeoutStatement.kt b/kotlinx-coroutines-debug/src/junit/junit4/CoroutinesTimeoutStatement.kt
new file mode 100644
index 00000000..aa6b8df2
--- /dev/null
+++ b/kotlinx-coroutines-debug/src/junit/junit4/CoroutinesTimeoutStatement.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.debug.junit4
+
+import kotlinx.coroutines.debug.*
+import org.junit.runner.*
+import org.junit.runners.model.*
+import java.util.concurrent.*
+
+internal class CoroutinesTimeoutStatement(
+ private val testStatement: Statement,
+ private val testDescription: Description,
+ private val testTimeoutMs: Long,
+ private val cancelOnTimeout: Boolean = false
+) : Statement() {
+
+ override fun evaluate() {
+ try {
+ runWithTimeoutDumpingCoroutines(testDescription.methodName, testTimeoutMs, cancelOnTimeout,
+ { TestTimedOutException(testTimeoutMs, TimeUnit.MILLISECONDS) })
+ {
+ testStatement.evaluate()
+ }
+ } finally {
+ DebugProbes.uninstall()
+ }
+ }
+}
diff --git a/kotlinx-coroutines-debug/src/junit/junit5/CoroutinesTimeout.kt b/kotlinx-coroutines-debug/src/junit/junit5/CoroutinesTimeout.kt
new file mode 100644
index 00000000..9a8263fe
--- /dev/null
+++ b/kotlinx-coroutines-debug/src/junit/junit5/CoroutinesTimeout.kt
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.debug.junit5
+import kotlinx.coroutines.debug.*
+import org.junit.jupiter.api.*
+import org.junit.jupiter.api.extension.*
+import org.junit.jupiter.api.parallel.*
+import java.lang.annotation.*
+
+/**
+ * Coroutines timeout annotation that is similar to JUnit5's [Timeout] annotation. It allows running test methods in a
+ * separate thread, failing them after the provided time limit and interrupting the thread.
+ *
+ * Additionally, it installs [DebugProbes] and dumps all coroutines at the moment of the timeout. It also cancels
+ * coroutines on timeout if [cancelOnTimeout] set to `true`. The dump contains the coroutine creation stack traces.
+ *
+ * This annotation has an effect on test, test factory, test template, and lifecycle methods and test classes that are
+ * annotated with it.
+ *
+ * Annotating a class is the same as annotating every test, test factory, and test template method (but not lifecycle
+ * methods) of that class and its inner test classes, unless any of them is annotated with [CoroutinesTimeout], in which
+ * case their annotation overrides the one on the containing class.
+ *
+ * Declaring [CoroutinesTimeout] on a test factory checks that it finishes in the specified time, but does not check
+ * whether the methods that it produces obey the timeout as well.
+ *
+ * Example usage:
+ * ```
+ * @CoroutinesTimeout(100)
+ * class CoroutinesTimeoutSimpleTest {
+ * // does not time out, as the annotation on the method overrides the class-level one
+ * @CoroutinesTimeout(1000)
+ * @Test
+ * fun classTimeoutIsOverridden() {
+ * runBlocking {
+ * delay(150)
+ * }
+ * }
+ *
+ * // times out in 100 ms, timeout value is taken from the class-level annotation
+ * @Test
+ * fun classTimeoutIsUsed() {
+ * runBlocking {
+ * delay(150)
+ * }
+ * }
+ * }
+ * ```
+ *
+ * @see Timeout
+ */
+@ExtendWith(CoroutinesTimeoutExtension::class)
+@Inherited
+@MustBeDocumented
+@ResourceLock("coroutines timeout", mode = ResourceAccessMode.READ)
+@Retention(value = AnnotationRetention.RUNTIME)
+@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION)
+public annotation class CoroutinesTimeout(
+ val testTimeoutMs: Long,
+ val cancelOnTimeout: Boolean = false
+)
diff --git a/kotlinx-coroutines-debug/src/junit/junit5/CoroutinesTimeoutExtension.kt b/kotlinx-coroutines-debug/src/junit/junit5/CoroutinesTimeoutExtension.kt
new file mode 100644
index 00000000..442fdf8c
--- /dev/null
+++ b/kotlinx-coroutines-debug/src/junit/junit5/CoroutinesTimeoutExtension.kt
@@ -0,0 +1,279 @@
+/*
+ * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.debug.junit5
+
+import kotlinx.coroutines.debug.*
+import kotlinx.coroutines.debug.runWithTimeoutDumpingCoroutines
+import org.junit.jupiter.api.extension.*
+import org.junit.platform.commons.support.AnnotationSupport
+import java.lang.reflect.*
+import java.util.*
+import java.util.concurrent.atomic.*
+
+internal class CoroutinesTimeoutException(val timeoutMs: Long): Exception("test timed out after $timeoutMs ms")
+
+/**
+ * This JUnit5 extension allows running test, test factory, test template, and lifecycle methods in a separate thread,
+ * failing them after the provided time limit and interrupting the thread.
+ *
+ * Additionally, it installs [DebugProbes] and dumps all coroutines at the moment of the timeout. It also cancels
+ * coroutines on timeout if [cancelOnTimeout] set to `true`.
+ * [enableCoroutineCreationStackTraces] controls the corresponding [DebugProbes.enableCreationStackTraces] property
+ * and can be optionally disabled to speed-up tests if creation stack traces are not needed.
+ *
+ * Beware that if several tests that use this extension set [enableCoroutineCreationStackTraces] to different values and
+ * execute in parallel, the behavior is ill-defined. In order to avoid conflicts between different instances of this
+ * extension when using JUnit5 in parallel, use [ResourceLock] with resource name `coroutines timeout` on tests that use
+ * it. Note that the tests annotated with [CoroutinesTimeout] already use this [ResourceLock], so there is no need to
+ * annotate them additionally.
+ *
+ * Note that while calls to test factories are verified to finish in the specified time, but the methods that they
+ * produce are not affected by this extension.
+ *
+ * Beware that registering the extension via [CoroutinesTimeout] annotation conflicts with manually registering it on
+ * the same tests via other methods (most notably, [RegisterExtension]) and is prohibited.
+ *
+ * Example of usage:
+ * ```
+ * class HangingTest {
+ * @JvmField
+ * @RegisterExtension
+ * val timeout = CoroutinesTimeoutExtension.seconds(5)
+ *
+ * @Test
+ * fun testThatHangs() = runBlocking {
+ * ...
+ * delay(Long.MAX_VALUE) // somewhere deep in the stack
+ * ...
+ * }
+ * }
+ * ```
+ *
+ * @see [CoroutinesTimeout]
+ * */
+// NB: the constructor is not private so that JUnit is able to call it via reflection.
+internal class CoroutinesTimeoutExtension internal constructor(
+ private val enableCoroutineCreationStackTraces: Boolean = true,
+ private val timeoutMs: Long? = null,
+ private val cancelOnTimeout: Boolean? = null): InvocationInterceptor
+{
+ /**
+ * Creates the [CoroutinesTimeoutExtension] extension with the given timeout in milliseconds.
+ */
+ public constructor(timeoutMs: Long, cancelOnTimeout: Boolean = false,
+ enableCoroutineCreationStackTraces: Boolean = true):
+ this(enableCoroutineCreationStackTraces, timeoutMs, cancelOnTimeout)
+
+ public companion object {
+ /**
+ * Creates the [CoroutinesTimeoutExtension] extension with the given timeout in seconds.
+ */
+ @JvmOverloads
+ public fun seconds(timeout: Int, cancelOnTimeout: Boolean = false,
+ enableCoroutineCreationStackTraces: Boolean = true): CoroutinesTimeoutExtension =
+ CoroutinesTimeoutExtension(enableCoroutineCreationStackTraces, timeout.toLong() * 1000, cancelOnTimeout)
+ }
+
+ /** @see [initialize] */
+ private val debugProbesOwnershipPassed = AtomicBoolean(false)
+
+ private fun tryPassDebugProbesOwnership() = debugProbesOwnershipPassed.compareAndSet(false, true)
+
+ /* We install the debug probes early so that the coroutines launched from the test constructor are captured as well.
+ However, this is not enough as the same extension instance may be reused several times, even cleaning up its
+ resources from the store. */
+ init {
+ DebugProbes.enableCreationStackTraces = enableCoroutineCreationStackTraces
+ DebugProbes.install()
+ }
+
+ // This is needed so that a class with no tests still successfully passes the ownership of DebugProbes to JUnit5.
+ override fun <T : Any?> interceptTestClassConstructor(
+ invocation: InvocationInterceptor.Invocation<T>,
+ invocationContext: ReflectiveInvocationContext<Constructor<T>>,
+ extensionContext: ExtensionContext
+ ): T {
+ initialize(extensionContext)
+ return invocation.proceed()
+ }
+
+ /**
+ * Initialize this extension instance and/or the extension value store.
+ *
+ * It seems that the only way to reliably have JUnit5 clean up after its extensions is to put an instance of
+ * [ExtensionContext.Store.CloseableResource] into the value store corresponding to the extension instance, which
+ * means that [DebugProbes.uninstall] must be placed into the value store. [debugProbesOwnershipPassed] is `true`
+ * if the call to [DebugProbes.install] performed in the constructor of the extension instance was matched with a
+ * placing of [DebugProbes.uninstall] into the value store. We call the process of placing the cleanup procedure
+ * "passing the ownership", as now JUnit5 (and not our code) has to worry about uninstalling the debug probes.
+ *
+ * However, extension instances can be reused with different value stores, and value stores can be reused across
+ * extension instances. This leads to a tricky scheme of performing [DebugProbes.uninstall]:
+ *
+ * * If neither the ownership of this instance's [DebugProbes] was yet passed nor there is any cleanup procedure
+ * stored, it means that we can just store our cleanup procedure, passing the ownership.
+ * * If the ownership was not yet passed, but a cleanup procedure is already stored, we can't just replace it with
+ * another one, as this would lead to imbalance between [DebugProbes.install] and [DebugProbes.uninstall].
+ * Instead, we know that this extension context will at least outlive this use of this instance, so some debug
+ * probes other than the ones from our constructor are already installed and won't be uninstalled during our
+ * operation. We simply uninstall the debug probes that were installed in our constructor.
+ * * If the ownership was passed, but the store is empty, it means that this test instance is reused and, possibly,
+ * the debug probes installed in its constructor were already uninstalled. This means that we have to install them
+ * anew and store an uninstaller.
+ */
+ private fun initialize(extensionContext: ExtensionContext) {
+ val store: ExtensionContext.Store = extensionContext.getStore(
+ ExtensionContext.Namespace.create(CoroutinesTimeoutExtension::class, extensionContext.uniqueId))
+ /** It seems that the JUnit5 documentation does not specify the relationship between the extension instances and
+ * the corresponding [ExtensionContext] (in which the value stores are managed), so it is unclear whether it's
+ * theoretically possible for two extension instances that run concurrently to share an extension context. So,
+ * just in case this risk exists, we synchronize here. */
+ synchronized(store) {
+ if (store["debugProbes"] == null) {
+ if (!tryPassDebugProbesOwnership()) {
+ /** This means that the [DebugProbes.install] call from the constructor of this extensions has
+ * already been matched with a corresponding cleanup procedure for JUnit5, but then JUnit5 cleaned
+ * everything up and later reused the same extension instance for other tests. Therefore, we need to
+ * install the [DebugProbes] anew. */
+ DebugProbes.enableCreationStackTraces = enableCoroutineCreationStackTraces
+ DebugProbes.install()
+ }
+ /** put a fake resource into this extensions's store so that JUnit cleans it up, uninstalling the
+ * [DebugProbes] after this extension instance is no longer needed. **/
+ store.put("debugProbes", ExtensionContext.Store.CloseableResource { DebugProbes.uninstall() })
+ } else if (!debugProbesOwnershipPassed.get()) {
+ /** This instance shares its store with other ones. Because of this, there was no need to install
+ * [DebugProbes], they are already installed, and this fact will outlive this use of this instance of
+ * the extension. */
+ if (tryPassDebugProbesOwnership()) {
+ // We successfully marked the ownership as passed and now may uninstall the extraneous debug probes.
+ DebugProbes.uninstall()
+ }
+ }
+ }
+ }
+
+ override fun interceptTestMethod(
+ invocation: InvocationInterceptor.Invocation<Void>,
+ invocationContext: ReflectiveInvocationContext<Method>,
+ extensionContext: ExtensionContext
+ ) {
+ interceptNormalMethod(invocation, invocationContext, extensionContext)
+ }
+
+ override fun interceptAfterAllMethod(
+ invocation: InvocationInterceptor.Invocation<Void>,
+ invocationContext: ReflectiveInvocationContext<Method>,
+ extensionContext: ExtensionContext
+ ) {
+ interceptLifecycleMethod(invocation, invocationContext, extensionContext)
+ }
+
+ override fun interceptAfterEachMethod(
+ invocation: InvocationInterceptor.Invocation<Void>,
+ invocationContext: ReflectiveInvocationContext<Method>,
+ extensionContext: ExtensionContext
+ ) {
+ interceptLifecycleMethod(invocation, invocationContext, extensionContext)
+ }
+
+ override fun interceptBeforeAllMethod(
+ invocation: InvocationInterceptor.Invocation<Void>,
+ invocationContext: ReflectiveInvocationContext<Method>,
+ extensionContext: ExtensionContext
+ ) {
+ interceptLifecycleMethod(invocation, invocationContext, extensionContext)
+ }
+
+ override fun interceptBeforeEachMethod(
+ invocation: InvocationInterceptor.Invocation<Void>,
+ invocationContext: ReflectiveInvocationContext<Method>,
+ extensionContext: ExtensionContext
+ ) {
+ interceptLifecycleMethod(invocation, invocationContext, extensionContext)
+ }
+
+ override fun <T : Any?> interceptTestFactoryMethod(
+ invocation: InvocationInterceptor.Invocation<T>,
+ invocationContext: ReflectiveInvocationContext<Method>,
+ extensionContext: ExtensionContext
+ ): T = interceptNormalMethod(invocation, invocationContext, extensionContext)
+
+ override fun interceptTestTemplateMethod(
+ invocation: InvocationInterceptor.Invocation<Void>,
+ invocationContext: ReflectiveInvocationContext<Method>,
+ extensionContext: ExtensionContext
+ ) {
+ interceptNormalMethod(invocation, invocationContext, extensionContext)
+ }
+
+ private fun<T> Class<T>.coroutinesTimeoutAnnotation(): Optional<CoroutinesTimeout> =
+ AnnotationSupport.findAnnotation(this, CoroutinesTimeout::class.java).or {
+ enclosingClass?.coroutinesTimeoutAnnotation() ?: Optional.empty()
+ }
+
+ private fun <T: Any?> interceptMethod(
+ useClassAnnotation: Boolean,
+ invocation: InvocationInterceptor.Invocation<T>,
+ invocationContext: ReflectiveInvocationContext<Method>,
+ extensionContext: ExtensionContext
+ ): T {
+ initialize(extensionContext)
+ val testAnnotationOptional =
+ AnnotationSupport.findAnnotation(invocationContext.executable, CoroutinesTimeout::class.java)
+ val classAnnotationOptional = extensionContext.testClass.flatMap { it.coroutinesTimeoutAnnotation() }
+ if (timeoutMs != null && cancelOnTimeout != null) {
+ // this means we @RegisterExtension was used in order to register this extension.
+ if (testAnnotationOptional.isPresent || classAnnotationOptional.isPresent) {
+ /* Using annotations creates a separate instance of the extension, which composes in a strange way: both
+ timeouts are applied. This is at odds with the concept that method-level annotations override the outer
+ rules and may lead to unexpected outcomes, so we prohibit this. */
+ throw UnsupportedOperationException("Using CoroutinesTimeout along with instance field-registered CoroutinesTimeout is prohibited; please use either @RegisterExtension or @CoroutinesTimeout, but not both")
+ }
+ return interceptInvocation(invocation, invocationContext.executable.name, timeoutMs, cancelOnTimeout)
+ }
+ /* The extension was registered via an annotation; check that we succeeded in finding the annotation that led to
+ the extension being registered and taking its parameters. */
+ if (testAnnotationOptional.isEmpty && classAnnotationOptional.isEmpty) {
+ throw UnsupportedOperationException("Timeout was registered with a CoroutinesTimeout annotation, but we were unable to find it. Please report this.")
+ }
+ return when {
+ testAnnotationOptional.isPresent -> {
+ val annotation = testAnnotationOptional.get()
+ interceptInvocation(invocation, invocationContext.executable.name, annotation.testTimeoutMs,
+ annotation.cancelOnTimeout)
+ }
+ useClassAnnotation && classAnnotationOptional.isPresent -> {
+ val annotation = classAnnotationOptional.get()
+ interceptInvocation(invocation, invocationContext.executable.name, annotation.testTimeoutMs,
+ annotation.cancelOnTimeout)
+ }
+ else -> {
+ invocation.proceed()
+ }
+ }
+ }
+
+ private fun<T> interceptNormalMethod(
+ invocation: InvocationInterceptor.Invocation<T>,
+ invocationContext: ReflectiveInvocationContext<Method>,
+ extensionContext: ExtensionContext
+ ): T = interceptMethod(true, invocation, invocationContext, extensionContext)
+
+ private fun interceptLifecycleMethod(
+ invocation: InvocationInterceptor.Invocation<Void>,
+ invocationContext: ReflectiveInvocationContext<Method>,
+ extensionContext: ExtensionContext
+ ) = interceptMethod(false, invocation, invocationContext, extensionContext)
+
+ private fun <T : Any?> interceptInvocation(
+ invocation: InvocationInterceptor.Invocation<T>,
+ methodName: String,
+ testTimeoutMs: Long,
+ cancelOnTimeout: Boolean
+ ): T =
+ runWithTimeoutDumpingCoroutines(methodName, testTimeoutMs, cancelOnTimeout,
+ { CoroutinesTimeoutException(testTimeoutMs) }, { invocation.proceed() })
+} \ No newline at end of file
diff --git a/kotlinx-coroutines-debug/src/junit4/CoroutinesTimeoutStatement.kt b/kotlinx-coroutines-debug/src/junit4/CoroutinesTimeoutStatement.kt
deleted file mode 100644
index 72413b91..00000000
--- a/kotlinx-coroutines-debug/src/junit4/CoroutinesTimeoutStatement.kt
+++ /dev/null
@@ -1,87 +0,0 @@
-/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
- */
-
-package kotlinx.coroutines.debug.junit4
-
-import kotlinx.coroutines.debug.*
-import org.junit.runner.*
-import org.junit.runners.model.*
-import java.util.concurrent.*
-
-internal class CoroutinesTimeoutStatement(
- testStatement: Statement,
- private val testDescription: Description,
- private val testTimeoutMs: Long,
- private val cancelOnTimeout: Boolean = false
-) : Statement() {
-
- private val testStartedLatch = CountDownLatch(1)
-
- private val testResult = FutureTask<Unit> {
- testStartedLatch.countDown()
- testStatement.evaluate()
- }
-
- /*
- * We are using hand-rolled thread instead of single thread executor
- * in order to be able to safely interrupt thread in the end of a test
- */
- private val testThread = Thread(testResult, "Timeout test thread").apply { isDaemon = true }
-
- override fun evaluate() {
- try {
- testThread.start()
- // Await until test is started to take only test execution time into account
- testStartedLatch.await()
- testResult.get(testTimeoutMs, TimeUnit.MILLISECONDS)
- return
- } catch (e: TimeoutException) {
- handleTimeout(testDescription)
- } catch (e: ExecutionException) {
- throw e.cause ?: e
- } finally {
- DebugProbes.uninstall()
- }
- }
-
- private fun handleTimeout(description: Description) {
- val units =
- if (testTimeoutMs % 1000 == 0L)
- "${testTimeoutMs / 1000} seconds"
- else "$testTimeoutMs milliseconds"
-
- System.err.println("\nTest ${description.methodName} timed out after $units\n")
- System.err.flush()
-
- DebugProbes.dumpCoroutines()
- System.out.flush() // Synchronize serr/sout
-
- /*
- * Order is important:
- * 1) Create exception with a stacktrace of hang test
- * 2) Cancel all coroutines via debug agent API (changing system state!)
- * 3) Throw created exception
- */
- val exception = createTimeoutException(testThread)
- cancelIfNecessary()
- // If timed out test throws an exception, we can't do much except ignoring it
- throw exception
- }
-
- private fun cancelIfNecessary() {
- if (cancelOnTimeout) {
- DebugProbes.dumpCoroutinesInfo().forEach {
- it.job?.cancel()
- }
- }
- }
-
- private fun createTimeoutException(thread: Thread): Exception {
- val stackTrace = thread.stackTrace
- val exception = TestTimedOutException(testTimeoutMs, TimeUnit.MILLISECONDS)
- exception.stackTrace = stackTrace
- thread.interrupt()
- return exception
- }
-}
diff --git a/kotlinx-coroutines-debug/test/DebugProbesTest.kt b/kotlinx-coroutines-debug/test/DebugProbesTest.kt
index 3b32db3a..4b394381 100644
--- a/kotlinx-coroutines-debug/test/DebugProbesTest.kt
+++ b/kotlinx-coroutines-debug/test/DebugProbesTest.kt
@@ -21,7 +21,6 @@ class DebugProbesTest : DebugTestBase() {
"java.util.concurrent.ExecutionException\n" +
"\tat kotlinx.coroutines.debug.DebugProbesTest\$createDeferred\$1.invokeSuspend(DebugProbesTest.kt:14)\n" +
"\t(Coroutine boundary)\n" +
- "\tat kotlinx.coroutines.DeferredCoroutine.await\$suspendImpl(Builders.common.kt)\n" +
"\tat kotlinx.coroutines.debug.DebugProbesTest.oneMoreNestedMethod(DebugProbesTest.kt:49)\n" +
"\tat kotlinx.coroutines.debug.DebugProbesTest.nestedMethod(DebugProbesTest.kt:44)\n" +
"\tat kotlinx.coroutines.debug.DebugProbesTest\$testAsync\$1.invokeSuspend(DebugProbesTest.kt:17)\n",
@@ -42,9 +41,9 @@ class DebugProbesTest : DebugTestBase() {
"java.util.concurrent.ExecutionException\n" +
"\tat kotlinx.coroutines.debug.DebugProbesTest\$createDeferred\$1.invokeSuspend(DebugProbesTest.kt)\n" +
"\t(Coroutine boundary)\n" +
- "\tat kotlinx.coroutines.DeferredCoroutine.await\$suspendImpl(Builders.common.kt)\n" +
"\tat kotlinx.coroutines.debug.DebugProbesTest.oneMoreNestedMethod(DebugProbesTest.kt)\n" +
"\tat kotlinx.coroutines.debug.DebugProbesTest.nestedMethod(DebugProbesTest.kt)\n" +
+ "\tat kotlinx.coroutines.debug.DebugProbesTest\$testAsyncWithProbes\$1\$1.invokeSuspend(DebugProbesTest.kt:62)\n" +
"\t(Coroutine creation stacktrace)\n" +
"\tat kotlin.coroutines.intrinsics.IntrinsicsKt__IntrinsicsJvmKt.createCoroutineUnintercepted(IntrinsicsJvm.kt)\n" +
"\tat kotlinx.coroutines.intrinsics.CancellableKt.startCoroutineCancellable(Cancellable.kt)\n" +
@@ -73,9 +72,9 @@ class DebugProbesTest : DebugTestBase() {
"java.util.concurrent.ExecutionException\n" +
"\tat kotlinx.coroutines.debug.DebugProbesTest\$createDeferred\$1.invokeSuspend(DebugProbesTest.kt:16)\n" +
"\t(Coroutine boundary)\n" +
- "\tat kotlinx.coroutines.DeferredCoroutine.await\$suspendImpl(Builders.common.kt)\n" +
"\tat kotlinx.coroutines.debug.DebugProbesTest.oneMoreNestedMethod(DebugProbesTest.kt:71)\n" +
"\tat kotlinx.coroutines.debug.DebugProbesTest.nestedMethod(DebugProbesTest.kt:66)\n" +
+ "\tat kotlinx.coroutines.debug.DebugProbesTest\$testAsyncWithSanitizedProbes\$1\$1.invokeSuspend(DebugProbesTest.kt:87)\n" +
"\t(Coroutine creation stacktrace)\n" +
"\tat kotlin.coroutines.intrinsics.IntrinsicsKt__IntrinsicsJvmKt.createCoroutineUnintercepted(IntrinsicsJvm.kt:116)\n" +
"\tat kotlinx.coroutines.intrinsics.CancellableKt.startCoroutineCancellable(Cancellable.kt:23)\n" +
diff --git a/kotlinx-coroutines-debug/test/SanitizedProbesTest.kt b/kotlinx-coroutines-debug/test/SanitizedProbesTest.kt
index bf6cbdf1..fd1c2882 100644
--- a/kotlinx-coroutines-debug/test/SanitizedProbesTest.kt
+++ b/kotlinx-coroutines-debug/test/SanitizedProbesTest.kt
@@ -28,7 +28,6 @@ class SanitizedProbesTest : DebugTestBase() {
"java.util.concurrent.ExecutionException\n" +
"\tat definitely.not.kotlinx.coroutines.SanitizedProbesTest\$createDeferredNested\$1.invokeSuspend(SanitizedProbesTest.kt:97)\n" +
"\t(Coroutine boundary)\n" +
- "\tat kotlinx.coroutines.DeferredCoroutine.await\$suspendImpl(Builders.common.kt:99)\n" +
"\tat definitely.not.kotlinx.coroutines.SanitizedProbesTest.oneMoreNestedMethod(SanitizedProbesTest.kt:67)\n" +
"\tat definitely.not.kotlinx.coroutines.SanitizedProbesTest.nestedMethod(SanitizedProbesTest.kt:61)\n" +
"\tat definitely.not.kotlinx.coroutines.SanitizedProbesTest\$testRecoveredStackTrace\$1.invokeSuspend(SanitizedProbesTest.kt:50)\n" +
@@ -63,6 +62,7 @@ class SanitizedProbesTest : DebugTestBase() {
"\t(Coroutine creation stacktrace)\n" +
"\tat kotlin.coroutines.intrinsics.IntrinsicsKt__IntrinsicsJvmKt.createCoroutineUnintercepted(IntrinsicsJvm.kt:116)\n" +
"\tat kotlinx.coroutines.intrinsics.CancellableKt.startCoroutineCancellable(Cancellable.kt:23)\n" +
+ "\tat kotlinx.coroutines.BuildersKt__Builders_commonKt.async\$default(Builders.common.kt)\n" +
"\tat kotlinx.coroutines.BuildersKt.async\$default(Unknown Source)\n" +
"\tat definitely.not.kotlinx.coroutines.SanitizedProbesTest.createActiveDeferred(SanitizedProbesTest.kt:62)\n" +
"\tat definitely.not.kotlinx.coroutines.SanitizedProbesTest.access\$createActiveDeferred(SanitizedProbesTest.kt:16)\n" +
@@ -87,10 +87,11 @@ class SanitizedProbesTest : DebugTestBase() {
"\tat kotlin.coroutines.intrinsics.IntrinsicsKt__IntrinsicsJvmKt.createCoroutineUnintercepted(IntrinsicsJvm.kt:116)",
"Coroutine \"coroutine#2\":StandaloneCoroutine{Active}@1b68b9a4, state: SUSPENDED\n" +
- "\tat definitely.not.kotlinx.coroutines.SanitizedProbesTest\$launchSelector\$1.invokeSuspend(SanitizedProbesTest.kt:143)\n" +
+ "\tat definitely.not.kotlinx.coroutines.SanitizedProbesTest\$launchSelector\$1\$1\$1.invokeSuspend(SanitizedProbesTest.kt)\n" +
"\t(Coroutine creation stacktrace)\n" +
"\tat kotlin.coroutines.intrinsics.IntrinsicsKt__IntrinsicsJvmKt.createCoroutineUnintercepted(IntrinsicsJvm.kt:116)\n" +
"\tat kotlinx.coroutines.intrinsics.CancellableKt.startCoroutineCancellable(Cancellable.kt:25)\n" +
+ "\tat kotlinx.coroutines.BuildersKt__Builders_commonKt.launch\$default(Builders.common.kt)\n" +
"\tat kotlinx.coroutines.BuildersKt.launch\$default(Unknown Source)\n" +
"\tat definitely.not.kotlinx.coroutines.SanitizedProbesTest.launchSelector(SanitizedProbesTest.kt:100)\n" +
"\tat definitely.not.kotlinx.coroutines.SanitizedProbesTest.access\$launchSelector(SanitizedProbesTest.kt:16)\n" +
diff --git a/kotlinx-coroutines-debug/test/junit4/CoroutinesTimeoutDisabledTracesTest.kt b/kotlinx-coroutines-debug/test/junit4/CoroutinesTimeoutDisabledTracesTest.kt
index 886007c3..2063090c 100644
--- a/kotlinx-coroutines-debug/test/junit4/CoroutinesTimeoutDisabledTracesTest.kt
+++ b/kotlinx-coroutines-debug/test/junit4/CoroutinesTimeoutDisabledTracesTest.kt
@@ -8,7 +8,7 @@ import kotlinx.coroutines.*
import org.junit.*
import org.junit.runners.model.*
-class CoroutinesTimeoutDisabledTracesTest : TestBase() {
+class CoroutinesTimeoutDisabledTracesTest : TestBase(disableOutCheck = true) {
@Rule
@JvmField
diff --git a/kotlinx-coroutines-debug/test/junit4/CoroutinesTimeoutEagerTest.kt b/kotlinx-coroutines-debug/test/junit4/CoroutinesTimeoutEagerTest.kt
index 0845f5bc..7a686ff2 100644
--- a/kotlinx-coroutines-debug/test/junit4/CoroutinesTimeoutEagerTest.kt
+++ b/kotlinx-coroutines-debug/test/junit4/CoroutinesTimeoutEagerTest.kt
@@ -8,7 +8,7 @@ import kotlinx.coroutines.*
import org.junit.*
import org.junit.runners.model.*
-class CoroutinesTimeoutEagerTest : TestBase() {
+class CoroutinesTimeoutEagerTest : TestBase(disableOutCheck = true) {
@Rule
@JvmField
diff --git a/kotlinx-coroutines-debug/test/junit4/CoroutinesTimeoutTest.kt b/kotlinx-coroutines-debug/test/junit4/CoroutinesTimeoutTest.kt
index ac3408e2..53447ac5 100644
--- a/kotlinx-coroutines-debug/test/junit4/CoroutinesTimeoutTest.kt
+++ b/kotlinx-coroutines-debug/test/junit4/CoroutinesTimeoutTest.kt
@@ -8,7 +8,7 @@ import kotlinx.coroutines.*
import org.junit.*
import org.junit.runners.model.*
-class CoroutinesTimeoutTest : TestBase() {
+class CoroutinesTimeoutTest : TestBase(disableOutCheck = true) {
@Rule
@JvmField
diff --git a/kotlinx-coroutines-debug/test/junit4/TestFailureValidation.kt b/kotlinx-coroutines-debug/test/junit4/TestFailureValidation.kt
index 34ba679a..6d25a6da 100644
--- a/kotlinx-coroutines-debug/test/junit4/TestFailureValidation.kt
+++ b/kotlinx-coroutines-debug/test/junit4/TestFailureValidation.kt
@@ -104,6 +104,6 @@ internal class TestFailureValidation(private val testsSpec: Map<String, TestResu
}
data class TestResultSpec(
- val testName: String, val expectedOutParts: List<String> = listOf(), val notExpectedOutParts:
- List<String> = listOf(), val error: Class<out Throwable>? = null
+ val testName: String, val expectedOutParts: List<String> = listOf(),
+ val notExpectedOutParts: List<String> = listOf(), val error: Class<out Throwable>? = null
)
diff --git a/kotlinx-coroutines-debug/test/junit5/CoroutinesTimeoutExtensionTest.kt b/kotlinx-coroutines-debug/test/junit5/CoroutinesTimeoutExtensionTest.kt
new file mode 100644
index 00000000..752c6c35
--- /dev/null
+++ b/kotlinx-coroutines-debug/test/junit5/CoroutinesTimeoutExtensionTest.kt
@@ -0,0 +1,121 @@
+/*
+ * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.debug.junit5
+
+import kotlinx.coroutines.*
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.extension.*
+import org.junit.jupiter.api.parallel.*
+
+class CoroutinesTimeoutExtensionTest {
+
+ /**
+ * Tests that disabling coroutine creation stacktraces in [CoroutinesTimeoutExtension] does lead to them not being
+ * created.
+ *
+ * Adapted from [CoroutinesTimeoutDisabledTracesTest], an identical test for the JUnit4 rule.
+ *
+ * This test class is not intended to be run manually. Instead, use [CoroutinesTimeoutTest] as the entry point.
+ */
+ class DisabledStackTracesTest {
+ @JvmField
+ @RegisterExtension
+ internal val timeout = CoroutinesTimeoutExtension(500, true, false)
+
+ private val job = GlobalScope.launch(Dispatchers.Unconfined) { hangForever() }
+
+ private suspend fun hangForever() {
+ suspendCancellableCoroutine<Unit> { }
+ expectUnreached()
+ }
+
+ @Test
+ fun hangingTest() = runBlocking<Unit> {
+ waitForHangJob()
+ expectUnreached()
+ }
+
+ private suspend fun waitForHangJob() {
+ job.join()
+ expectUnreached()
+ }
+ }
+
+ /**
+ * Tests that [CoroutinesTimeoutExtension] is installed eagerly and detects the coroutines that were launched before
+ * any test events start happening.
+ *
+ * Adapted from [CoroutinesTimeoutEagerTest], an identical test for the JUnit4 rule.
+ *
+ * This test class is not intended to be run manually. Instead, use [CoroutinesTimeoutTest] as the entry point.
+ */
+ class EagerTest {
+
+ @JvmField
+ @RegisterExtension
+ internal val timeout = CoroutinesTimeoutExtension(500)
+
+ private val job = GlobalScope.launch(Dispatchers.Unconfined) { hangForever() }
+
+ private suspend fun hangForever() {
+ suspendCancellableCoroutine<Unit> { }
+ expectUnreached()
+ }
+
+ @Test
+ fun hangingTest() = runBlocking<Unit> {
+ waitForHangJob()
+ expectUnreached()
+ }
+
+ private suspend fun waitForHangJob() {
+ job.join()
+ expectUnreached()
+ }
+ }
+
+ /**
+ * Tests that [CoroutinesTimeoutExtension] performs sensibly in some simple scenarios.
+ *
+ * Adapted from [CoroutinesTimeoutTest], an identical test for the JUnit4 rule.
+ *
+ * This test class is not intended to be run manually. Instead, use [CoroutinesTimeoutTest] as the entry point.
+ */
+ class SimpleTest {
+
+ @JvmField
+ @RegisterExtension
+ internal val timeout = CoroutinesTimeoutExtension(1000, false, true)
+
+ @Test
+ fun hangingTest() = runBlocking<Unit> {
+ suspendForever()
+ expectUnreached()
+ }
+
+ private suspend fun suspendForever() {
+ delay(Long.MAX_VALUE)
+ expectUnreached()
+ }
+
+ @Test
+ fun throwingTest() = runBlocking<Unit> {
+ throw RuntimeException()
+ }
+
+ @Test
+ fun successfulTest() = runBlocking {
+ val job = launch {
+ yield()
+ }
+
+ job.join()
+ }
+ }
+}
+
+private fun expectUnreached(): Nothing {
+ error("Should not be reached")
+} \ No newline at end of file
diff --git a/kotlinx-coroutines-debug/test/junit5/CoroutinesTimeoutInheritanceTest.kt b/kotlinx-coroutines-debug/test/junit5/CoroutinesTimeoutInheritanceTest.kt
new file mode 100644
index 00000000..7c8de53d
--- /dev/null
+++ b/kotlinx-coroutines-debug/test/junit5/CoroutinesTimeoutInheritanceTest.kt
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.debug.junit5
+
+import kotlinx.coroutines.*
+import org.junit.jupiter.api.*
+
+/**
+ * Tests that [CoroutinesTimeout] is inherited.
+ *
+ * This test class is not intended to be run manually. Instead, use [CoroutinesTimeoutTest] as the entry point.
+ */
+class CoroutinesTimeoutInheritanceTest {
+
+ @CoroutinesTimeout(100)
+ open class Base
+
+ @TestMethodOrder(MethodOrderer.OrderAnnotation::class)
+ class InheritedWithNoTimeout: Base() {
+
+ @Test
+ @Order(1)
+ fun usesBaseClassTimeout() = runBlocking {
+ delay(1000)
+ }
+
+ @CoroutinesTimeout(300)
+ @Test
+ @Order(2)
+ fun methodOverridesBaseClassTimeoutWithGreaterTimeout() = runBlocking {
+ delay(200)
+ }
+
+ @CoroutinesTimeout(10)
+ @Test
+ @Order(3)
+ fun methodOverridesBaseClassTimeoutWithLesserTimeout() = runBlocking {
+ delay(50)
+ }
+
+ }
+
+ @CoroutinesTimeout(300)
+ class InheritedWithGreaterTimeout : TestBase() {
+
+ @Test
+ fun classOverridesBaseClassTimeout1() = runBlocking {
+ delay(200)
+ }
+
+ @Test
+ fun classOverridesBaseClassTimeout2() = runBlocking {
+ delay(400)
+ }
+
+ }
+
+} \ No newline at end of file
diff --git a/kotlinx-coroutines-debug/test/junit5/CoroutinesTimeoutMethodTest.kt b/kotlinx-coroutines-debug/test/junit5/CoroutinesTimeoutMethodTest.kt
new file mode 100644
index 00000000..64611b31
--- /dev/null
+++ b/kotlinx-coroutines-debug/test/junit5/CoroutinesTimeoutMethodTest.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.debug.junit5
+
+import kotlinx.coroutines.*
+import org.junit.jupiter.api.*
+
+/**
+ * Tests usage of [CoroutinesTimeout] on classes and test methods when only methods are annotated.
+ *
+ * This test class is not intended to be run manually. Instead, use [CoroutinesTimeoutTest] as the entry point.
+ */
+@TestMethodOrder(MethodOrderer.OrderAnnotation::class)
+class CoroutinesTimeoutMethodTest {
+
+ @Test
+ @Order(1)
+ fun noClassTimeout() {
+ runBlocking {
+ delay(150)
+ }
+ }
+
+ @CoroutinesTimeout(100)
+ @Test
+ @Order(2)
+ fun usesMethodTimeoutWithNoClassTimeout() {
+ runBlocking {
+ delay(1000)
+ }
+ }
+
+ @CoroutinesTimeout(1000)
+ @Test
+ @Order(3)
+ fun fitsInMethodTimeout() {
+ runBlocking {
+ delay(10)
+ }
+ }
+
+}
diff --git a/kotlinx-coroutines-debug/test/junit5/CoroutinesTimeoutNestedTest.kt b/kotlinx-coroutines-debug/test/junit5/CoroutinesTimeoutNestedTest.kt
new file mode 100644
index 00000000..04c933d0
--- /dev/null
+++ b/kotlinx-coroutines-debug/test/junit5/CoroutinesTimeoutNestedTest.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.debug.junit5
+
+import kotlinx.coroutines.*
+import org.junit.jupiter.api.*
+
+/**
+ * This test checks that nested classes correctly recognize the [CoroutinesTimeout] annotation.
+ *
+ * This test class is not intended to be run manually. Instead, use [CoroutinesTimeoutTest] as the entry point.
+ */
+@CoroutinesTimeout(200)
+class CoroutinesTimeoutNestedTest {
+ @Nested
+ inner class NestedInInherited {
+ @Test
+ fun usesOuterClassTimeout() = runBlocking {
+ delay(1000)
+ }
+
+ @Test
+ fun fitsInOuterClassTimeout() = runBlocking {
+ delay(10)
+ }
+ }
+}
diff --git a/kotlinx-coroutines-debug/test/junit5/CoroutinesTimeoutSimpleTest.kt b/kotlinx-coroutines-debug/test/junit5/CoroutinesTimeoutSimpleTest.kt
new file mode 100644
index 00000000..513a8846
--- /dev/null
+++ b/kotlinx-coroutines-debug/test/junit5/CoroutinesTimeoutSimpleTest.kt
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.debug.junit5
+
+import kotlinx.coroutines.*
+import org.junit.jupiter.api.*
+
+/**
+ * Tests the basic usage of [CoroutinesTimeout] on classes and test methods.
+ *
+ * This test class is not intended to be run manually. Instead, use [CoroutinesTimeoutTest] as the entry point.
+ */
+@TestMethodOrder(MethodOrderer.OrderAnnotation::class)
+@CoroutinesTimeout(100)
+class CoroutinesTimeoutSimpleTest {
+
+ @Test
+ @Order(1)
+ fun usesClassTimeout1() {
+ runBlocking {
+ delay(150)
+ }
+ }
+
+ @CoroutinesTimeout(1000)
+ @Test
+ @Order(2)
+ fun ignoresClassTimeout() {
+ runBlocking {
+ delay(150)
+ }
+ }
+
+ @CoroutinesTimeout(200)
+ @Test
+ @Order(3)
+ fun usesMethodTimeout() {
+ runBlocking {
+ delay(300)
+ }
+ }
+
+ @Test
+ @Order(4)
+ fun fitsInClassTimeout() {
+ runBlocking {
+ delay(50)
+ }
+ }
+
+ @Test
+ @Order(5)
+ fun usesClassTimeout2() {
+ runBlocking {
+ delay(150)
+ }
+ }
+
+}
diff --git a/kotlinx-coroutines-debug/test/junit5/CoroutinesTimeoutTest.kt b/kotlinx-coroutines-debug/test/junit5/CoroutinesTimeoutTest.kt
new file mode 100644
index 00000000..1f7b2080
--- /dev/null
+++ b/kotlinx-coroutines-debug/test/junit5/CoroutinesTimeoutTest.kt
@@ -0,0 +1,170 @@
+/*
+ * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.debug.junit5
+
+import org.assertj.core.api.*
+import org.junit.Ignore
+import org.junit.Assert.*
+import org.junit.Test
+import org.junit.platform.engine.*
+import org.junit.platform.engine.discovery.DiscoverySelectors.*
+import org.junit.platform.testkit.engine.*
+import org.junit.platform.testkit.engine.EventConditions.*
+import java.io.*
+
+// note that these tests are run using JUnit4 in order not to mix the testing systems.
+class CoroutinesTimeoutTest {
+
+ // This test is ignored because it just checks an example.
+ @Test
+ @Ignore
+ fun testRegisterExtensionExample() {
+ val capturedOut = ByteArrayOutputStream()
+ eventsForSelector(selectClass(RegisterExtensionExample::class.java), capturedOut)
+ .testTimedOut("testThatHangs", 5000)
+ }
+
+ @Test
+ fun testCoroutinesTimeoutSimple() {
+ val capturedOut = ByteArrayOutputStream()
+ eventsForSelector(selectClass(CoroutinesTimeoutSimpleTest::class.java), capturedOut)
+ .testFinishedSuccessfully("ignoresClassTimeout")
+ .testFinishedSuccessfully("fitsInClassTimeout")
+ .testTimedOut("usesClassTimeout1", 100)
+ .testTimedOut("usesMethodTimeout", 200)
+ .testTimedOut("usesClassTimeout2", 100)
+ assertEquals(capturedOut.toString(), 3, countDumps(capturedOut))
+ }
+
+ @Test
+ fun testCoroutinesTimeoutMethod() {
+ val capturedOut = ByteArrayOutputStream()
+ eventsForSelector(selectClass(CoroutinesTimeoutMethodTest::class.java), capturedOut)
+ .testFinishedSuccessfully("fitsInMethodTimeout")
+ .testFinishedSuccessfully("noClassTimeout")
+ .testTimedOut("usesMethodTimeoutWithNoClassTimeout", 100)
+ assertEquals(capturedOut.toString(), 1, countDumps(capturedOut))
+ }
+
+ @Test
+ fun testCoroutinesTimeoutNested() {
+ val capturedOut = ByteArrayOutputStream()
+ eventsForSelector(selectClass(CoroutinesTimeoutNestedTest::class.java), capturedOut)
+ .testFinishedSuccessfully("fitsInOuterClassTimeout")
+ .testTimedOut("usesOuterClassTimeout", 200)
+ assertEquals(capturedOut.toString(), 1, countDumps(capturedOut))
+ }
+
+ @Test
+ fun testCoroutinesTimeoutInheritanceWithNoTimeoutInDerived() {
+ val capturedOut = ByteArrayOutputStream()
+ eventsForSelector(selectClass(CoroutinesTimeoutInheritanceTest.InheritedWithNoTimeout::class.java), capturedOut)
+ .testFinishedSuccessfully("methodOverridesBaseClassTimeoutWithGreaterTimeout")
+ .testTimedOut("usesBaseClassTimeout", 100)
+ .testTimedOut("methodOverridesBaseClassTimeoutWithLesserTimeout", 10)
+ assertEquals(capturedOut.toString(), 2, countDumps(capturedOut))
+ }
+
+ @Test
+ fun testCoroutinesTimeoutInheritanceWithGreaterTimeoutInDerived() {
+ val capturedOut = ByteArrayOutputStream()
+ eventsForSelector(
+ selectClass(CoroutinesTimeoutInheritanceTest.InheritedWithGreaterTimeout::class.java),
+ capturedOut
+ )
+ .testFinishedSuccessfully("classOverridesBaseClassTimeout1")
+ .testTimedOut("classOverridesBaseClassTimeout2", 300)
+ assertEquals(capturedOut.toString(), 1, countDumps(capturedOut))
+ }
+
+ /* Currently there's no ability to replicate [TestFailureValidation] as is for JUnit5:
+ https://github.com/junit-team/junit5/issues/506. So, the test mechanism is more ad-hoc. */
+
+ @Test
+ fun testCoroutinesTimeoutExtensionDisabledTraces() {
+ val capturedOut = ByteArrayOutputStream()
+ eventsForSelector(selectClass(CoroutinesTimeoutExtensionTest.DisabledStackTracesTest::class.java), capturedOut)
+ .testTimedOut("hangingTest", 500)
+ assertEquals(false, capturedOut.toString().contains("Coroutine creation stacktrace"))
+ assertEquals(capturedOut.toString(), 1, countDumps(capturedOut))
+ }
+
+ @Test
+ fun testCoroutinesTimeoutExtensionEager() {
+ val capturedOut = ByteArrayOutputStream()
+ eventsForSelector(selectClass(CoroutinesTimeoutExtensionTest.EagerTest::class.java), capturedOut)
+ .testTimedOut("hangingTest", 500)
+ for (expectedPart in listOf("hangForever", "waitForHangJob", "BlockingCoroutine{Active}")) {
+ assertEquals(expectedPart, true, capturedOut.toString().contains(expectedPart))
+ }
+ assertEquals(capturedOut.toString(), 1, countDumps(capturedOut))
+ }
+
+ @Test
+ fun testCoroutinesTimeoutExtensionSimple() {
+ val capturedOut = ByteArrayOutputStream()
+ eventsForSelector(selectClass(CoroutinesTimeoutExtensionTest.SimpleTest::class.java), capturedOut)
+ .testFinishedSuccessfully("successfulTest")
+ .testTimedOut("hangingTest", 1000)
+ .haveExactly(1, event(
+ test("throwingTest"),
+ finishedWithFailure(Condition({ it is RuntimeException}, "is RuntimeException"))
+ ))
+ for (expectedPart in listOf("suspendForever", "invokeSuspend", "BlockingCoroutine{Active}")) {
+ assertEquals(expectedPart, true, capturedOut.toString().contains(expectedPart))
+ }
+ for (nonExpectedPart in listOf("delay", "throwingTest")) {
+ assertEquals(nonExpectedPart, false, capturedOut.toString().contains(nonExpectedPart))
+ }
+ assertEquals(capturedOut.toString(), 1, countDumps(capturedOut))
+ }
+}
+
+private fun eventsForSelector(selector: DiscoverySelector, capturedOut: OutputStream): ListAssert<Event> {
+ val systemOut: PrintStream = System.out
+ val systemErr: PrintStream = System.err
+ return try {
+ System.setOut(PrintStream(capturedOut))
+ System.setErr(PrintStream(capturedOut))
+ EngineTestKit.engine("junit-jupiter")
+ .selectors(selector)
+ .execute()
+ .testEvents()
+ .assertThatEvents()
+ } finally {
+ System.setOut(systemOut)
+ System.setErr(systemErr)
+ }
+}
+
+private fun ListAssert<Event>.testFinishedSuccessfully(testName: String): ListAssert<Event> =
+ haveExactly(1, event(
+ test(testName),
+ finishedSuccessfully()
+ ))
+
+private fun ListAssert<Event>.testTimedOut(testName: String, after: Long): ListAssert<Event> =
+ haveExactly(1, event(
+ test(testName),
+ finishedWithFailure(Condition({ it is CoroutinesTimeoutException && it.timeoutMs == after },
+ "is CoroutinesTimeoutException($after)"))
+ ))
+
+/** Counts the number of occurrences of "Coroutines dump" in [capturedOut] */
+private fun countDumps(capturedOut: ByteArrayOutputStream): Int {
+ var result = 0
+ val outStr = capturedOut.toString()
+ val header = "Coroutines dump"
+ var i = 0
+ while (i < outStr.length - header.length) {
+ if (outStr.substring(i, i + header.length) == header) {
+ result += 1
+ i += header.length
+ } else {
+ i += 1
+ }
+ }
+ return result
+} \ No newline at end of file
diff --git a/kotlinx-coroutines-debug/test/junit5/RegisterExtensionExample.kt b/kotlinx-coroutines-debug/test/junit5/RegisterExtensionExample.kt
new file mode 100644
index 00000000..2de6b5b2
--- /dev/null
+++ b/kotlinx-coroutines-debug/test/junit5/RegisterExtensionExample.kt
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.debug.junit5
+
+import kotlinx.coroutines.*
+import org.junit.jupiter.api.*
+import org.junit.jupiter.api.extension.*
+
+class RegisterExtensionExample {
+ @JvmField
+ @RegisterExtension
+ internal val timeout = CoroutinesTimeoutExtension.seconds(5)
+
+ @Test
+ fun testThatHangs() = runBlocking {
+ delay(Long.MAX_VALUE) // somewhere deep in the stack
+ }
+} \ No newline at end of file
diff --git a/kotlinx-coroutines-test/README.md b/kotlinx-coroutines-test/README.md
index afcd4a3b..43ae18f5 100644
--- a/kotlinx-coroutines-test/README.md
+++ b/kotlinx-coroutines-test/README.md
@@ -9,7 +9,7 @@ This package provides testing utilities for effectively testing coroutines.
Add `kotlinx-coroutines-test` to your project test dependencies:
```
dependencies {
- testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.4.1'
+ testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.5.2'
}
```
@@ -431,6 +431,7 @@ If you have any suggestions for improvements to this experimental API please sha
<!--- MODULE kotlinx-coroutines-core -->
<!--- INDEX kotlinx.coroutines -->
+
[Dispatchers.Main]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-dispatchers/-main.html
[CoroutineDispatcher]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-dispatcher/index.html
[launch]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/launch.html
@@ -440,9 +441,11 @@ If you have any suggestions for improvements to this experimental API please sha
[CoroutineStart]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-start/index.html
[CoroutineScope]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-scope/index.html
[ExperimentalCoroutinesApi]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-experimental-coroutines-api/index.html
+
<!--- MODULE kotlinx-coroutines-test -->
<!--- INDEX kotlinx.coroutines.test -->
-[setMain]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-test/kotlinx.coroutines.test/kotlinx.coroutines.-dispatchers/set-main.html
+
+[setMain]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-test/kotlinx.coroutines.test/set-main.html
[runBlockingTest]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-test/kotlinx.coroutines.test/run-blocking-test.html
[UncompletedCoroutinesError]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-test/kotlinx.coroutines.test/-uncompleted-coroutines-error/index.html
[DelayController]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-test/kotlinx.coroutines.test/-delay-controller/index.html
@@ -454,4 +457,5 @@ If you have any suggestions for improvements to this experimental API please sha
[TestCoroutineExceptionHandler]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-test/kotlinx.coroutines.test/-test-coroutine-exception-handler/index.html
[TestCoroutineScope.cleanupTestCoroutines]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-test/kotlinx.coroutines.test/-test-coroutine-scope/cleanup-test-coroutines.html
[DelayController.cleanupTestCoroutines]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-test/kotlinx.coroutines.test/-delay-controller/cleanup-test-coroutines.html
+
<!--- END -->
diff --git a/kotlinx-coroutines-test/build.gradle.kts b/kotlinx-coroutines-test/build.gradle.kts
index 825c95f4..fef0a146 100644
--- a/kotlinx-coroutines-test/build.gradle.kts
+++ b/kotlinx-coroutines-test/build.gradle.kts
@@ -1,3 +1,7 @@
+/*
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
dependencies {
implementation(project(":kotlinx-coroutines-debug"))
}
diff --git a/kotlinx-coroutines-test/resources/META-INF/proguard/coroutines.pro b/kotlinx-coroutines-test/resources/META-INF/proguard/coroutines.pro
index 41c9eb07..1fdfb787 100644
--- a/kotlinx-coroutines-test/resources/META-INF/proguard/coroutines.pro
+++ b/kotlinx-coroutines-test/resources/META-INF/proguard/coroutines.pro
@@ -4,6 +4,6 @@
-keepnames class kotlinx.coroutines.android.AndroidDispatcherFactory {}
# Most of volatile fields are updated with AFU and should not be mangled
--keepclassmembernames class kotlinx.** {
+-keepclassmembers class kotlinx.coroutines.** {
volatile <fields>;
}
diff --git a/kotlinx-coroutines-test/src/DelayController.kt b/kotlinx-coroutines-test/src/DelayController.kt
index a777d4b9..6e722227 100644
--- a/kotlinx-coroutines-test/src/DelayController.kt
+++ b/kotlinx-coroutines-test/src/DelayController.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.test
diff --git a/kotlinx-coroutines-test/src/TestBuilders.kt b/kotlinx-coroutines-test/src/TestBuilders.kt
index 88fa01b7..b40769ee 100644
--- a/kotlinx-coroutines-test/src/TestBuilders.kt
+++ b/kotlinx-coroutines-test/src/TestBuilders.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.test
diff --git a/kotlinx-coroutines-test/src/TestCoroutineDispatcher.kt b/kotlinx-coroutines-test/src/TestCoroutineDispatcher.kt
index cad2636f..f6464789 100644
--- a/kotlinx-coroutines-test/src/TestCoroutineDispatcher.kt
+++ b/kotlinx-coroutines-test/src/TestCoroutineDispatcher.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.test
diff --git a/kotlinx-coroutines-test/src/TestCoroutineExceptionHandler.kt b/kotlinx-coroutines-test/src/TestCoroutineExceptionHandler.kt
index ed08fbcd..66eb2359 100644
--- a/kotlinx-coroutines-test/src/TestCoroutineExceptionHandler.kt
+++ b/kotlinx-coroutines-test/src/TestCoroutineExceptionHandler.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.test
diff --git a/kotlinx-coroutines-test/src/TestCoroutineScope.kt b/kotlinx-coroutines-test/src/TestCoroutineScope.kt
index 4034fcaa..7c1ff872 100644
--- a/kotlinx-coroutines-test/src/TestCoroutineScope.kt
+++ b/kotlinx-coroutines-test/src/TestCoroutineScope.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.test
diff --git a/kotlinx-coroutines-test/src/TestDispatchers.kt b/kotlinx-coroutines-test/src/TestDispatchers.kt
index a247ca83..bf068f9d 100644
--- a/kotlinx-coroutines-test/src/TestDispatchers.kt
+++ b/kotlinx-coroutines-test/src/TestDispatchers.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
@file:Suppress("unused")
@file:JvmName("TestDispatchers")
diff --git a/kotlinx-coroutines-test/src/internal/MainTestDispatcher.kt b/kotlinx-coroutines-test/src/internal/MainTestDispatcher.kt
index baa1aa5f..c85d27ea 100644
--- a/kotlinx-coroutines-test/src/internal/MainTestDispatcher.kt
+++ b/kotlinx-coroutines-test/src/internal/MainTestDispatcher.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.test.internal
@@ -35,7 +35,6 @@ internal class TestMainDispatcher(private val mainFactory: MainDispatcherFactory
delegate.dispatch(context, block)
}
- @ExperimentalCoroutinesApi
override fun isDispatchNeeded(context: CoroutineContext): Boolean = delegate.isDispatchNeeded(context)
override fun scheduleResumeAfterDelay(timeMillis: Long, continuation: CancellableContinuation<Unit>) {
@@ -50,11 +49,11 @@ internal class TestMainDispatcher(private val mainFactory: MainDispatcherFactory
return delay.invokeOnTimeout(timeMillis, block, context)
}
- public fun setDispatcher(dispatcher: CoroutineDispatcher) {
+ fun setDispatcher(dispatcher: CoroutineDispatcher) {
_delegate = dispatcher
}
- public fun resetDispatcher() {
+ fun resetDispatcher() {
_delegate = null
}
}
@@ -64,7 +63,7 @@ internal class TestMainDispatcherFactory : MainDispatcherFactory {
override fun createDispatcher(allFactories: List<MainDispatcherFactory>): MainCoroutineDispatcher {
val originalFactory = allFactories.asSequence()
.filter { it !== this }
- .maxBy { it.loadPriority } ?: MissingMainCoroutineDispatcherFactory
+ .maxByOrNull { it.loadPriority } ?: MissingMainCoroutineDispatcherFactory
return TestMainDispatcher(originalFactory)
}
diff --git a/reactive/knit.properties b/reactive/knit.properties
index 18aecba6..452cd2f1 100644
--- a/reactive/knit.properties
+++ b/reactive/knit.properties
@@ -1,6 +1,6 @@
#
-# Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+# Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
#
knit.package=kotlinx.coroutines.rx2.guide
-knit.dir=kotlinx-coroutines-rx2/test/guide/ \ No newline at end of file
+knit.dir=kotlinx-coroutines-rx2/test/guide/
diff --git a/reactive/kotlinx-coroutines-jdk9/build.gradle.kts b/reactive/kotlinx-coroutines-jdk9/build.gradle.kts
index c721746f..be5eb421 100644
--- a/reactive/kotlinx-coroutines-jdk9/build.gradle.kts
+++ b/reactive/kotlinx-coroutines-jdk9/build.gradle.kts
@@ -1,10 +1,9 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
dependencies {
- compile(project(":kotlinx-coroutines-reactive"))
- compile("org.reactivestreams:reactive-streams-flow-adapters:${version("reactive_streams")}")
+ implementation(project(":kotlinx-coroutines-reactive"))
}
tasks {
diff --git a/reactive/kotlinx-coroutines-jdk9/src/Await.kt b/reactive/kotlinx-coroutines-jdk9/src/Await.kt
index 88268890..dfe6ec52 100644
--- a/reactive/kotlinx-coroutines-jdk9/src/Await.kt
+++ b/reactive/kotlinx-coroutines-jdk9/src/Await.kt
@@ -1,81 +1,85 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.jdk9
+import kotlinx.coroutines.*
import java.util.concurrent.*
import org.reactivestreams.FlowAdapters
import kotlinx.coroutines.reactive.*
/**
- * Awaits for the first value from the given publisher without blocking a thread and
- * returns the resulting value or throws the corresponding exception if this publisher had produced error.
+ * Awaits the first value from the given publisher without blocking the thread and returns the resulting value, or, if
+ * the publisher has produced an error, throws the corresponding exception.
*
* This suspending function is cancellable.
- * If the [Job] of the current coroutine is cancelled or completed while this suspending function is waiting, this function
- * immediately resumes with [CancellationException].
+ * If the [Job] of the current coroutine is cancelled or completed while the suspending function is waiting, this
+ * function immediately cancels its [Flow.Subscription] and resumes with [CancellationException].
*
- * @throws NoSuchElementException if publisher does not emit any value
+ * @throws NoSuchElementException if the publisher does not emit any value
*/
-public suspend fun <T> Flow.Publisher<T>.awaitFirst(): T = FlowAdapters.toPublisher(this).awaitFirst()
+public suspend fun <T> Flow.Publisher<T>.awaitFirst(): T =
+ FlowAdapters.toPublisher(this).awaitFirst()
/**
- * Awaits for the first value from the given observable or the [default] value if none is emitted without blocking a
- * thread and returns the resulting value or throws the corresponding exception if this observable had produced error.
+ * Awaits the first value from the given publisher, or returns the [default] value if none is emitted, without blocking
+ * the thread, and returns the resulting value, or, if this publisher has produced an error, throws the corresponding
+ * exception.
*
* This suspending function is cancellable.
- * If the [Job] of the current coroutine is cancelled or completed while this suspending function is waiting, this function
- * immediately resumes with [CancellationException].
+ * If the [Job] of the current coroutine is cancelled or completed while the suspending function is waiting, this
+ * function immediately cancels its [Flow.Subscription] and resumes with [CancellationException].
*/
public suspend fun <T> Flow.Publisher<T>.awaitFirstOrDefault(default: T): T =
- FlowAdapters.toPublisher(this).awaitFirstOrDefault(default)
+ FlowAdapters.toPublisher(this).awaitFirstOrDefault(default)
/**
- * Awaits for the first value from the given observable or `null` value if none is emitted without blocking a
- * thread and returns the resulting value or throws the corresponding exception if this observable had produced error.
+ * Awaits the first value from the given publisher, or returns `null` if none is emitted, without blocking the thread,
+ * and returns the resulting value, or, if this publisher has produced an error, throws the corresponding exception.
*
* This suspending function is cancellable.
- * If the [Job] of the current coroutine is cancelled or completed while this suspending function is waiting, this function
- * immediately resumes with [CancellationException].
+ * If the [Job] of the current coroutine is cancelled or completed while the suspending function is waiting, this
+ * function immediately cancels its [Flow.Subscription] and resumes with [CancellationException].
*/
public suspend fun <T> Flow.Publisher<T>.awaitFirstOrNull(): T? =
- FlowAdapters.toPublisher(this).awaitFirstOrNull()
+ FlowAdapters.toPublisher(this).awaitFirstOrNull()
/**
- * Awaits for the first value from the given observable or call [defaultValue] to get a value if none is emitted without blocking a
- * thread and returns the resulting value or throws the corresponding exception if this observable had produced error.
+ * Awaits the first value from the given publisher, or calls [defaultValue] to get a value if none is emitted, without
+ * blocking the thread, and returns the resulting value, or, if this publisher has produced an error, throws the
+ * corresponding exception.
*
* This suspending function is cancellable.
- * If the [Job] of the current coroutine is cancelled or completed while this suspending function is waiting, this function
- * immediately resumes with [CancellationException].
+ * If the [Job] of the current coroutine is cancelled or completed while the suspending function is waiting, this
+ * function immediately cancels its [Flow.Subscription] and resumes with [CancellationException].
*/
public suspend fun <T> Flow.Publisher<T>.awaitFirstOrElse(defaultValue: () -> T): T =
- FlowAdapters.toPublisher(this).awaitFirstOrElse(defaultValue)
+ FlowAdapters.toPublisher(this).awaitFirstOrElse(defaultValue)
/**
- * Awaits for the last value from the given publisher without blocking a thread and
- * returns the resulting value or throws the corresponding exception if this publisher had produced error.
+ * Awaits the last value from the given publisher without blocking the thread and
+ * returns the resulting value, or, if this publisher has produced an error, throws the corresponding exception.
*
* This suspending function is cancellable.
- * If the [Job] of the current coroutine is cancelled or completed while this suspending function is waiting, this function
- * immediately resumes with [CancellationException].
+ * If the [Job] of the current coroutine is cancelled or completed while the suspending function is waiting, this
+ * function immediately cancels its [Flow.Subscription] and resumes with [CancellationException].
*
- * @throws NoSuchElementException if publisher does not emit any value
+ * @throws NoSuchElementException if the publisher does not emit any value
*/
public suspend fun <T> Flow.Publisher<T>.awaitLast(): T =
- FlowAdapters.toPublisher(this).awaitLast()
+ FlowAdapters.toPublisher(this).awaitLast()
/**
- * Awaits for the single value from the given publisher without blocking a thread and
- * returns the resulting value or throws the corresponding exception if this publisher had produced error.
+ * Awaits the single value from the given publisher without blocking the thread and returns the resulting value, or,
+ * if this publisher has produced an error, throws the corresponding exception.
*
* This suspending function is cancellable.
- * If the [Job] of the current coroutine is cancelled or completed while this suspending function is waiting, this function
- * immediately resumes with [CancellationException].
+ * If the [Job] of the current coroutine is cancelled or completed while the suspending function is waiting, this
+ * function immediately cancels its [Flow.Subscription] and resumes with [CancellationException].
*
- * @throws NoSuchElementException if publisher does not emit any value
- * @throws IllegalArgumentException if publisher emits more than one value
+ * @throws NoSuchElementException if the publisher does not emit any value
+ * @throws IllegalArgumentException if the publisher emits more than one value
*/
public suspend fun <T> Flow.Publisher<T>.awaitSingle(): T =
- FlowAdapters.toPublisher(this).awaitSingle()
+ FlowAdapters.toPublisher(this).awaitSingle()
diff --git a/reactive/kotlinx-coroutines-jdk9/src/Publish.kt b/reactive/kotlinx-coroutines-jdk9/src/Publish.kt
index 6fd9a5e7..529bc12f 100644
--- a/reactive/kotlinx-coroutines-jdk9/src/Publish.kt
+++ b/reactive/kotlinx-coroutines-jdk9/src/Publish.kt
@@ -1,38 +1,38 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.jdk9
import kotlinx.coroutines.*
import kotlinx.coroutines.channels.*
+import kotlinx.coroutines.reactive.*
import java.util.concurrent.*
import kotlin.coroutines.*
import org.reactivestreams.FlowAdapters
/**
- * Creates cold reactive [Flow.Publisher] that runs a given [block] in a coroutine.
+ * Creates a cold reactive [Flow.Publisher] that runs a given [block] in a coroutine.
+ *
* Every time the returned flux is subscribed, it starts a new coroutine in the specified [context].
- * Coroutine emits ([Subscriber.onNext]) values with `send`, completes ([Subscriber.onComplete])
- * when the coroutine completes or channel is explicitly closed and emits error ([Subscriber.onError])
- * if coroutine throws an exception or closes channel with a cause.
- * Unsubscribing cancels running coroutine.
+ * The coroutine emits (via [Flow.Subscriber.onNext]) values with [send][ProducerScope.send],
+ * completes (via [Flow.Subscriber.onComplete]) when the coroutine completes or channel is explicitly closed, and emits
+ * errors (via [Flow.Subscriber.onError]) if the coroutine throws an exception or closes channel with a cause.
+ * Unsubscribing cancels the running coroutine.
*
- * Invocations of `send` are suspended appropriately when subscribers apply back-pressure and to ensure that
- * `onNext` is not invoked concurrently.
+ * Invocations of [send][ProducerScope.send] are suspended appropriately when subscribers apply back-pressure and to
+ * ensure that [onNext][Flow.Subscriber.onNext] is not invoked concurrently.
*
* Coroutine context can be specified with [context] argument.
- * If the context does not have any dispatcher nor any other [ContinuationInterceptor], then [Dispatchers.Default] is used.
- * Method throws [IllegalArgumentException] if provided [context] contains a [Job] instance.
+ * If the context does not have any dispatcher nor any other [ContinuationInterceptor], then [Dispatchers.Default] is
+ * used.
*
* **Note: This is an experimental api.** Behaviour of publishers that work as children in a parent scope with respect
* to cancellation and error handling may change in the future.
+ *
+ * @throws IllegalArgumentException if the provided [context] contains a [Job] instance.
*/
-@ExperimentalCoroutinesApi // Since 1.3.x
public fun <T> flowPublish(
context: CoroutineContext = EmptyCoroutineContext,
@BuilderInference block: suspend ProducerScope<T>.() -> Unit
-): Flow.Publisher<T> {
- val reactivePublisher : org.reactivestreams.Publisher<T> = kotlinx.coroutines.reactive.publish<T>(context, block)
- return FlowAdapters.toFlowPublisher(reactivePublisher)
-}
+): Flow.Publisher<T> = FlowAdapters.toFlowPublisher(publish(context, block))
diff --git a/reactive/kotlinx-coroutines-jdk9/src/ReactiveFlow.kt b/reactive/kotlinx-coroutines-jdk9/src/ReactiveFlow.kt
index 5d546dff..6031e0a8 100644
--- a/reactive/kotlinx-coroutines-jdk9/src/ReactiveFlow.kt
+++ b/reactive/kotlinx-coroutines-jdk9/src/ReactiveFlow.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.jdk9
@@ -7,41 +7,43 @@ package kotlinx.coroutines.jdk9
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.reactive.asFlow
-import kotlinx.coroutines.reactive.asPublisher
+import kotlinx.coroutines.reactive.asPublisher as asReactivePublisher
import kotlinx.coroutines.reactive.collect
+import kotlinx.coroutines.channels.*
import org.reactivestreams.*
import kotlin.coroutines.*
import java.util.concurrent.Flow as JFlow
/**
- * Transforms the given reactive [Publisher] into [Flow].
- * Use [buffer] operator on the resulting flow to specify the size of the backpressure.
- * More precisely, it specifies the value of the subscription's [request][Subscription.request].
- * [buffer] default capacity is used by default.
+ * Transforms the given reactive [Flow Publisher][JFlow.Publisher] into [Flow].
+ * Use the [buffer] operator on the resulting flow to specify the size of the back-pressure.
+ * In effect, it specifies the value of the subscription's [request][JFlow.Subscription.request].
+ * The [default buffer capacity][Channel.BUFFERED] for a suspending channel is used by default.
*
- * If any of the resulting flow transformations fails, subscription is immediately cancelled and all in-flight elements
- * are discarded.
+ * If any of the resulting flow transformations fails, the subscription is immediately cancelled and all the in-flight
+ * elements are discarded.
*/
public fun <T : Any> JFlow.Publisher<T>.asFlow(): Flow<T> =
- FlowAdapters.toPublisher(this).asFlow()
+ FlowAdapters.toPublisher(this).asFlow()
/**
- * Transforms the given flow to a reactive specification compliant [Publisher].
+ * Transforms the given flow into a reactive specification compliant [Flow Publisher][JFlow.Publisher].
*
- * An optional [context] can be specified to control the execution context of calls to [Subscriber] methods.
- * You can set a [CoroutineDispatcher] to confine them to a specific thread and/or various [ThreadContextElement] to
+ * An optional [context] can be specified to control the execution context of calls to the [Flow Subscriber][Subscriber]
+ * methods.
+ * A [CoroutineDispatcher] can be set to confine them to a specific thread; various [ThreadContextElement] can be set to
* inject additional context into the caller thread. By default, the [Unconfined][Dispatchers.Unconfined] dispatcher
* is used, so calls are performed from an arbitrary thread.
*/
@JvmOverloads // binary compatibility
-public fun <T : Any> Flow<T>.asPublisher(context: CoroutineContext = EmptyCoroutineContext): JFlow.Publisher<T> {
- val reactivePublisher : org.reactivestreams.Publisher<T> = this.asPublisher<T>(context)
- return FlowAdapters.toFlowPublisher(reactivePublisher)
-}
+public fun <T : Any> Flow<T>.asPublisher(context: CoroutineContext = EmptyCoroutineContext): JFlow.Publisher<T> =
+ FlowAdapters.toFlowPublisher(asReactivePublisher(context))
/**
- * Subscribes to this [Publisher] and performs the specified action for each received element.
- * Cancels subscription if any exception happens during collect.
+ * Subscribes to this [Flow Publisher][JFlow.Publisher] and performs the specified action for each received element.
+ *
+ * If [action] throws an exception at some point, the subscription is cancelled, and the exception is rethrown from
+ * [collect]. Also, if the publisher signals an error, that error is rethrown from [collect].
*/
public suspend inline fun <T> JFlow.Publisher<T>.collect(action: (T) -> Unit): Unit =
FlowAdapters.toPublisher(this).collect(action)
diff --git a/reactive/kotlinx-coroutines-jdk9/test/AwaitTest.kt b/reactive/kotlinx-coroutines-jdk9/test/AwaitTest.kt
new file mode 100644
index 00000000..5a95d098
--- /dev/null
+++ b/reactive/kotlinx-coroutines-jdk9/test/AwaitTest.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.jdk9
+
+import kotlinx.coroutines.*
+import org.junit.*
+import java.util.concurrent.Flow as JFlow
+
+class AwaitTest: TestBase() {
+
+ /** Tests that calls to [awaitFirst] (and, thus, to the rest of these functions) throw [CancellationException] and
+ * unsubscribe from the publisher when their [Job] is cancelled. */
+ @Test
+ fun testAwaitCancellation() = runTest {
+ expect(1)
+ val publisher = JFlow.Publisher<Int> { s ->
+ s.onSubscribe(object : JFlow.Subscription {
+ override fun request(n: Long) {
+ expect(3)
+ }
+
+ override fun cancel() {
+ expect(5)
+ }
+ })
+ }
+ val job = launch(start = CoroutineStart.UNDISPATCHED) {
+ try {
+ expect(2)
+ publisher.awaitFirst()
+ } catch (e: CancellationException) {
+ expect(6)
+ throw e
+ }
+ }
+ expect(4)
+ job.cancelAndJoin()
+ finish(7)
+ }
+
+} \ No newline at end of file
diff --git a/reactive/kotlinx-coroutines-jdk9/test/FlowAsPublisherTest.kt b/reactive/kotlinx-coroutines-jdk9/test/FlowAsPublisherTest.kt
index 488695de..b860e162 100644
--- a/reactive/kotlinx-coroutines-jdk9/test/FlowAsPublisherTest.kt
+++ b/reactive/kotlinx-coroutines-jdk9/test/FlowAsPublisherTest.kt
@@ -15,7 +15,7 @@ class FlowAsPublisherTest : TestBase() {
@Test
fun testErrorOnCancellationIsReported() {
expect(1)
- flow<Int> {
+ flow {
try {
emit(2)
} finally {
@@ -50,13 +50,13 @@ class FlowAsPublisherTest : TestBase() {
@Test
fun testCancellationIsNotReported() {
expect(1)
- flow<Int> {
+ flow {
emit(2)
}.asPublisher().subscribe(object : JFlow.Subscriber<Int> {
private lateinit var subscription: JFlow.Subscription
override fun onComplete() {
- expect(3)
+ expectUnreached()
}
override fun onSubscribe(s: JFlow.Subscription?) {
@@ -73,6 +73,21 @@ class FlowAsPublisherTest : TestBase() {
expectUnreached()
}
})
+ finish(3)
+ }
+
+ @Test
+ fun testFlowWithTimeout() = runTest {
+ val publisher = flow<Int> {
+ expect(2)
+ withTimeout(1) { delay(Long.MAX_VALUE) }
+ }.asPublisher()
+ try {
+ expect(1)
+ publisher.awaitFirstOrNull()
+ } catch (e: CancellationException) {
+ expect(3)
+ }
finish(4)
}
}
diff --git a/reactive/kotlinx-coroutines-jdk9/test/IntegrationTest.kt b/reactive/kotlinx-coroutines-jdk9/test/IntegrationTest.kt
index 5bfddfee..5b3542ad 100644
--- a/reactive/kotlinx-coroutines-jdk9/test/IntegrationTest.kt
+++ b/reactive/kotlinx-coroutines-jdk9/test/IntegrationTest.kt
@@ -5,10 +5,12 @@
package kotlinx.coroutines.jdk9
import kotlinx.coroutines.*
+import kotlinx.coroutines.exceptions.*
import org.junit.Test
import kotlinx.coroutines.flow.flowOn
import org.junit.runner.*
import org.junit.runners.*
+import kotlin.contracts.*
import java.util.concurrent.Flow as JFlow
import kotlin.coroutines.*
import kotlin.test.*
@@ -129,4 +131,20 @@ class IntegrationTest(
assertEquals(n, last)
}
-} \ No newline at end of file
+}
+
+@OptIn(ExperimentalContracts::class)
+internal suspend inline fun <reified E: Throwable> assertCallsExceptionHandlerWith(
+ crossinline operation: suspend (CoroutineExceptionHandler) -> Unit): E {
+ contract {
+ callsInPlace(operation, InvocationKind.EXACTLY_ONCE)
+ }
+ val handler = CapturingHandler()
+ return withContext(handler) {
+ operation(handler)
+ handler.getException().let {
+ assertTrue(it is E, it.toString())
+ it
+ }
+ }
+}
diff --git a/reactive/kotlinx-coroutines-jdk9/test/PublishTest.kt b/reactive/kotlinx-coroutines-jdk9/test/PublishTest.kt
index 1a36a389..3682d5e3 100644
--- a/reactive/kotlinx-coroutines-jdk9/test/PublishTest.kt
+++ b/reactive/kotlinx-coroutines-jdk9/test/PublishTest.kt
@@ -5,6 +5,7 @@
package kotlinx.coroutines.jdk9
import kotlinx.coroutines.*
+import kotlinx.coroutines.channels.*
import org.junit.Test
import java.util.concurrent.Flow as JFlow
import kotlin.test.*
@@ -121,44 +122,110 @@ class PublishTest : TestBase() {
finish(7)
}
+ /** Tests that, as soon as `ProducerScope.close` is called, `isClosedForSend` starts returning `true`. */
@Test
- fun testOnNextError() = runTest {
+ fun testChannelClosing() = runTest {
expect(1)
- val publisher = flowPublish(currentDispatcher()) {
+ val publisher = flowPublish<Int>(Dispatchers.Unconfined) {
+ expect(3)
+ close()
+ assert(isClosedForSend)
expect(4)
- try {
- send("OK")
- } catch(e: Throwable) {
- expect(6)
- assert(e is TestException)
- }
}
- expect(2)
+ try {
+ expect(2)
+ publisher.awaitFirstOrNull()
+ } catch (e: CancellationException) {
+ expect(5)
+ }
+ finish(6)
+ }
+
+ @Test
+ fun testOnNextError() = runTest {
val latch = CompletableDeferred<Unit>()
- publisher.subscribe(object : JFlow.Subscriber<String> {
- override fun onComplete() {
- expectUnreached()
+ expect(1)
+ assertCallsExceptionHandlerWith<TestException> { exceptionHandler ->
+ val publisher = flowPublish(currentDispatcher() + exceptionHandler) {
+ expect(4)
+ try {
+ send("OK")
+ } catch (e: Throwable) {
+ expect(6)
+ assert(e is TestException)
+ assert(isClosedForSend)
+ latch.complete(Unit)
+ }
}
+ expect(2)
+ publisher.subscribe(object : JFlow.Subscriber<String> {
+ override fun onComplete() {
+ expectUnreached()
+ }
- override fun onSubscribe(s: JFlow.Subscription) {
- expect(3)
- s.request(1)
- }
+ override fun onSubscribe(s: JFlow.Subscription) {
+ expect(3)
+ s.request(1)
+ }
- override fun onNext(t: String) {
- expect(5)
- assertEquals("OK", t)
- throw TestException()
- }
+ override fun onNext(t: String) {
+ expect(5)
+ assertEquals("OK", t)
+ throw TestException()
+ }
- override fun onError(t: Throwable) {
- expect(7)
- assert(t is TestException)
- latch.complete(Unit)
+ override fun onError(t: Throwable) {
+ expectUnreached()
+ }
+ })
+ latch.await()
+ }
+ finish(7)
+ }
+
+ /** Tests the behavior when a call to `onNext` fails after the channel is already closed. */
+ @Test
+ fun testOnNextErrorAfterCancellation() = runTest {
+ assertCallsExceptionHandlerWith<TestException> { handler ->
+ var producerScope: ProducerScope<Int>? = null
+ CompletableDeferred<Unit>()
+ expect(1)
+ var job: Job? = null
+ val publisher = flowPublish<Int>(handler + Dispatchers.Unconfined) {
+ producerScope = this
+ expect(4)
+ job = launch {
+ delay(Long.MAX_VALUE)
+ }
}
- })
- latch.await()
- finish(8)
+ expect(2)
+ publisher.subscribe(object: JFlow.Subscriber<Int> {
+ override fun onSubscribe(s: JFlow.Subscription) {
+ expect(3)
+ s.request(Long.MAX_VALUE)
+ }
+ override fun onNext(t: Int) {
+ expect(6)
+ assertEquals(1, t)
+ job!!.cancel()
+ throw TestException()
+ }
+ override fun onError(t: Throwable?) {
+ /* Correct changes to the implementation could lead to us entering or not entering this method, but
+ it only matters that if we do, it is the "correct" exception that was validly used to cancel the
+ coroutine that gets passed here and not `TestException`. */
+ assertTrue(t is CancellationException)
+ }
+ override fun onComplete() { expectUnreached() }
+ })
+ expect(5)
+ val result: ChannelResult<Unit> = producerScope!!.trySend(1)
+ val e = result.exceptionOrNull()!!
+ assertTrue(e is CancellationException, "The actual error: $e")
+ assertTrue(producerScope!!.isClosedForSend)
+ assertTrue(result.isFailure)
+ }
+ finish(7)
}
@Test
@@ -182,4 +249,39 @@ class PublishTest : TestBase() {
fun testIllegalArgumentException() {
assertFailsWith<IllegalArgumentException> { flowPublish<Int>(Job()) { } }
}
+
+ /** Tests that `trySend` doesn't throw in `flowPublish`. */
+ @Test
+ fun testTrySendNotThrowing() = runTest {
+ var producerScope: ProducerScope<Int>? = null
+ expect(1)
+ val publisher = flowPublish<Int>(Dispatchers.Unconfined) {
+ producerScope = this
+ expect(3)
+ delay(Long.MAX_VALUE)
+ }
+ val job = launch(start = CoroutineStart.UNDISPATCHED) {
+ expect(2)
+ publisher.awaitFirstOrNull()
+ expectUnreached()
+ }
+ job.cancel()
+ expect(4)
+ val result = producerScope!!.trySend(1)
+ assertTrue(result.isFailure)
+ finish(5)
+ }
+
+ /** Tests that all methods on `flowPublish` fail without closing the channel when attempting to emit `null`. */
+ @Test
+ fun testEmittingNull() = runTest {
+ val publisher = flowPublish {
+ assertFailsWith<NullPointerException> { send(null) }
+ assertFailsWith<NullPointerException> { trySend(null) }
+ @Suppress("DEPRECATION")
+ assertFailsWith<NullPointerException> { offer(null) }
+ send("OK")
+ }
+ assertEquals("OK", publisher.awaitFirstOrNull())
+ }
} \ No newline at end of file
diff --git a/reactive/kotlinx-coroutines-jdk9/test/PublisherAsFlowTest.kt b/reactive/kotlinx-coroutines-jdk9/test/PublisherAsFlowTest.kt
index 97f106b3..b5b2a0a2 100644
--- a/reactive/kotlinx-coroutines-jdk9/test/PublisherAsFlowTest.kt
+++ b/reactive/kotlinx-coroutines-jdk9/test/PublisherAsFlowTest.kt
@@ -70,7 +70,7 @@ class PublisherAsFlowTest : TestBase() {
send(it + 1)
expect(it + 1)
}
- assertFalse { offer(-1) }
+ assertFalse { trySend(-1).isSuccess }
}
publisher.asFlow().collect {
diff --git a/reactive/kotlinx-coroutines-jdk9/test/PublisherCollectTest.kt b/reactive/kotlinx-coroutines-jdk9/test/PublisherCollectTest.kt
new file mode 100644
index 00000000..c2e88483
--- /dev/null
+++ b/reactive/kotlinx-coroutines-jdk9/test/PublisherCollectTest.kt
@@ -0,0 +1,146 @@
+/*
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.jdk9
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.reactive.*
+import org.junit.Test
+import org.reactivestreams.*
+import kotlin.test.*
+import java.util.concurrent.Flow as JFlow
+
+class PublisherCollectTest: TestBase() {
+
+ /** Tests the simple scenario where the publisher outputs a bounded stream of values to collect. */
+ @Test
+ fun testCollect() = runTest {
+ val x = 100
+ val xSum = x * (x + 1) / 2
+ val publisher = JFlow.Publisher<Int> { subscriber ->
+ var requested = 0L
+ var lastOutput = 0
+ subscriber.onSubscribe(object: JFlow.Subscription {
+
+ override fun request(n: Long) {
+ requested += n
+ if (n <= 0) {
+ subscriber.onError(IllegalArgumentException())
+ return
+ }
+ while (lastOutput < x && lastOutput < requested) {
+ lastOutput += 1
+ subscriber.onNext(lastOutput)
+ }
+ if (lastOutput == x)
+ subscriber.onComplete()
+ }
+
+ override fun cancel() {
+ /** According to rule 3.5 of the
+ * [reactive spec](https://github.com/reactive-streams/reactive-streams-jvm/blob/v1.0.3/README.md#3.5),
+ * this method can be called by the subscriber at any point, so it's not an error if it's called
+ * in this scenario. */
+ }
+
+ })
+ }
+ var sum = 0
+ publisher.collect {
+ sum += it
+ }
+ assertEquals(xSum, sum)
+ }
+
+ /** Tests the behavior of [collect] when the publisher raises an error. */
+ @Test
+ fun testCollectThrowingPublisher() = runTest {
+ val errorString = "Too many elements requested"
+ val x = 100
+ val xSum = x * (x + 1) / 2
+ val publisher = Publisher<Int> { subscriber ->
+ var requested = 0L
+ var lastOutput = 0
+ subscriber.onSubscribe(object: Subscription {
+
+ override fun request(n: Long) {
+ requested += n
+ if (n <= 0) {
+ subscriber.onError(IllegalArgumentException())
+ return
+ }
+ while (lastOutput < x && lastOutput < requested) {
+ lastOutput += 1
+ subscriber.onNext(lastOutput)
+ }
+ if (lastOutput == x)
+ subscriber.onError(IllegalArgumentException(errorString))
+ }
+
+ override fun cancel() {
+ /** See the comment for the corresponding part of [testCollect]. */
+ }
+
+ })
+ }
+ var sum = 0
+ try {
+ publisher.collect {
+ sum += it
+ }
+ } catch (e: IllegalArgumentException) {
+ assertEquals(errorString, e.message)
+ }
+ assertEquals(xSum, sum)
+ }
+
+ /** Tests the behavior of [collect] when the action throws. */
+ @Test
+ fun testCollectThrowingAction() = runTest {
+ val errorString = "Too many elements produced"
+ val x = 100
+ val xSum = x * (x + 1) / 2
+ val publisher = Publisher<Int> { subscriber ->
+ var requested = 0L
+ var lastOutput = 0
+ subscriber.onSubscribe(object: Subscription {
+
+ override fun request(n: Long) {
+ requested += n
+ if (n <= 0) {
+ subscriber.onError(IllegalArgumentException())
+ return
+ }
+ while (lastOutput < x && lastOutput < requested) {
+ lastOutput += 1
+ subscriber.onNext(lastOutput)
+ }
+ }
+
+ override fun cancel() {
+ assertEquals(x, lastOutput)
+ expect(x + 2)
+ }
+
+ })
+ }
+ var sum = 0
+ try {
+ expect(1)
+ var i = 1
+ publisher.collect {
+ sum += it
+ i += 1
+ expect(i)
+ if (sum >= xSum) {
+ throw IllegalArgumentException(errorString)
+ }
+ }
+ } catch (e: IllegalArgumentException) {
+ expect(x + 3)
+ assertEquals(errorString, e.message)
+ }
+ finish(x + 4)
+ }
+} \ No newline at end of file
diff --git a/reactive/kotlinx-coroutines-reactive/README.md b/reactive/kotlinx-coroutines-reactive/README.md
index aed26226..ec59d3d9 100644
--- a/reactive/kotlinx-coroutines-reactive/README.md
+++ b/reactive/kotlinx-coroutines-reactive/README.md
@@ -32,19 +32,25 @@ Suspending extension functions and suspending iteration:
<!--- MODULE kotlinx-coroutines-core -->
<!--- INDEX kotlinx.coroutines -->
<!--- INDEX kotlinx.coroutines.flow -->
+
[Flow]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/-flow/index.html
+
<!--- INDEX kotlinx.coroutines.channels -->
+
[ProducerScope]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-producer-scope/index.html
+
<!--- MODULE kotlinx-coroutines-reactive -->
<!--- INDEX kotlinx.coroutines.reactive -->
+
[kotlinx.coroutines.reactive.publish]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-reactive/kotlinx.coroutines.reactive/publish.html
-[Publisher.asFlow]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-reactive/kotlinx.coroutines.reactive/org.reactivestreams.-publisher/as-flow.html
-[Flow.asPublisher]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-reactive/kotlinx.coroutines.reactive/kotlinx.coroutines.flow.-flow/as-publisher.html
-[org.reactivestreams.Publisher.awaitFirst]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-reactive/kotlinx.coroutines.reactive/org.reactivestreams.-publisher/await-first.html
-[org.reactivestreams.Publisher.awaitFirstOrDefault]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-reactive/kotlinx.coroutines.reactive/org.reactivestreams.-publisher/await-first-or-default.html
-[org.reactivestreams.Publisher.awaitFirstOrElse]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-reactive/kotlinx.coroutines.reactive/org.reactivestreams.-publisher/await-first-or-else.html
-[org.reactivestreams.Publisher.awaitFirstOrNull]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-reactive/kotlinx.coroutines.reactive/org.reactivestreams.-publisher/await-first-or-null.html
-[org.reactivestreams.Publisher.awaitSingle]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-reactive/kotlinx.coroutines.reactive/org.reactivestreams.-publisher/await-single.html
+[Publisher.asFlow]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-reactive/kotlinx.coroutines.reactive/as-flow.html
+[Flow.asPublisher]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-reactive/kotlinx.coroutines.reactive/as-publisher.html
+[org.reactivestreams.Publisher.awaitFirst]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-reactive/kotlinx.coroutines.reactive/await-first.html
+[org.reactivestreams.Publisher.awaitFirstOrDefault]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-reactive/kotlinx.coroutines.reactive/await-first-or-default.html
+[org.reactivestreams.Publisher.awaitFirstOrElse]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-reactive/kotlinx.coroutines.reactive/await-first-or-else.html
+[org.reactivestreams.Publisher.awaitFirstOrNull]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-reactive/kotlinx.coroutines.reactive/await-first-or-null.html
+[org.reactivestreams.Publisher.awaitSingle]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-reactive/kotlinx.coroutines.reactive/await-single.html
+
<!--- END -->
# Package kotlinx.coroutines.reactive
diff --git a/reactive/kotlinx-coroutines-reactive/api/kotlinx-coroutines-reactive.api b/reactive/kotlinx-coroutines-reactive/api/kotlinx-coroutines-reactive.api
index 961fdbe2..75f1b306 100644
--- a/reactive/kotlinx-coroutines-reactive/api/kotlinx-coroutines-reactive.api
+++ b/reactive/kotlinx-coroutines-reactive/api/kotlinx-coroutines-reactive.api
@@ -12,9 +12,9 @@ public final class kotlinx/coroutines/reactive/AwaitKt {
public final class kotlinx/coroutines/reactive/ChannelKt {
public static final fun collect (Lorg/reactivestreams/Publisher;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
- public static final fun consumeEach (Lorg/reactivestreams/Publisher;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static final fun openSubscription (Lorg/reactivestreams/Publisher;I)Lkotlinx/coroutines/channels/ReceiveChannel;
public static synthetic fun openSubscription$default (Lorg/reactivestreams/Publisher;IILjava/lang/Object;)Lkotlinx/coroutines/channels/ReceiveChannel;
+ public static final fun toChannel (Lorg/reactivestreams/Publisher;I)Lkotlinx/coroutines/channels/ReceiveChannel;
}
public abstract interface class kotlinx/coroutines/reactive/ContextInjector {
@@ -27,9 +27,9 @@ public final class kotlinx/coroutines/reactive/ConvertKt {
}
public final class kotlinx/coroutines/reactive/FlowKt {
- public static final fun asFlow (Lorg/reactivestreams/Publisher;)Lkotlinx/coroutines/flow/Flow;
- public static final fun asFlow (Lorg/reactivestreams/Publisher;I)Lkotlinx/coroutines/flow/Flow;
- public static final fun asPublisher (Lkotlinx/coroutines/flow/Flow;)Lorg/reactivestreams/Publisher;
+ public static final synthetic fun asFlow (Lorg/reactivestreams/Publisher;)Lkotlinx/coroutines/flow/Flow;
+ public static final synthetic fun asFlow (Lorg/reactivestreams/Publisher;I)Lkotlinx/coroutines/flow/Flow;
+ public static final synthetic fun asPublisher (Lkotlinx/coroutines/flow/Flow;)Lorg/reactivestreams/Publisher;
}
public final class kotlinx/coroutines/reactive/FlowSubscription : kotlinx/coroutines/AbstractCoroutine, org/reactivestreams/Subscription {
@@ -42,7 +42,7 @@ public final class kotlinx/coroutines/reactive/FlowSubscription : kotlinx/corout
public final class kotlinx/coroutines/reactive/PublishKt {
public static final fun publish (Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;)Lorg/reactivestreams/Publisher;
- public static final fun publish (Lkotlinx/coroutines/CoroutineScope;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;)Lorg/reactivestreams/Publisher;
+ public static final synthetic fun publish (Lkotlinx/coroutines/CoroutineScope;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;)Lorg/reactivestreams/Publisher;
public static synthetic fun publish$default (Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lorg/reactivestreams/Publisher;
public static synthetic fun publish$default (Lkotlinx/coroutines/CoroutineScope;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lorg/reactivestreams/Publisher;
public static final fun publishInternal (Lkotlinx/coroutines/CoroutineScope;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;)Lorg/reactivestreams/Publisher;
@@ -57,12 +57,12 @@ public final class kotlinx/coroutines/reactive/PublisherCoroutine : kotlinx/coro
public fun invokeOnClose (Lkotlin/jvm/functions/Function1;)Ljava/lang/Void;
public synthetic fun invokeOnClose (Lkotlin/jvm/functions/Function1;)V
public fun isClosedForSend ()Z
- public fun isFull ()Z
public fun offer (Ljava/lang/Object;)Z
public synthetic fun onCompleted (Ljava/lang/Object;)V
public fun registerSelectClause2 (Lkotlinx/coroutines/selects/SelectInstance;Ljava/lang/Object;Lkotlin/jvm/functions/Function2;)V
public fun request (J)V
public fun send (Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+ public fun trySend-JP2dKIU (Ljava/lang/Object;)Ljava/lang/Object;
}
public final class kotlinx/coroutines/reactive/ReactiveFlowKt {
diff --git a/reactive/kotlinx-coroutines-reactive/build.gradle.kts b/reactive/kotlinx-coroutines-reactive/build.gradle.kts
index 2ace4f9f..128d4d86 100644
--- a/reactive/kotlinx-coroutines-reactive/build.gradle.kts
+++ b/reactive/kotlinx-coroutines-reactive/build.gradle.kts
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
val reactiveStreamsVersion = property("reactive_streams_version")
diff --git a/reactive/kotlinx-coroutines-reactive/src/Await.kt b/reactive/kotlinx-coroutines-reactive/src/Await.kt
index 7956c260..fef1205a 100644
--- a/reactive/kotlinx-coroutines-reactive/src/Await.kt
+++ b/reactive/kotlinx-coroutines-reactive/src/Await.kt
@@ -1,123 +1,173 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.reactive
-import kotlinx.coroutines.CancellationException
-import kotlinx.coroutines.Job
-import kotlinx.coroutines.suspendCancellableCoroutine
+import kotlinx.coroutines.*
import org.reactivestreams.Publisher
import org.reactivestreams.Subscriber
import org.reactivestreams.Subscription
-import java.util.*
+import java.lang.IllegalStateException
import kotlin.coroutines.*
/**
- * Awaits for the first value from the given publisher without blocking a thread and
- * returns the resulting value or throws the corresponding exception if this publisher had produced error.
+ * Awaits the first value from the given publisher without blocking the thread and returns the resulting value, or, if
+ * the publisher has produced an error, throws the corresponding exception.
*
* This suspending function is cancellable.
- * If the [Job] of the current coroutine is cancelled or completed while this suspending function is waiting, this function
- * immediately resumes with [CancellationException].
+ * If the [Job] of the current coroutine is cancelled or completed while the suspending function is waiting, this
+ * function immediately cancels its [Subscription] and resumes with [CancellationException].
*
- * @throws NoSuchElementException if publisher does not emit any value
+ * @throws NoSuchElementException if the publisher does not emit any value
*/
public suspend fun <T> Publisher<T>.awaitFirst(): T = awaitOne(Mode.FIRST)
/**
- * Awaits for the first value from the given observable or the [default] value if none is emitted without blocking a
- * thread and returns the resulting value or throws the corresponding exception if this observable had produced error.
+ * Awaits the first value from the given publisher, or returns the [default] value if none is emitted, without blocking
+ * the thread, and returns the resulting value, or, if this publisher has produced an error, throws the corresponding
+ * exception.
*
* This suspending function is cancellable.
- * If the [Job] of the current coroutine is cancelled or completed while this suspending function is waiting, this function
- * immediately resumes with [CancellationException].
+ * If the [Job] of the current coroutine is cancelled or completed while the suspending function is waiting, this
+ * function immediately cancels its [Subscription] and resumes with [CancellationException].
*/
public suspend fun <T> Publisher<T>.awaitFirstOrDefault(default: T): T = awaitOne(Mode.FIRST_OR_DEFAULT, default)
/**
- * Awaits for the first value from the given observable or `null` value if none is emitted without blocking a
- * thread and returns the resulting value or throws the corresponding exception if this observable had produced error.
+ * Awaits the first value from the given publisher, or returns `null` if none is emitted, without blocking the thread,
+ * and returns the resulting value, or, if this publisher has produced an error, throws the corresponding exception.
*
* This suspending function is cancellable.
- * If the [Job] of the current coroutine is cancelled or completed while this suspending function is waiting, this function
- * immediately resumes with [CancellationException].
+ * If the [Job] of the current coroutine is cancelled or completed while the suspending function is waiting, this
+ * function immediately cancels its [Subscription] and resumes with [CancellationException].
*/
public suspend fun <T> Publisher<T>.awaitFirstOrNull(): T? = awaitOne(Mode.FIRST_OR_DEFAULT)
/**
- * Awaits for the first value from the given observable or call [defaultValue] to get a value if none is emitted without blocking a
- * thread and returns the resulting value or throws the corresponding exception if this observable had produced error.
+ * Awaits the first value from the given publisher, or calls [defaultValue] to get a value if none is emitted, without
+ * blocking the thread, and returns the resulting value, or, if this publisher has produced an error, throws the
+ * corresponding exception.
*
* This suspending function is cancellable.
- * If the [Job] of the current coroutine is cancelled or completed while this suspending function is waiting, this function
- * immediately resumes with [CancellationException].
+ * If the [Job] of the current coroutine is cancelled or completed while the suspending function is waiting, this
+ * function immediately cancels its [Subscription] and resumes with [CancellationException].
*/
public suspend fun <T> Publisher<T>.awaitFirstOrElse(defaultValue: () -> T): T = awaitOne(Mode.FIRST_OR_DEFAULT) ?: defaultValue()
/**
- * Awaits for the last value from the given publisher without blocking a thread and
- * returns the resulting value or throws the corresponding exception if this publisher had produced error.
+ * Awaits the last value from the given publisher without blocking the thread and
+ * returns the resulting value, or, if this publisher has produced an error, throws the corresponding exception.
*
* This suspending function is cancellable.
- * If the [Job] of the current coroutine is cancelled or completed while this suspending function is waiting, this function
- * immediately resumes with [CancellationException].
+ * If the [Job] of the current coroutine is cancelled or completed while the suspending function is waiting, this
+ * function immediately cancels its [Subscription] and resumes with [CancellationException].
*
- * @throws NoSuchElementException if publisher does not emit any value
+ * @throws NoSuchElementException if the publisher does not emit any value
*/
public suspend fun <T> Publisher<T>.awaitLast(): T = awaitOne(Mode.LAST)
/**
- * Awaits for the single value from the given publisher without blocking a thread and
- * returns the resulting value or throws the corresponding exception if this publisher had produced error.
+ * Awaits the single value from the given publisher without blocking the thread and returns the resulting value, or,
+ * if this publisher has produced an error, throws the corresponding exception.
*
* This suspending function is cancellable.
- * If the [Job] of the current coroutine is cancelled or completed while this suspending function is waiting, this function
- * immediately resumes with [CancellationException].
+ * If the [Job] of the current coroutine is cancelled or completed while the suspending function is waiting, this
+ * function immediately cancels its [Subscription] and resumes with [CancellationException].
*
- * @throws NoSuchElementException if publisher does not emit any value
- * @throws IllegalArgumentException if publisher emits more than one value
+ * @throws NoSuchElementException if the publisher does not emit any value
+ * @throws IllegalArgumentException if the publisher emits more than one value
*/
public suspend fun <T> Publisher<T>.awaitSingle(): T = awaitOne(Mode.SINGLE)
/**
- * Awaits for the single value from the given publisher or the [default] value if none is emitted without blocking a thread and
- * returns the resulting value or throws the corresponding exception if this publisher had produced error.
+ * Awaits the single value from the given publisher, or returns the [default] value if none is emitted, without
+ * blocking the thread, and returns the resulting value, or, if this publisher has produced an error, throws the
+ * corresponding exception.
*
* This suspending function is cancellable.
- * If the [Job] of the current coroutine is cancelled or completed while this suspending function is waiting, this function
- * immediately resumes with [CancellationException].
+ * If the [Job] of the current coroutine is cancelled or completed while the suspending function is waiting, this
+ * function immediately cancels its [Subscription] and resumes with [CancellationException].
*
- * @throws NoSuchElementException if publisher does not emit any value
- * @throws IllegalArgumentException if publisher emits more than one value
+ * ### Deprecation
+ *
+ * This method is deprecated because the conventions established in Kotlin mandate that an operation with the name
+ * `awaitSingleOrDefault` returns the default value instead of throwing in case there is an error; however, this would
+ * also mean that this method would return the default value if there are *too many* values. This could be confusing to
+ * those who expect this function to validate that there is a single element or none at all emitted, and cases where
+ * there are no elements are indistinguishable from those where there are too many, though these cases have different
+ * meaning.
+ *
+ * @throws NoSuchElementException if the publisher does not emit any value
+ * @throws IllegalArgumentException if the publisher emits more than one value
+ *
+ * @suppress
*/
+@Deprecated(
+ message = "Deprecated without a replacement due to its name incorrectly conveying the behavior. " +
+ "Please consider using awaitFirstOrDefault().",
+ level = DeprecationLevel.WARNING
+) // Warning since 1.5, error in 1.6, hidden in 1.7
public suspend fun <T> Publisher<T>.awaitSingleOrDefault(default: T): T = awaitOne(Mode.SINGLE_OR_DEFAULT, default)
/**
- * Awaits for the single value from the given publisher or `null` value if none is emitted without blocking a thread and
- * returns the resulting value or throws the corresponding exception if this publisher had produced error.
+ * Awaits the single value from the given publisher without blocking the thread and returns the resulting value, or, if
+ * this publisher has produced an error, throws the corresponding exception. If more than one value or none were
+ * produced by the publisher, `null` is returned.
*
* This suspending function is cancellable.
- * If the [Job] of the current coroutine is cancelled or completed while this suspending function is waiting, this function
- * immediately resumes with [CancellationException].
+ * If the [Job] of the current coroutine is cancelled or completed while the suspending function is waiting, this
+ * function immediately cancels its [Subscription] and resumes with [CancellationException].
+ *
+ * ### Deprecation
*
- * @throws NoSuchElementException if publisher does not emit any value
- * @throws IllegalArgumentException if publisher emits more than one value
+ * This method is deprecated because the conventions established in Kotlin mandate that an operation with the name
+ * `awaitSingleOrNull` returns `null` instead of throwing in case there is an error; however, this would
+ * also mean that this method would return `null` if there are *too many* values. This could be confusing to
+ * those who expect this function to validate that there is a single element or none at all emitted, and cases where
+ * there are no elements are indistinguishable from those where there are too many, though these cases have different
+ * meaning.
+ *
+ * @throws IllegalArgumentException if the publisher emits more than one value
+ * @suppress
*/
-public suspend fun <T> Publisher<T>.awaitSingleOrNull(): T = awaitOne(Mode.SINGLE_OR_DEFAULT)
+@Deprecated(
+ message = "Deprecated without a replacement due to its name incorrectly conveying the behavior. " +
+ "There is a specialized version for Reactor's Mono, please use that where applicable. " +
+ "Alternatively, please consider using awaitFirstOrNull().",
+ level = DeprecationLevel.WARNING,
+ replaceWith = ReplaceWith("this.awaitSingleOrNull()", "kotlinx.coroutines.reactor")
+) // Warning since 1.5, error in 1.6, hidden in 1.7
+public suspend fun <T> Publisher<T>.awaitSingleOrNull(): T? = awaitOne(Mode.SINGLE_OR_DEFAULT)
/**
- * Awaits for the single value from the given publisher or call [defaultValue] to get a value if none is emitted without blocking a thread and
- * returns the resulting value or throws the corresponding exception if this publisher had produced error.
+ * Awaits the single value from the given publisher, or calls [defaultValue] to get a value if none is emitted, without
+ * blocking the thread, and returns the resulting value, or, if this publisher has produced an error, throws the
+ * corresponding exception.
*
* This suspending function is cancellable.
- * If the [Job] of the current coroutine is cancelled or completed while this suspending function is waiting, this function
- * immediately resumes with [CancellationException].
+ * If the [Job] of the current coroutine is cancelled or completed while the suspending function is waiting, this
+ * function immediately cancels its [Subscription] and resumes with [CancellationException].
+ *
+ * ### Deprecation
+ *
+ * This method is deprecated because the conventions established in Kotlin mandate that an operation with the name
+ * `awaitSingleOrElse` returns the calculated value instead of throwing in case there is an error; however, this would
+ * also mean that this method would return the calculated value if there are *too many* values. This could be confusing
+ * to those who expect this function to validate that there is a single element or none at all emitted, and cases where
+ * there are no elements are indistinguishable from those where there are too many, though these cases have different
+ * meaning.
*
- * @throws NoSuchElementException if publisher does not emit any value
- * @throws IllegalArgumentException if publisher emits more than one value
+ * @throws IllegalArgumentException if the publisher emits more than one value
+ * @suppress
*/
-public suspend fun <T> Publisher<T>.awaitSingleOrElse(defaultValue: () -> T): T = awaitOne(Mode.SINGLE_OR_DEFAULT) ?: defaultValue()
+@Deprecated(
+ message = "Deprecated without a replacement due to its name incorrectly conveying the behavior. " +
+ "Please consider using awaitFirstOrElse().",
+ level = DeprecationLevel.WARNING
+) // Warning since 1.5, error in 1.6, hidden in 1.7
+public suspend fun <T> Publisher<T>.awaitSingleOrElse(defaultValue: () -> T): T =
+ awaitOne(Mode.SINGLE_OR_DEFAULT) ?: defaultValue()
// ------------------------ private ------------------------
@@ -134,31 +184,61 @@ private suspend fun <T> Publisher<T>.awaitOne(
mode: Mode,
default: T? = null
): T = suspendCancellableCoroutine { cont ->
+ /* This implementation must obey
+ https://github.com/reactive-streams/reactive-streams-jvm/blob/v1.0.3/README.md#2-subscriber-code
+ The numbers of rules are taken from there. */
injectCoroutineContext(cont.context).subscribe(object : Subscriber<T> {
- private lateinit var subscription: Subscription
+ // It is unclear whether 2.13 implies (T: Any), but if so, it seems that we don't break anything by not adhering
+ private var subscription: Subscription? = null
private var value: T? = null
private var seenValue = false
+ private var inTerminalState = false
override fun onSubscribe(sub: Subscription) {
+ /** cancelling the new subscription due to rule 2.5, though the publisher would either have to
+ * subscribe more than once, which would break 2.12, or leak this [Subscriber]. */
+ if (subscription != null) {
+ sub.cancel()
+ return
+ }
subscription = sub
cont.invokeOnCancellation { sub.cancel() }
- sub.request(if (mode == Mode.FIRST) 1 else Long.MAX_VALUE)
+ sub.request(if (mode == Mode.FIRST || mode == Mode.FIRST_OR_DEFAULT) 1 else Long.MAX_VALUE)
}
override fun onNext(t: T) {
+ val sub = subscription.let {
+ if (it == null) {
+ /** Enforce rule 1.9: expect [Subscriber.onSubscribe] before any other signals. */
+ handleCoroutineException(cont.context,
+ IllegalStateException("'onNext' was called before 'onSubscribe'"))
+ return
+ } else {
+ it
+ }
+ }
+ if (inTerminalState) {
+ gotSignalInTerminalStateException(cont.context, "onNext")
+ return
+ }
when (mode) {
Mode.FIRST, Mode.FIRST_OR_DEFAULT -> {
- if (!seenValue) {
- seenValue = true
- subscription.cancel()
- cont.resume(t)
+ if (seenValue) {
+ moreThanOneValueProvidedException(cont.context, mode)
+ return
}
+ seenValue = true
+ sub.cancel()
+ cont.resume(t)
}
Mode.LAST, Mode.SINGLE, Mode.SINGLE_OR_DEFAULT -> {
if ((mode == Mode.SINGLE || mode == Mode.SINGLE_OR_DEFAULT) && seenValue) {
- subscription.cancel()
- if (cont.isActive)
+ sub.cancel()
+ /* the check for `cont.isActive` is needed in case `sub.cancel() above calls `onComplete` or
+ `onError` on its own. */
+ if (cont.isActive) {
cont.resumeWithException(IllegalArgumentException("More than one onNext value for $mode"))
+ }
} else {
value = t
seenValue = true
@@ -169,8 +249,16 @@ private suspend fun <T> Publisher<T>.awaitOne(
@Suppress("UNCHECKED_CAST")
override fun onComplete() {
+ if (!tryEnterTerminalState("onComplete")) {
+ return
+ }
if (seenValue) {
- if (cont.isActive) cont.resume(value as T)
+ /* the check for `cont.isActive` is needed because, otherwise, if the publisher doesn't acknowledge the
+ call to `cancel` for modes `SINGLE*` when more than one value was seen, it may call `onComplete`, and
+ here `cont.resume` would fail. */
+ if (mode != Mode.FIRST_OR_DEFAULT && mode != Mode.FIRST && cont.isActive) {
+ cont.resume(value as T)
+ }
return
}
when {
@@ -178,14 +266,43 @@ private suspend fun <T> Publisher<T>.awaitOne(
cont.resume(default as T)
}
cont.isActive -> {
+ // the check for `cont.isActive` is just a slight optimization and doesn't affect correctness
cont.resumeWithException(NoSuchElementException("No value received via onNext for $mode"))
}
}
}
override fun onError(e: Throwable) {
- cont.resumeWithException(e)
+ if (tryEnterTerminalState("onError")) {
+ cont.resumeWithException(e)
+ }
+ }
+
+ /**
+ * Enforce rule 2.4: assume that the [Publisher] is in a terminal state after [onError] or [onComplete].
+ */
+ private fun tryEnterTerminalState(signalName: String): Boolean {
+ if (inTerminalState) {
+ gotSignalInTerminalStateException(cont.context, signalName)
+ return false
+ }
+ inTerminalState = true
+ return true
}
})
}
+/**
+ * Enforce rule 2.4 (detect publishers that don't respect rule 1.7): don't process anything after a terminal
+ * state was reached.
+ */
+private fun gotSignalInTerminalStateException(context: CoroutineContext, signalName: String) =
+ handleCoroutineException(context,
+ IllegalStateException("'$signalName' was called after the publisher already signalled being in a terminal state"))
+
+/**
+ * Enforce rule 1.1: it is invalid for a publisher to provide more values than requested.
+ */
+private fun moreThanOneValueProvidedException(context: CoroutineContext, mode: Mode) =
+ handleCoroutineException(context,
+ IllegalStateException("Only a single value was requested in '$mode', but the publisher provided more"))
diff --git a/reactive/kotlinx-coroutines-reactive/src/Channel.kt b/reactive/kotlinx-coroutines-reactive/src/Channel.kt
index 26f14ec6..b7fbf134 100644
--- a/reactive/kotlinx-coroutines-reactive/src/Channel.kt
+++ b/reactive/kotlinx-coroutines-reactive/src/Channel.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.reactive
@@ -11,10 +11,10 @@ import kotlinx.coroutines.internal.*
import org.reactivestreams.*
/**
- * Subscribes to this [Publisher] and returns a channel to receive elements emitted by it.
- * The resulting channel shall be [cancelled][ReceiveChannel.cancel] to unsubscribe from this publisher.
+ * Subscribes to this [Publisher] and returns a channel to receive the elements emitted by it.
+ * The resulting channel needs to be [cancelled][ReceiveChannel.cancel] in order to unsubscribe from this publisher.
- * @param request how many items to request from publisher in advance (optional, one by default).
+ * @param request how many items to request from the publisher in advance (optional, a single element by default).
*
* This method is deprecated in the favor of [Flow].
* Instead of iterating over the resulting channel please use [collect][Flow.collect]:
@@ -26,24 +26,28 @@ import org.reactivestreams.*
*/
@Deprecated(
message = "Transforming publisher to channel is deprecated, use asFlow() instead",
- level = DeprecationLevel.WARNING) // Will be error in 1.4
+ level = DeprecationLevel.ERROR) // Will be error in 1.4
public fun <T> Publisher<T>.openSubscription(request: Int = 1): ReceiveChannel<T> {
val channel = SubscriptionChannel<T>(request)
subscribe(channel)
return channel
}
-// Will be promoted to error in 1.3.0, removed in 1.4.0
-@Deprecated(message = "Use collect instead", level = DeprecationLevel.ERROR, replaceWith = ReplaceWith("this.collect(action)"))
-public suspend inline fun <T> Publisher<T>.consumeEach(action: (T) -> Unit): Unit =
- openSubscription().consumeEach(action)
-
/**
* Subscribes to this [Publisher] and performs the specified action for each received element.
- * Cancels subscription if any exception happens during collect.
+ *
+ * If [action] throws an exception at some point, the subscription is cancelled, and the exception is rethrown from
+ * [collect]. Also, if the publisher signals an error, that error is rethrown from [collect].
*/
public suspend inline fun <T> Publisher<T>.collect(action: (T) -> Unit): Unit =
- openSubscription().consumeEach(action)
+ toChannel().consumeEach(action)
+
+@PublishedApi
+internal fun <T> Publisher<T>.toChannel(request: Int = 1): ReceiveChannel<T> {
+ val channel = SubscriptionChannel<T>(request)
+ subscribe(channel)
+ return channel
+}
@Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER", "SubscriberImplementation")
private class SubscriptionChannel<T>(
@@ -59,7 +63,7 @@ private class SubscriptionChannel<T>(
// can be negative if we have receivers, but no subscription yet
private val _requested = atomic(0)
- // AbstractChannel overrides
+ // --------------------- AbstractChannel overrides -------------------------------
@Suppress("CANNOT_OVERRIDE_INVISIBLE_MEMBER")
override fun onReceiveEnqueued() {
_requested.loop { wasRequested ->
@@ -87,7 +91,7 @@ private class SubscriptionChannel<T>(
_subscription.getAndSet(null)?.cancel() // cancel exactly once
}
- // Subscriber overrides
+ // --------------------- Subscriber overrides -------------------------------
override fun onSubscribe(s: Subscription) {
_subscription.value = s
while (true) { // lock-free loop on _requested
@@ -107,7 +111,7 @@ private class SubscriptionChannel<T>(
override fun onNext(t: T) {
_requested.decrementAndGet()
- offer(t)
+ trySend(t) // Safe to ignore return value here, expectedly racing with cancellation
}
override fun onComplete() {
diff --git a/reactive/kotlinx-coroutines-reactive/src/ContextInjector.kt b/reactive/kotlinx-coroutines-reactive/src/ContextInjector.kt
index d5390fd6..043e223d 100644
--- a/reactive/kotlinx-coroutines-reactive/src/ContextInjector.kt
+++ b/reactive/kotlinx-coroutines-reactive/src/ContextInjector.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.reactive
diff --git a/reactive/kotlinx-coroutines-reactive/src/Convert.kt b/reactive/kotlinx-coroutines-reactive/src/Convert.kt
index 727eff8b..3cb05b60 100644
--- a/reactive/kotlinx-coroutines-reactive/src/Convert.kt
+++ b/reactive/kotlinx-coroutines-reactive/src/Convert.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.reactive
@@ -16,8 +16,8 @@ import kotlin.coroutines.*
* @param context -- the coroutine context from which the resulting observable is going to be signalled
*/
@Deprecated(message = "Deprecated in the favour of consumeAsFlow()",
- level = DeprecationLevel.WARNING, // Error in 1.4
- replaceWith = ReplaceWith("this.consumeAsFlow().asPublisher()"))
+ level = DeprecationLevel.ERROR, // Error in 1.4
+ replaceWith = ReplaceWith("this.consumeAsFlow().asPublisher(context)", imports = ["kotlinx.coroutines.flow.consumeAsFlow"]))
public fun <T> ReceiveChannel<T>.asPublisher(context: CoroutineContext = EmptyCoroutineContext): Publisher<T> = publish(context) {
for (t in this@asPublisher)
send(t)
diff --git a/reactive/kotlinx-coroutines-reactive/src/Migration.kt b/reactive/kotlinx-coroutines-reactive/src/Migration.kt
index 4e0dca63..41927e67 100644
--- a/reactive/kotlinx-coroutines-reactive/src/Migration.kt
+++ b/reactive/kotlinx-coroutines-reactive/src/Migration.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
@file:JvmMultifileClass
@@ -12,25 +12,28 @@ import kotlinx.coroutines.flow.*
import org.reactivestreams.*
// Binary compatibility with Spring 5.2 RC
+/** @suppress */
@Deprecated(
message = "Replaced in favor of ReactiveFlow extension, please import kotlinx.coroutines.reactive.* instead of kotlinx.coroutines.reactive.FlowKt",
- level = DeprecationLevel.ERROR
+ level = DeprecationLevel.HIDDEN
)
@JvmName("asFlow")
public fun <T : Any> Publisher<T>.asFlowDeprecated(): Flow<T> = asFlow()
// Binary compatibility with Spring 5.2 RC
+/** @suppress */
@Deprecated(
message = "Replaced in favor of ReactiveFlow extension, please import kotlinx.coroutines.reactive.* instead of kotlinx.coroutines.reactive.FlowKt",
- level = DeprecationLevel.ERROR
+ level = DeprecationLevel.HIDDEN
)
@JvmName("asPublisher")
public fun <T : Any> Flow<T>.asPublisherDeprecated(): Publisher<T> = asPublisher()
+/** @suppress */
@FlowPreview
@Deprecated(
message = "batchSize parameter is deprecated, use .buffer() instead to control the backpressure",
- level = DeprecationLevel.ERROR,
+ level = DeprecationLevel.HIDDEN,
replaceWith = ReplaceWith("asFlow().buffer(batchSize)", imports = ["kotlinx.coroutines.flow.*"])
)
public fun <T : Any> Publisher<T>.asFlow(batchSize: Int): Flow<T> = asFlow().buffer(batchSize)
diff --git a/reactive/kotlinx-coroutines-reactive/src/Publish.kt b/reactive/kotlinx-coroutines-reactive/src/Publish.kt
index ddfd7f8a..4928a743 100644
--- a/reactive/kotlinx-coroutines-reactive/src/Publish.kt
+++ b/reactive/kotlinx-coroutines-reactive/src/Publish.kt
@@ -1,9 +1,6 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
-
-@file:Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER")
-
package kotlinx.coroutines.reactive
import kotlinx.atomicfu.*
@@ -13,27 +10,28 @@ import kotlinx.coroutines.selects.*
import kotlinx.coroutines.sync.*
import org.reactivestreams.*
import kotlin.coroutines.*
-import kotlin.internal.LowPriorityInOverloadResolution
/**
- * Creates cold reactive [Publisher] that runs a given [block] in a coroutine.
+ * Creates a cold reactive [Publisher] that runs a given [block] in a coroutine.
+ *
* Every time the returned flux is subscribed, it starts a new coroutine in the specified [context].
- * Coroutine emits ([Subscriber.onNext]) values with `send`, completes ([Subscriber.onComplete])
- * when the coroutine completes or channel is explicitly closed and emits error ([Subscriber.onError])
- * if coroutine throws an exception or closes channel with a cause.
- * Unsubscribing cancels running coroutine.
+ * The coroutine emits (via [Subscriber.onNext]) values with [send][ProducerScope.send],
+ * completes (via [Subscriber.onComplete]) when the coroutine completes or channel is explicitly closed, and emits
+ * errors (via [Subscriber.onError]) if the coroutine throws an exception or closes channel with a cause.
+ * Unsubscribing cancels the running coroutine.
*
- * Invocations of `send` are suspended appropriately when subscribers apply back-pressure and to ensure that
- * `onNext` is not invoked concurrently.
+ * Invocations of [send][ProducerScope.send] are suspended appropriately when subscribers apply back-pressure and to
+ * ensure that [onNext][Subscriber.onNext] is not invoked concurrently.
*
* Coroutine context can be specified with [context] argument.
- * If the context does not have any dispatcher nor any other [ContinuationInterceptor], then [Dispatchers.Default] is used.
- * Method throws [IllegalArgumentException] if provided [context] contains a [Job] instance.
+ * If the context does not have any dispatcher nor any other [ContinuationInterceptor], then [Dispatchers.Default] is
+ * used.
*
* **Note: This is an experimental api.** Behaviour of publishers that work as children in a parent scope with respect
* to cancellation and error handling may change in the future.
+ *
+ * @throws IllegalArgumentException if the provided [context] contains a [Job] instance.
*/
-@ExperimentalCoroutinesApi
public fun <T> publish(
context: CoroutineContext = EmptyCoroutineContext,
@BuilderInference block: suspend ProducerScope<T>.() -> Unit
@@ -43,17 +41,6 @@ public fun <T> publish(
return publishInternal(GlobalScope, context, DEFAULT_HANDLER, block)
}
-@Deprecated(
- message = "CoroutineScope.publish is deprecated in favour of top-level publish",
- level = DeprecationLevel.ERROR,
- replaceWith = ReplaceWith("publish(context, block)")
-) // Since 1.3.0, will be error in 1.3.1 and hidden in 1.4.0. Binary compatibility with Spring
-@LowPriorityInOverloadResolution
-public fun <T> CoroutineScope.publish(
- context: CoroutineContext = EmptyCoroutineContext,
- @BuilderInference block: suspend ProducerScope<T>.() -> Unit
-): Publisher<T> = publishInternal(this, context, DEFAULT_HANDLER ,block)
-
/** @suppress For internal use from other reactive integration modules only */
@InternalCoroutinesApi
public fun <T> publishInternal(
@@ -74,13 +61,14 @@ private const val CLOSED = -1L // closed, but have not signalled onCompleted/
private const val SIGNALLED = -2L // already signalled subscriber onCompleted/onError
private val DEFAULT_HANDLER: (Throwable, CoroutineContext) -> Unit = { t, ctx -> if (t !is CancellationException) handleCoroutineException(ctx, t) }
+/** @suppress */
@Suppress("CONFLICTING_JVM_DECLARATIONS", "RETURN_TYPE_MISMATCH_ON_INHERITANCE")
@InternalCoroutinesApi
public class PublisherCoroutine<in T>(
parentContext: CoroutineContext,
private val subscriber: Subscriber<T>,
private val exceptionOnCancelHandler: (Throwable, CoroutineContext) -> Unit
-) : AbstractCoroutine<Unit>(parentContext, true), ProducerScope<T>, Subscription, SelectClause2<T, SendChannel<T>> {
+) : AbstractCoroutine<Unit>(parentContext, false, true), ProducerScope<T>, Subscription, SelectClause2<T, SendChannel<T>> {
override val channel: SendChannel<T> get() = this
// Mutex is locked when either nRequested == 0 or while subscriber.onXXX is being invoked
@@ -88,30 +76,26 @@ public class PublisherCoroutine<in T>(
private val _nRequested = atomic(0L) // < 0 when closed (CLOSED or SIGNALLED)
@Volatile
- private var cancelled = false // true when Subscription.cancel() is invoked
+ private var cancelled = false // true after Subscription.cancel() is invoked
- override val isClosedForSend: Boolean get() = isCompleted
- override val isFull: Boolean = mutex.isLocked
+ override val isClosedForSend: Boolean get() = !isActive
override fun close(cause: Throwable?): Boolean = cancelCoroutine(cause)
override fun invokeOnClose(handler: (Throwable?) -> Unit): Nothing =
throw UnsupportedOperationException("PublisherCoroutine doesn't support invokeOnClose")
- override fun offer(element: T): Boolean {
- if (!mutex.tryLock()) return false
- doLockedNext(element)
- return true
- }
+ override fun trySend(element: T): ChannelResult<Unit> =
+ if (!mutex.tryLock()) {
+ ChannelResult.failure()
+ } else {
+ when (val throwable = doLockedNext(element)) {
+ null -> ChannelResult.success(Unit)
+ else -> ChannelResult.closed(throwable)
+ }
+ }
public override suspend fun send(element: T) {
- // fast-path -- try send without suspension
- if (offer(element)) return
- // slow-path does suspend
- return sendSuspend(element)
- }
-
- private suspend fun sendSuspend(element: T) {
mutex.lock()
- doLockedNext(element)
+ doLockedNext(element)?.let { throw it }
}
override val onSend: SelectClause2<T, SendChannel<T>>
@@ -121,13 +105,13 @@ public class PublisherCoroutine<in T>(
@Suppress("PARAMETER_NAME_CHANGED_ON_OVERRIDE")
override fun <R> registerSelectClause2(select: SelectInstance<R>, element: T, block: suspend (SendChannel<T>) -> R) {
mutex.onLock.registerSelectClause2(select, null) {
- doLockedNext(element)
+ doLockedNext(element)?.let { throw it }
block(this)
}
}
/*
- * This code is not trivial because of the two properties:
+ * This code is not trivial because of the following properties:
* 1. It ensures conformance to the reactive specification that mandates that onXXX invocations should not
* be concurrent. It uses Mutex to protect all onXXX invocation and ensure conformance even when multiple
* coroutines are invoking `send` function.
@@ -136,27 +120,61 @@ public class PublisherCoroutine<in T>(
* globally-scoped coroutine that is invoking `send` outside of this context. Without extra precaution this may
* lead to `onNext` that is concurrent with `onComplete/onError`, so that is why signalling for
* `onComplete/onError` is also done under the same mutex.
+ * 3. The reactive specification forbids emitting more elements than requested, so `onNext` is forbidden until the
+ * subscriber actually requests some elements. This is implemented by the mutex being locked when emitting
+ * elements is not permitted (`_nRequested.value == 0`).
*/
- // assert: mutex.isLocked()
- private fun doLockedNext(elem: T) {
- // check if already closed for send, note that isActive becomes false as soon as cancel() is invoked,
- // because the job is cancelled, so this check also ensure conformance to the reactive specification's
- // requirement that after cancellation requested we don't call onXXX
+ /**
+ * Attempts to emit a value to the subscriber and, if back-pressure permits this, unlock the mutex.
+ *
+ * Requires that the caller has locked the mutex before this invocation.
+ *
+ * If the channel is closed, returns the corresponding [Throwable]; otherwise, returns `null` to denote success.
+ *
+ * @throws NullPointerException if the passed element is `null`
+ */
+ private fun doLockedNext(elem: T): Throwable? {
+ if (elem == null) {
+ unlockAndCheckCompleted()
+ throw NullPointerException("Attempted to emit `null` inside a reactive publisher")
+ }
+ /** This guards against the case when the caller of this function managed to lock the mutex not because some
+ * elements were requested--and thus it is permitted to call `onNext`--but because the channel was closed.
+ *
+ * It may look like there is a race condition here between `isActive` and a concurrent cancellation, but it's
+ * okay for a cancellation to happen during `onNext`, as the reactive spec only requires that we *eventually*
+ * stop signalling the subscriber. */
if (!isActive) {
unlockAndCheckCompleted()
- throw getCancellationException()
+ return getCancellationException()
}
- // notify subscriber
+ // notify the subscriber
try {
subscriber.onNext(elem)
- } catch (e: Throwable) {
- // If onNext fails with exception, then we cancel coroutine (with this exception) and then rethrow it
- // to abort the corresponding send/offer invocation. From the standpoint of coroutines machinery,
- // this failure is essentially equivalent to a failure of a child coroutine.
- cancelCoroutine(e)
+ } catch (cause: Throwable) {
+ /** The reactive streams spec forbids the subscribers from throwing from [Subscriber.onNext] unless the
+ * element is `null`, which we check not to be the case. Therefore, we report this exception to the handler
+ * for uncaught exceptions and consider the subscription cancelled, as mandated by
+ * https://github.com/reactive-streams/reactive-streams-jvm/blob/v1.0.3/README.md#2.13.
+ *
+ * Some reactive implementations, like RxJava or Reactor, are known to throw from [Subscriber.onNext] if the
+ * execution encounters an exception they consider to be "fatal", like [VirtualMachineError] or
+ * [ThreadDeath]. Us using the handler for the undeliverable exceptions to signal "fatal" exceptions is
+ * inconsistent with RxJava and Reactor, which attempt to bubble the exception up the call chain as soon as
+ * possible. However, we can't do much better here, as simply throwing from all methods indiscriminately
+ * would violate the contracts we place on them. */
+ cancelled = true
+ val causeDelivered = close(cause)
unlockAndCheckCompleted()
- throw e
+ return if (causeDelivered) {
+ // `cause` is the reason this channel is closed
+ cause
+ } else {
+ // Someone else closed the channel during `onNext`. We report `cause` as an undeliverable exception.
+ exceptionOnCancelHandler(cause, context)
+ getCancellationException()
+ }
}
// now update nRequested
while (true) { // lock-free loop on nRequested
@@ -167,12 +185,13 @@ public class PublisherCoroutine<in T>(
if (_nRequested.compareAndSet(current, updated)) {
if (updated == 0L) {
// return to keep locked due to back-pressure
- return
+ return null
}
break // unlock if updated > 0
}
}
unlockAndCheckCompleted()
+ return null
}
private fun unlockAndCheckCompleted() {
@@ -192,38 +211,31 @@ public class PublisherCoroutine<in T>(
// assert: mutex.isLocked() & isCompleted
private fun doLockedSignalCompleted(cause: Throwable?, handled: Boolean) {
try {
- if (_nRequested.value >= CLOSED) {
- _nRequested.value = SIGNALLED // we'll signal onError/onCompleted (that the final state -- no CAS needed)
- // Specification requires that after cancellation requested we don't call onXXX
- if (cancelled) {
- // If the parent had failed to handle our exception, then we must not lose this exception
- if (cause != null && !handled) exceptionOnCancelHandler(cause, context)
- return
- }
-
+ if (_nRequested.value == SIGNALLED)
+ return
+ _nRequested.value = SIGNALLED // we'll signal onError/onCompleted (the final state, so no CAS needed)
+ // Specification requires that after the cancellation is requested we eventually stop calling onXXX
+ if (cancelled) {
+ // If the parent failed to handle this exception, then we must not lose the exception
+ if (cause != null && !handled) exceptionOnCancelHandler(cause, context)
+ return
+ }
+ if (cause == null) {
try {
- if (cause != null && cause !is CancellationException) {
- /*
- * Reactive frameworks have two types of exceptions: regular and fatal.
- * Regular are passed to onError.
- * Fatal can be passed to onError, but even the standard implementations **can just swallow it** (e.g. see #1297).
- * Such behaviour is inconsistent, leads to silent failures and we can't possibly know whether
- * the cause will be handled by onError (and moreover, it depends on whether a fatal exception was
- * thrown by subscriber or upstream).
- * To make behaviour consistent and least surprising, we always handle fatal exceptions
- * by coroutines machinery, anyway, they should not be present in regular program flow,
- * thus our goal here is just to expose it as soon as possible.
- */
- subscriber.onError(cause)
- if (!handled && cause.isFatal()) {
- exceptionOnCancelHandler(cause, context)
- }
- } else {
- subscriber.onComplete()
- }
+ subscriber.onComplete()
} catch (e: Throwable) {
handleCoroutineException(context, e)
}
+ } else {
+ try {
+ // This can't be the cancellation exception from `cancel`, as then `cancelled` would be `true`.
+ subscriber.onError(cause)
+ } catch (e: Throwable) {
+ if (e !== cause) {
+ cause.addSuppressed(e)
+ }
+ handleCoroutineException(context, cause)
+ }
}
} finally {
mutex.unlock()
@@ -232,13 +244,13 @@ public class PublisherCoroutine<in T>(
override fun request(n: Long) {
if (n <= 0) {
- // Specification requires IAE for n <= 0
+ // Specification requires to call onError with IAE for n <= 0
cancelCoroutine(IllegalArgumentException("non-positive subscription request $n"))
return
}
while (true) { // lock-free loop for nRequested
val cur = _nRequested.value
- if (cur < 0) return // already closed for send, ignore requests
+ if (cur < 0) return // already closed for send, ignore requests, as mandated by the reactive streams spec
var upd = cur + n
if (upd < 0 || n == Long.MAX_VALUE)
upd = Long.MAX_VALUE
@@ -246,6 +258,11 @@ public class PublisherCoroutine<in T>(
if (_nRequested.compareAndSet(cur, upd)) {
// unlock the mutex when we don't have back-pressure anymore
if (cur == 0L) {
+ /** In a sense, after a successful CAS, it is this invocation, not the coroutine itself, that owns
+ * the lock, given that `upd` is necessarily strictly positive. Thus, no other operation has the
+ * right to lower the value on [_nRequested], it can only grow or become [CLOSED]. Therefore, it is
+ * impossible for any other operations to assume that they own the lock without actually acquiring
+ * it. */
unlockAndCheckCompleted()
}
return
@@ -286,6 +303,14 @@ public class PublisherCoroutine<in T>(
cancelled = true
super.cancel(null)
}
-
- private fun Throwable.isFatal() = this is VirtualMachineError || this is ThreadDeath || this is LinkageError
}
+
+@Deprecated(
+ message = "CoroutineScope.publish is deprecated in favour of top-level publish",
+ level = DeprecationLevel.HIDDEN,
+ replaceWith = ReplaceWith("publish(context, block)")
+) // Since 1.3.0, will be error in 1.3.1 and hidden in 1.4.0. Binary compatibility with Spring
+public fun <T> CoroutineScope.publish(
+ context: CoroutineContext = EmptyCoroutineContext,
+ @BuilderInference block: suspend ProducerScope<T>.() -> Unit
+): Publisher<T> = publishInternal(this, context, DEFAULT_HANDLER, block)
diff --git a/reactive/kotlinx-coroutines-reactive/src/ReactiveFlow.kt b/reactive/kotlinx-coroutines-reactive/src/ReactiveFlow.kt
index 5834220c..1a527a3c 100644
--- a/reactive/kotlinx-coroutines-reactive/src/ReactiveFlow.kt
+++ b/reactive/kotlinx-coroutines-reactive/src/ReactiveFlow.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.reactive
@@ -13,15 +13,16 @@ import kotlinx.coroutines.intrinsics.*
import org.reactivestreams.*
import java.util.*
import kotlin.coroutines.*
+import kotlinx.coroutines.internal.*
/**
* Transforms the given reactive [Publisher] into [Flow].
- * Use [buffer] operator on the resulting flow to specify the size of the backpressure.
- * More precisely, it specifies the value of the subscription's [request][Subscription.request].
- * [buffer] default capacity is used by default.
+ * Use the [buffer] operator on the resulting flow to specify the size of the back-pressure.
+ * In effect, it specifies the value of the subscription's [request][Subscription.request].
+ * The [default buffer capacity][Channel.BUFFERED] for a suspending channel is used by default.
*
- * If any of the resulting flow transformations fails, subscription is immediately cancelled and all in-flight elements
- * are discarded.
+ * If any of the resulting flow transformations fails, the subscription is immediately cancelled and all the in-flight
+ * elements are discarded.
*
* This function is integrated with `ReactorContext` from `kotlinx-coroutines-reactor` module,
* see its documentation for additional details.
@@ -30,13 +31,13 @@ public fun <T : Any> Publisher<T>.asFlow(): Flow<T> =
PublisherAsFlow(this)
/**
- * Transforms the given flow to a reactive specification compliant [Publisher].
+ * Transforms the given flow into a reactive specification compliant [Publisher].
*
* This function is integrated with `ReactorContext` from `kotlinx-coroutines-reactor` module,
* see its documentation for additional details.
*
- * An optional [context] can be specified to control the execution context of calls to [Subscriber] methods.
- * You can set a [CoroutineDispatcher] to confine them to a specific thread and/or various [ThreadContextElement] to
+ * An optional [context] can be specified to control the execution context of calls to the [Subscriber] methods.
+ * A [CoroutineDispatcher] can be set to confine them to a specific thread; various [ThreadContextElement] can be set to
* inject additional context into the caller thread. By default, the [Unconfined][Dispatchers.Unconfined] dispatcher
* is used, so calls are performed from an arbitrary thread.
*/
@@ -54,8 +55,8 @@ private class PublisherAsFlow<T : Any>(
PublisherAsFlow(publisher, context, capacity, onBufferOverflow)
/*
- * Suppress for Channel.CHANNEL_DEFAULT_CAPACITY.
- * It's too counter-intuitive to be public and moving it to Flow companion
+ * The @Suppress is for Channel.CHANNEL_DEFAULT_CAPACITY.
+ * It's too counter-intuitive to be public, and moving it to Flow companion
* will also create undesired effect.
*/
@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
@@ -112,7 +113,7 @@ private class PublisherAsFlow<T : Any>(
collectImpl(scope.coroutineContext, SendingCollector(scope.channel))
}
-@Suppress("SubscriberImplementation")
+@Suppress("ReactiveStreamsSubscriberImplementation")
private class ReactiveSubscriber<T : Any>(
capacity: Int,
onBufferOverflow: BufferOverflow,
@@ -124,11 +125,15 @@ private class ReactiveSubscriber<T : Any>(
// be reliable with rendezvous channel, so a rendezvous channel is replaced with buffer=1 channel
private val channel = Channel<T>(if (capacity == Channel.RENDEZVOUS) 1 else capacity, onBufferOverflow)
- suspend fun takeNextOrNull(): T? = channel.receiveOrNull()
+ suspend fun takeNextOrNull(): T? {
+ val result = channel.receiveCatching()
+ result.exceptionOrNull()?.let { throw it }
+ return result.getOrElse { null } // Closed channel
+ }
override fun onNext(value: T) {
// Controlled by requestSize
- require(channel.offer(value)) { "Element $value was not added to channel because it was full, $channel" }
+ require(channel.trySend(value).isSuccess) { "Element $value was not added to channel because it was full, $channel" }
}
override fun onComplete() {
@@ -184,9 +189,15 @@ public class FlowSubscription<T>(
@JvmField public val flow: Flow<T>,
@JvmField public val subscriber: Subscriber<in T>,
context: CoroutineContext
-) : Subscription, AbstractCoroutine<Unit>(context, true) {
+) : Subscription, AbstractCoroutine<Unit>(context, initParentJob = false, true) {
+ /*
+ * We deliberately set initParentJob to false and do not establish parent-child
+ * relationship because FlowSubscription doesn't support it
+ */
private val requested = atomic(0L)
private val producer = atomic<Continuation<Unit>?>(createInitialContinuation())
+ @Volatile
+ private var cancellationRequested = false
// This code wraps startCoroutineCancellable into continuation
private fun createInitialContinuation(): Continuation<Unit> = Continuation(coroutineContext) {
@@ -196,18 +207,25 @@ public class FlowSubscription<T>(
private suspend fun flowProcessing() {
try {
consumeFlow()
- subscriber.onComplete()
- } catch (e: Throwable) {
- try {
- if (e is CancellationException) {
- subscriber.onComplete()
- } else {
- subscriber.onError(e)
+ } catch (cause: Throwable) {
+ @Suppress("INVISIBLE_MEMBER")
+ val unwrappedCause = unwrap(cause)
+ if (!cancellationRequested || isActive || unwrappedCause !== getCancellationException()) {
+ try {
+ subscriber.onError(cause)
+ } catch (e: Throwable) {
+ // Last ditch report
+ cause.addSuppressed(e)
+ handleCoroutineException(coroutineContext, cause)
}
- } catch (e: Throwable) {
- // Last ditch report
- handleCoroutineException(coroutineContext, e)
}
+ return
+ }
+ // We only call this if `consumeFlow()` finished successfully
+ try {
+ subscriber.onComplete()
+ } catch (e: Throwable) {
+ handleCoroutineException(coroutineContext, e)
}
}
@@ -231,6 +249,7 @@ public class FlowSubscription<T>(
}
override fun cancel() {
+ cancellationRequested = true
cancel(null)
}
@@ -243,7 +262,7 @@ public class FlowSubscription<T>(
if (old <= 0L) {
assert(old == 0L)
// Emitter is not started yet or has suspended -- spin on race with suspendCancellableCoroutine
- while(true) {
+ while (true) {
val producer = producer.getAndSet(null) ?: continue // spin if not set yet
producer.resume(Unit)
break
diff --git a/reactive/kotlinx-coroutines-reactive/test/AwaitTest.kt b/reactive/kotlinx-coroutines-reactive/test/AwaitTest.kt
new file mode 100644
index 00000000..6749423f
--- /dev/null
+++ b/reactive/kotlinx-coroutines-reactive/test/AwaitTest.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.reactive
+
+import kotlinx.coroutines.*
+import org.junit.*
+import org.reactivestreams.*
+
+class AwaitTest: TestBase() {
+
+ /** Tests that calls to [awaitFirst] (and, thus, to the rest of these functions) throw [CancellationException] and
+ * unsubscribe from the publisher when their [Job] is cancelled. */
+ @Test
+ fun testAwaitCancellation() = runTest {
+ expect(1)
+ val publisher = Publisher<Int> { s ->
+ s.onSubscribe(object: Subscription {
+ override fun request(n: Long) {
+ expect(3)
+ }
+
+ override fun cancel() {
+ expect(5)
+ }
+ })
+ }
+ val job = launch(start = CoroutineStart.UNDISPATCHED) {
+ try {
+ expect(2)
+ publisher.awaitFirst()
+ } catch (e: CancellationException) {
+ expect(6)
+ throw e
+ }
+ }
+ expect(4)
+ job.cancelAndJoin()
+ finish(7)
+ }
+
+} \ No newline at end of file
diff --git a/reactive/kotlinx-coroutines-reactive/test/CancelledParentAttachTest.kt b/reactive/kotlinx-coroutines-reactive/test/CancelledParentAttachTest.kt
new file mode 100644
index 00000000..1db10b27
--- /dev/null
+++ b/reactive/kotlinx-coroutines-reactive/test/CancelledParentAttachTest.kt
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.reactive
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.flow.*
+import org.junit.*
+
+
+class CancelledParentAttachTest : TestBase() {;
+
+ @Test
+ fun testFlow() = runTest {
+ val f = flowOf(1, 2, 3).cancellable()
+ val j = Job().also { it.cancel() }
+ f.asPublisher(j).asFlow().collect()
+ }
+
+}
diff --git a/reactive/kotlinx-coroutines-reactive/test/FlowAsPublisherTest.kt b/reactive/kotlinx-coroutines-reactive/test/FlowAsPublisherTest.kt
index e7b8cb17..02c9e242 100644
--- a/reactive/kotlinx-coroutines-reactive/test/FlowAsPublisherTest.kt
+++ b/reactive/kotlinx-coroutines-reactive/test/FlowAsPublisherTest.kt
@@ -5,6 +5,7 @@
package kotlinx.coroutines.reactive
import kotlinx.coroutines.*
+import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.flow.*
import org.junit.Test
import org.reactivestreams.*
@@ -15,7 +16,7 @@ class FlowAsPublisherTest : TestBase() {
@Test
fun testErrorOnCancellationIsReported() {
expect(1)
- flow<Int> {
+ flow {
try {
emit(2)
} finally {
@@ -50,13 +51,13 @@ class FlowAsPublisherTest : TestBase() {
@Test
fun testCancellationIsNotReported() {
expect(1)
- flow<Int> {
+ flow {
emit(2)
}.asPublisher().subscribe(object : Subscriber<Int> {
private lateinit var subscription: Subscription
override fun onComplete() {
- expect(3)
+ expectUnreached()
}
override fun onSubscribe(s: Subscription?) {
@@ -73,7 +74,7 @@ class FlowAsPublisherTest : TestBase() {
expectUnreached()
}
})
- finish(4)
+ finish(3)
}
@Test
@@ -149,4 +150,19 @@ class FlowAsPublisherTest : TestBase() {
}
finish(5)
}
+
+ @Test
+ fun testFlowWithTimeout() = runTest {
+ val publisher = flow<Int> {
+ expect(2)
+ withTimeout(1) { delay(Long.MAX_VALUE) }
+ }.asPublisher()
+ try {
+ expect(1)
+ publisher.awaitFirstOrNull()
+ } catch (e: CancellationException) {
+ expect(3)
+ }
+ finish(4)
+ }
}
diff --git a/reactive/kotlinx-coroutines-reactive/test/IntegrationTest.kt b/reactive/kotlinx-coroutines-reactive/test/IntegrationTest.kt
index 18cd012d..efe7ec7e 100644
--- a/reactive/kotlinx-coroutines-reactive/test/IntegrationTest.kt
+++ b/reactive/kotlinx-coroutines-reactive/test/IntegrationTest.kt
@@ -5,10 +5,14 @@
package kotlinx.coroutines.reactive
import kotlinx.coroutines.*
+import kotlinx.coroutines.exceptions.*
import org.junit.Test
import org.junit.runner.*
import org.junit.runners.*
import org.reactivestreams.*
+import java.lang.IllegalStateException
+import java.lang.RuntimeException
+import kotlin.contracts.*
import kotlin.coroutines.*
import kotlin.test.*
@@ -80,30 +84,6 @@ class IntegrationTest(
}
@Test
- fun testNumbers() = runBlocking<Unit> {
- val n = 100 * stressTestMultiplier
- val pub = publish(ctx(coroutineContext)) {
- for (i in 1..n) {
- send(i)
- if (delay) delay(1)
- }
- }
- assertEquals(1, pub.awaitFirst())
- assertEquals(1, pub.awaitFirstOrDefault(0))
- assertEquals(1, pub.awaitFirstOrNull())
- assertEquals(1, pub.awaitFirstOrElse { 0 })
- assertEquals(n, pub.awaitLast())
- assertFailsWith<IllegalArgumentException> { pub.awaitSingle() }
- assertFailsWith<IllegalArgumentException> { pub.awaitSingleOrDefault(0) }
- assertFailsWith<IllegalArgumentException> { pub.awaitSingleOrNull() }
- assertFailsWith<IllegalArgumentException> { pub.awaitSingleOrElse { 0 } }
- checkNumbers(n, pub)
- val channel = pub.openSubscription()
- checkNumbers(n, channel.asPublisher(ctx(coroutineContext)))
- channel.cancel()
- }
-
- @Test
fun testCancelWithoutValue() = runTest {
val job = launch(Job(), start = CoroutineStart.UNDISPATCHED) {
publish<String> {
@@ -116,7 +96,7 @@ class IntegrationTest(
}
@Test
- fun testEmptySingle() = runTest(unhandled = listOf({e -> e is NoSuchElementException})) {
+ fun testEmptySingle() = runTest(unhandled = listOf { e -> e is NoSuchElementException }) {
expect(1)
val job = launch(Job(), start = CoroutineStart.UNDISPATCHED) {
publish<String> {
@@ -130,12 +110,145 @@ class IntegrationTest(
finish(3)
}
- private suspend fun checkNumbers(n: Int, pub: Publisher<Int>) {
- var last = 0
- pub.collect {
- assertEquals(++last, it)
+ /**
+ * Test that the continuation is not being resumed after it has already failed due to there having been too many
+ * values passed.
+ */
+ @Test
+ fun testNotCompletingFailedAwait() = runTest {
+ try {
+ expect(1)
+ Publisher<Int> { sub ->
+ sub.onSubscribe(object: Subscription {
+ override fun request(n: Long) {
+ expect(2)
+ sub.onNext(1)
+ sub.onNext(2)
+ expect(4)
+ sub.onComplete()
+ }
+
+ override fun cancel() {
+ expect(3)
+ }
+ })
+ }.awaitSingle()
+ } catch (e: java.lang.IllegalArgumentException) {
+ expect(5)
}
- assertEquals(n, last)
+ finish(6)
}
+ /**
+ * Test the behavior of [awaitOne] on unconforming publishers.
+ */
+ @Test
+ fun testAwaitOnNonconformingPublishers() = runTest {
+ fun <T> publisher(block: Subscriber<in T>.(n: Long) -> Unit) =
+ Publisher<T> { subscriber ->
+ subscriber.onSubscribe(object: Subscription {
+ override fun request(n: Long) {
+ subscriber.block(n)
+ }
+
+ override fun cancel() {
+ }
+ })
+ }
+ val dummyMessage = "dummy"
+ val dummyThrowable = RuntimeException(dummyMessage)
+ suspend fun <T> assertDetectsBadPublisher(
+ operation: suspend Publisher<T>.() -> T,
+ message: String,
+ block: Subscriber<in T>.(n: Long) -> Unit,
+ ) {
+ assertCallsExceptionHandlerWith<IllegalStateException> {
+ try {
+ publisher(block).operation()
+ } catch (e: Throwable) {
+ if (e.message != dummyMessage)
+ throw e
+ }
+ }.let {
+ assertTrue("Expected the message to contain '$message', got '${it.message}'") {
+ it.message?.contains(message) ?: false
+ }
+ }
+ }
+
+ // Rule 1.1 broken: the publisher produces more values than requested.
+ assertDetectsBadPublisher<Int>({ awaitFirst() }, "provided more") {
+ onNext(1)
+ onNext(2)
+ onComplete()
+ }
+
+ // Rule 1.7 broken: the publisher calls a method on a subscriber after reaching the terminal state.
+ assertDetectsBadPublisher<Int>({ awaitSingle() }, "terminal state") {
+ onNext(1)
+ onError(dummyThrowable)
+ onComplete()
+ }
+ assertDetectsBadPublisher<Int>({ awaitSingleOrDefault(2) }, "terminal state") {
+ onComplete()
+ onError(dummyThrowable)
+ }
+ assertDetectsBadPublisher<Int>({ awaitFirst() }, "terminal state") {
+ onNext(0)
+ onComplete()
+ onComplete()
+ }
+ assertDetectsBadPublisher<Int>({ awaitFirstOrDefault(1) }, "terminal state") {
+ onComplete()
+ onNext(3)
+ }
+ assertDetectsBadPublisher<Int>({ awaitSingle() }, "terminal state") {
+ onError(dummyThrowable)
+ onNext(3)
+ }
+
+ // Rule 1.9 broken (the first signal to the subscriber was not 'onSubscribe')
+ assertCallsExceptionHandlerWith<IllegalStateException> {
+ try {
+ Publisher<Int> { subscriber ->
+ subscriber.onNext(3)
+ subscriber.onComplete()
+ }.awaitFirst()
+ } catch (e: NoSuchElementException) {
+ // intentionally blank
+ }
+ }.let { assertTrue(it.message?.contains("onSubscribe") ?: false) }
+ }
+
+ @Test
+ fun testPublishWithTimeout() = runTest {
+ val publisher = publish<Int> {
+ expect(2)
+ withTimeout(1) { delay(100) }
+ }
+ try {
+ expect(1)
+ publisher.awaitFirstOrNull()
+ } catch (e: CancellationException) {
+ expect(3)
+ }
+ finish(4)
+ }
+
+}
+
+@OptIn(ExperimentalContracts::class)
+internal suspend inline fun <reified E: Throwable> assertCallsExceptionHandlerWith(
+ crossinline operation: suspend (CoroutineExceptionHandler) -> Unit): E {
+ contract {
+ callsInPlace(operation, InvocationKind.EXACTLY_ONCE)
+ }
+ val handler = CapturingHandler()
+ return withContext(handler) {
+ operation(handler)
+ handler.getException().let {
+ assertTrue(it is E, it.toString())
+ it
+ }
+ }
} \ No newline at end of file
diff --git a/reactive/kotlinx-coroutines-reactive/test/IterableFlowTckTest.kt b/reactive/kotlinx-coroutines-reactive/test/IterableFlowTckTest.kt
index 906b2579..cf935f97 100644
--- a/reactive/kotlinx-coroutines-reactive/test/IterableFlowTckTest.kt
+++ b/reactive/kotlinx-coroutines-reactive/test/IterableFlowTckTest.kt
@@ -7,18 +7,12 @@
package kotlinx.coroutines.reactive
import kotlinx.coroutines.flow.*
-import org.junit.*
import org.junit.Ignore
import org.junit.Test
import org.reactivestreams.*
import org.reactivestreams.tck.*
-
-import org.reactivestreams.Subscription
-import org.reactivestreams.Subscriber
-import java.util.ArrayList
import java.util.concurrent.*
-import java.util.concurrent.CountDownLatch
-import java.util.concurrent.ForkJoinPool.commonPool
+import java.util.concurrent.ForkJoinPool.*
import kotlin.test.*
class IterableFlowTckTest : PublisherVerification<Long>(TestEnvironment()) {
@@ -97,7 +91,7 @@ class IterableFlowTckTest : PublisherVerification<Long>(TestEnvironment()) {
override fun onSubscribe(s: Subscription) {
this.s = s
- for (i in 0 until n) {
+ for (i in 0..n) {
commonPool().execute { s.request(1) }
}
}
@@ -115,7 +109,7 @@ class IterableFlowTckTest : PublisherVerification<Long>(TestEnvironment()) {
}
})
- latch.await(50, TimeUnit.SECONDS)
+ latch.await()
assertEquals(array.toList(), collected)
}
diff --git a/reactive/kotlinx-coroutines-reactive/test/PublishTest.kt b/reactive/kotlinx-coroutines-reactive/test/PublishTest.kt
index 9e3c07b6..095b724d 100644
--- a/reactive/kotlinx-coroutines-reactive/test/PublishTest.kt
+++ b/reactive/kotlinx-coroutines-reactive/test/PublishTest.kt
@@ -5,6 +5,7 @@
package kotlinx.coroutines.reactive
import kotlinx.coroutines.*
+import kotlinx.coroutines.channels.*
import org.junit.Test
import org.reactivestreams.*
import kotlin.test.*
@@ -121,44 +122,110 @@ class PublishTest : TestBase() {
finish(7)
}
+ /** Tests that, as soon as `ProducerScope.close` is called, `isClosedForSend` starts returning `true`. */
@Test
- fun testOnNextError() = runTest {
+ fun testChannelClosing() = runTest {
expect(1)
- val publisher = publish(currentDispatcher()) {
+ val publisher = publish<Int>(Dispatchers.Unconfined) {
+ expect(3)
+ close()
+ assert(isClosedForSend)
expect(4)
- try {
- send("OK")
- } catch(e: Throwable) {
- expect(6)
- assert(e is TestException)
- }
}
- expect(2)
+ try {
+ expect(2)
+ publisher.awaitFirstOrNull()
+ } catch (e: CancellationException) {
+ expect(5)
+ }
+ finish(6)
+ }
+
+ @Test
+ fun testOnNextError() = runTest {
val latch = CompletableDeferred<Unit>()
- publisher.subscribe(object : Subscriber<String> {
- override fun onComplete() {
- expectUnreached()
+ expect(1)
+ assertCallsExceptionHandlerWith<TestException> { exceptionHandler ->
+ val publisher = publish(currentDispatcher() + exceptionHandler) {
+ expect(4)
+ try {
+ send("OK")
+ } catch (e: Throwable) {
+ expect(6)
+ assert(e is TestException)
+ assert(isClosedForSend)
+ latch.complete(Unit)
+ }
}
+ expect(2)
+ publisher.subscribe(object : Subscriber<String> {
+ override fun onComplete() {
+ expectUnreached()
+ }
- override fun onSubscribe(s: Subscription) {
- expect(3)
- s.request(1)
- }
+ override fun onSubscribe(s: Subscription) {
+ expect(3)
+ s.request(1)
+ }
- override fun onNext(t: String) {
- expect(5)
- assertEquals("OK", t)
- throw TestException()
- }
+ override fun onNext(t: String) {
+ expect(5)
+ assertEquals("OK", t)
+ throw TestException()
+ }
- override fun onError(t: Throwable) {
- expect(7)
- assert(t is TestException)
- latch.complete(Unit)
+ override fun onError(t: Throwable) {
+ expectUnreached()
+ }
+ })
+ latch.await()
+ }
+ finish(7)
+ }
+
+ /** Tests the behavior when a call to `onNext` fails after the channel is already closed. */
+ @Test
+ fun testOnNextErrorAfterCancellation() = runTest {
+ assertCallsExceptionHandlerWith<TestException> { handler ->
+ var producerScope: ProducerScope<Int>? = null
+ CompletableDeferred<Unit>()
+ expect(1)
+ var job: Job? = null
+ val publisher = publish<Int>(handler + Dispatchers.Unconfined) {
+ producerScope = this
+ expect(4)
+ job = launch {
+ delay(Long.MAX_VALUE)
+ }
}
- })
- latch.await()
- finish(8)
+ expect(2)
+ publisher.subscribe(object: Subscriber<Int> {
+ override fun onSubscribe(s: Subscription) {
+ expect(3)
+ s.request(Long.MAX_VALUE)
+ }
+ override fun onNext(t: Int) {
+ expect(6)
+ assertEquals(1, t)
+ job!!.cancel()
+ throw TestException()
+ }
+ override fun onError(t: Throwable?) {
+ /* Correct changes to the implementation could lead to us entering or not entering this method, but
+ it only matters that if we do, it is the "correct" exception that was validly used to cancel the
+ coroutine that gets passed here and not `TestException`. */
+ assertTrue(t is CancellationException)
+ }
+ override fun onComplete() { expectUnreached() }
+ })
+ expect(5)
+ val result: ChannelResult<Unit> = producerScope!!.trySend(1)
+ val e = result.exceptionOrNull()!!
+ assertTrue(e is CancellationException, "The actual error: $e")
+ assertTrue(producerScope!!.isClosedForSend)
+ assertTrue(result.isFailure)
+ }
+ finish(7)
}
@Test
@@ -182,4 +249,39 @@ class PublishTest : TestBase() {
fun testIllegalArgumentException() {
assertFailsWith<IllegalArgumentException> { publish<Int>(Job()) { } }
}
+
+ /** Tests that `trySend` doesn't throw in `publish`. */
+ @Test
+ fun testTrySendNotThrowing() = runTest {
+ var producerScope: ProducerScope<Int>? = null
+ expect(1)
+ val publisher = publish<Int>(Dispatchers.Unconfined) {
+ producerScope = this
+ expect(3)
+ delay(Long.MAX_VALUE)
+ }
+ val job = launch(start = CoroutineStart.UNDISPATCHED) {
+ expect(2)
+ publisher.awaitFirstOrNull()
+ expectUnreached()
+ }
+ job.cancel()
+ expect(4)
+ val result = producerScope!!.trySend(1)
+ assertTrue(result.isFailure)
+ finish(5)
+ }
+
+ /** Tests that all methods on `publish` fail without closing the channel when attempting to emit `null`. */
+ @Test
+ fun testEmittingNull() = runTest {
+ val publisher = publish {
+ assertFailsWith<NullPointerException> { send(null) }
+ assertFailsWith<NullPointerException> { trySend(null) }
+ @Suppress("DEPRECATION")
+ assertFailsWith<NullPointerException> { offer(null) }
+ send("OK")
+ }
+ assertEquals("OK", publisher.awaitFirstOrNull())
+ }
} \ No newline at end of file
diff --git a/reactive/kotlinx-coroutines-reactive/test/PublisherAsFlowTest.kt b/reactive/kotlinx-coroutines-reactive/test/PublisherAsFlowTest.kt
index 04833e98..7a0e0fac 100644
--- a/reactive/kotlinx-coroutines-reactive/test/PublisherAsFlowTest.kt
+++ b/reactive/kotlinx-coroutines-reactive/test/PublisherAsFlowTest.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.reactive
@@ -71,7 +71,7 @@ class PublisherAsFlowTest : TestBase() {
send(it + 1)
expect(it + 1)
}
- assertFalse { offer(-1) }
+ assertFalse { trySend(-1).isSuccess }
}
publisher.asFlow().collect {
@@ -263,4 +263,14 @@ class PublisherAsFlowTest : TestBase() {
}
assertEquals(expected, list)
}
+
+ @Test
+ fun testException() = runTest {
+ expect(1)
+ val p = publish<Int> { throw TestException() }.asFlow()
+ p.catch {
+ assertTrue { it is TestException }
+ finish(2)
+ }.collect()
+ }
}
diff --git a/reactive/kotlinx-coroutines-reactive/test/PublisherCollectTest.kt b/reactive/kotlinx-coroutines-reactive/test/PublisherCollectTest.kt
new file mode 100644
index 00000000..e4753f04
--- /dev/null
+++ b/reactive/kotlinx-coroutines-reactive/test/PublisherCollectTest.kt
@@ -0,0 +1,144 @@
+/*
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.reactive
+
+import kotlinx.coroutines.*
+import org.junit.Test
+import org.reactivestreams.*
+import kotlin.test.*
+
+class PublisherCollectTest: TestBase() {
+
+ /** Tests the simple scenario where the publisher outputs a bounded stream of values to collect. */
+ @Test
+ fun testCollect() = runTest {
+ val x = 100
+ val xSum = x * (x + 1) / 2
+ val publisher = Publisher<Int> { subscriber ->
+ var requested = 0L
+ var lastOutput = 0
+ subscriber.onSubscribe(object: Subscription {
+
+ override fun request(n: Long) {
+ requested += n
+ if (n <= 0) {
+ subscriber.onError(IllegalArgumentException())
+ return
+ }
+ while (lastOutput < x && lastOutput < requested) {
+ lastOutput += 1
+ subscriber.onNext(lastOutput)
+ }
+ if (lastOutput == x)
+ subscriber.onComplete()
+ }
+
+ override fun cancel() {
+ /** According to rule 3.5 of the
+ * [reactive spec](https://github.com/reactive-streams/reactive-streams-jvm/blob/v1.0.3/README.md#3.5),
+ * this method can be called by the subscriber at any point, so it's not an error if it's called
+ * in this scenario. */
+ }
+
+ })
+ }
+ var sum = 0
+ publisher.collect {
+ sum += it
+ }
+ assertEquals(xSum, sum)
+ }
+
+ /** Tests the behavior of [collect] when the publisher raises an error. */
+ @Test
+ fun testCollectThrowingPublisher() = runTest {
+ val errorString = "Too many elements requested"
+ val x = 100
+ val xSum = x * (x + 1) / 2
+ val publisher = Publisher<Int> { subscriber ->
+ var requested = 0L
+ var lastOutput = 0
+ subscriber.onSubscribe(object: Subscription {
+
+ override fun request(n: Long) {
+ requested += n
+ if (n <= 0) {
+ subscriber.onError(IllegalArgumentException())
+ return
+ }
+ while (lastOutput < x && lastOutput < requested) {
+ lastOutput += 1
+ subscriber.onNext(lastOutput)
+ }
+ if (lastOutput == x)
+ subscriber.onError(IllegalArgumentException(errorString))
+ }
+
+ override fun cancel() {
+ /** See the comment for the corresponding part of [testCollect]. */
+ }
+
+ })
+ }
+ var sum = 0
+ try {
+ publisher.collect {
+ sum += it
+ }
+ } catch (e: IllegalArgumentException) {
+ assertEquals(errorString, e.message)
+ }
+ assertEquals(xSum, sum)
+ }
+
+ /** Tests the behavior of [collect] when the action throws. */
+ @Test
+ fun testCollectThrowingAction() = runTest {
+ val errorString = "Too many elements produced"
+ val x = 100
+ val xSum = x * (x + 1) / 2
+ val publisher = Publisher<Int> { subscriber ->
+ var requested = 0L
+ var lastOutput = 0
+ subscriber.onSubscribe(object: Subscription {
+
+ override fun request(n: Long) {
+ requested += n
+ if (n <= 0) {
+ subscriber.onError(IllegalArgumentException())
+ return
+ }
+ while (lastOutput < x && lastOutput < requested) {
+ lastOutput += 1
+ subscriber.onNext(lastOutput)
+ }
+ }
+
+ override fun cancel() {
+ assertEquals(x, lastOutput)
+ expect(x + 2)
+ }
+
+ })
+ }
+ var sum = 0
+ try {
+ expect(1)
+ var i = 1
+ publisher.collect {
+ sum += it
+ i += 1
+ expect(i)
+ if (sum >= xSum) {
+ throw IllegalArgumentException(errorString)
+ }
+ }
+ } catch (e: IllegalArgumentException) {
+ expect(x + 3)
+ assertEquals(errorString, e.message)
+ }
+ finish(x + 4)
+ }
+} \ No newline at end of file
diff --git a/reactive/kotlinx-coroutines-reactive/test/PublisherRequestStressTest.kt b/reactive/kotlinx-coroutines-reactive/test/PublisherRequestStressTest.kt
index 736a6640..a19ce2f4 100644
--- a/reactive/kotlinx-coroutines-reactive/test/PublisherRequestStressTest.kt
+++ b/reactive/kotlinx-coroutines-reactive/test/PublisherRequestStressTest.kt
@@ -29,13 +29,14 @@ import kotlin.random.*
*/
@Suppress("ReactiveStreamsSubscriberImplementation")
class PublisherRequestStressTest : TestBase() {
+
private val testDurationSec = 3 * stressTestMultiplier
// Original code in Amazon SDK uses 4 and 16 as low/high watermarks.
- // There constants were chosen so that problem reproduces asap with particular this code.
+ // These constants were chosen so that problem reproduces asap with particular this code.
private val minDemand = 8L
private val maxDemand = 16L
-
+
private val nEmitThreads = 4
private val emitThreadNo = AtomicInteger()
@@ -47,7 +48,7 @@ class PublisherRequestStressTest : TestBase() {
private val reqPool = Executors.newSingleThreadExecutor { r ->
Thread(r, "PublisherRequestStressTest-req")
}
-
+
private val nextValue = AtomicLong(0)
@After
@@ -64,7 +65,6 @@ class PublisherRequestStressTest : TestBase() {
fun testRequestStress() {
val expectedValue = AtomicLong(0)
val requestedTill = AtomicLong(0)
- val completionLatch = CountDownLatch(1)
val callingOnNext = AtomicInteger()
val publisher = mtFlow().asPublisher()
@@ -74,7 +74,7 @@ class PublisherRequestStressTest : TestBase() {
private var demand = 0L // only updated from reqPool
override fun onComplete() {
- completionLatch.countDown()
+ // Typically unreached, but, rarely, `emitPool` may shut down before the cancellation is performed.
}
override fun onSubscribe(sub: Subscription) {
@@ -123,7 +123,9 @@ class PublisherRequestStressTest : TestBase() {
}
if (!error) {
subscription.cancel()
- completionLatch.await()
+ runBlocking {
+ (subscription as AbstractCoroutine<*>).join()
+ }
}
}
diff --git a/reactive/kotlinx-coroutines-reactive/test/PublisherSubscriptionSelectTest.kt b/reactive/kotlinx-coroutines-reactive/test/PublisherSubscriptionSelectTest.kt
index 110718ac..740fd86a 100644
--- a/reactive/kotlinx-coroutines-reactive/test/PublisherSubscriptionSelectTest.kt
+++ b/reactive/kotlinx-coroutines-reactive/test/PublisherSubscriptionSelectTest.kt
@@ -1,10 +1,11 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.reactive
import kotlinx.coroutines.*
+import kotlinx.coroutines.channels.*
import kotlinx.coroutines.selects.*
import org.junit.Test
import org.junit.runner.*
@@ -27,27 +28,27 @@ class PublisherSubscriptionSelectTest(private val request: Int) : TestBase() {
var a = 0
var b = 0
// open two subs
- val channelA = source.openSubscription(request)
- val channelB = source.openSubscription(request)
+ val channelA = source.toChannel(request)
+ val channelB = source.toChannel(request)
loop@ while (true) {
val done: Int = select {
- channelA.onReceiveOrNull {
- if (it != null) assertEquals(a++, it)
- if (it == null) 0 else 1
+ channelA.onReceiveCatching { result ->
+ result.onSuccess { assertEquals(a++, it) }
+ if (result.isSuccess) 1 else 0
}
- channelB.onReceiveOrNull {
- if (it != null) assertEquals(b++, it)
- if (it == null) 0 else 2
+ channelB.onReceiveCatching { result ->
+ result.onSuccess { assertEquals(b++, it) }
+ if (result.isSuccess) 2 else 0
}
}
when (done) {
0 -> break@loop
1 -> {
- val r = channelB.receiveOrNull()
+ val r = channelB.receiveCatching().getOrNull()
if (r != null) assertEquals(b++, r)
}
2 -> {
- val r = channelA.receiveOrNull()
+ val r = channelA.receiveCatching().getOrNull()
if (r != null) assertEquals(a++, r)
}
}
@@ -58,4 +59,4 @@ class PublisherSubscriptionSelectTest(private val request: Int) : TestBase() {
// should receive one of them fully
assertTrue(a == n || b == n)
}
-} \ No newline at end of file
+}
diff --git a/reactive/kotlinx-coroutines-reactor/README.md b/reactive/kotlinx-coroutines-reactor/README.md
index cd4a42a3..69157afc 100644
--- a/reactive/kotlinx-coroutines-reactor/README.md
+++ b/reactive/kotlinx-coroutines-reactor/README.md
@@ -27,26 +27,30 @@ Conversion functions:
| -------- | ---------------
| [Job.asMono][kotlinx.coroutines.Job.asMono] | Converts a job to a hot Mono
| [Deferred.asMono][kotlinx.coroutines.Deferred.asMono] | Converts a deferred value to a hot Mono
-| [ReceiveChannel.asFlux][kotlinx.coroutines.channels.ReceiveChannel.asFlux] | Converts a streaming channel to a hot Flux
| [Scheduler.asCoroutineDispatcher][reactor.core.scheduler.Scheduler.asCoroutineDispatcher] | Converts a scheduler to a [CoroutineDispatcher]
<!--- MODULE kotlinx-coroutines-core -->
<!--- INDEX kotlinx.coroutines -->
+
[CoroutineScope]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-scope/index.html
[CoroutineDispatcher]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-dispatcher/index.html
+
<!--- INDEX kotlinx.coroutines.channels -->
<!--- INDEX kotlinx.coroutines.flow -->
+
[Flow]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/-flow/index.html
+
<!--- MODULE kotlinx-coroutines-reactor -->
<!--- INDEX kotlinx.coroutines.reactor -->
+
[mono]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-reactor/kotlinx.coroutines.reactor/mono.html
[flux]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-reactor/kotlinx.coroutines.reactor/flux.html
-[Flow.asFlux]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-reactor/kotlinx.coroutines.reactor/kotlinx.coroutines.flow.-flow/as-flux.html
+[Flow.asFlux]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-reactor/kotlinx.coroutines.reactor/as-flux.html
[ReactorContext]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-reactor/kotlinx.coroutines.reactor/-reactor-context/index.html
-[kotlinx.coroutines.Job.asMono]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-reactor/kotlinx.coroutines.reactor/kotlinx.coroutines.-job/as-mono.html
-[kotlinx.coroutines.Deferred.asMono]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-reactor/kotlinx.coroutines.reactor/kotlinx.coroutines.-deferred/as-mono.html
-[kotlinx.coroutines.channels.ReceiveChannel.asFlux]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-reactor/kotlinx.coroutines.reactor/kotlinx.coroutines.channels.-receive-channel/as-flux.html
-[reactor.core.scheduler.Scheduler.asCoroutineDispatcher]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-reactor/kotlinx.coroutines.reactor/reactor.core.scheduler.-scheduler/as-coroutine-dispatcher.html
+[kotlinx.coroutines.Job.asMono]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-reactor/kotlinx.coroutines.reactor/as-mono.html
+[kotlinx.coroutines.Deferred.asMono]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-reactor/kotlinx.coroutines.reactor/as-mono.html
+[reactor.core.scheduler.Scheduler.asCoroutineDispatcher]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-reactor/kotlinx.coroutines.reactor/as-coroutine-dispatcher.html
+
<!--- END -->
# Package kotlinx.coroutines.reactor
diff --git a/reactive/kotlinx-coroutines-reactor/api/kotlinx-coroutines-reactor.api b/reactive/kotlinx-coroutines-reactor/api/kotlinx-coroutines-reactor.api
index b46fe338..4589117c 100644
--- a/reactive/kotlinx-coroutines-reactor/api/kotlinx-coroutines-reactor.api
+++ b/reactive/kotlinx-coroutines-reactor/api/kotlinx-coroutines-reactor.api
@@ -6,19 +6,26 @@ public final class kotlinx/coroutines/reactor/ConvertKt {
}
public final class kotlinx/coroutines/reactor/FlowKt {
- public static final fun asFlux (Lkotlinx/coroutines/flow/Flow;)Lreactor/core/publisher/Flux;
+ public static final synthetic fun asFlux (Lkotlinx/coroutines/flow/Flow;)Lreactor/core/publisher/Flux;
}
public final class kotlinx/coroutines/reactor/FluxKt {
public static final fun flux (Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;)Lreactor/core/publisher/Flux;
- public static final fun flux (Lkotlinx/coroutines/CoroutineScope;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;)Lreactor/core/publisher/Flux;
+ public static final synthetic fun flux (Lkotlinx/coroutines/CoroutineScope;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;)Lreactor/core/publisher/Flux;
public static synthetic fun flux$default (Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lreactor/core/publisher/Flux;
public static synthetic fun flux$default (Lkotlinx/coroutines/CoroutineScope;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lreactor/core/publisher/Flux;
}
public final class kotlinx/coroutines/reactor/MonoKt {
+ public static final fun awaitFirst (Lreactor/core/publisher/Mono;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+ public static final fun awaitFirstOrDefault (Lreactor/core/publisher/Mono;Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+ public static final fun awaitFirstOrElse (Lreactor/core/publisher/Mono;Lkotlin/jvm/functions/Function0;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+ public static final fun awaitFirstOrNull (Lreactor/core/publisher/Mono;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+ public static final fun awaitLast (Lreactor/core/publisher/Mono;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+ public static final fun awaitSingle (Lreactor/core/publisher/Mono;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+ public static final fun awaitSingleOrNull (Lreactor/core/publisher/Mono;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static final fun mono (Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;)Lreactor/core/publisher/Mono;
- public static final fun mono (Lkotlinx/coroutines/CoroutineScope;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;)Lreactor/core/publisher/Mono;
+ public static final synthetic fun mono (Lkotlinx/coroutines/CoroutineScope;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;)Lreactor/core/publisher/Mono;
public static synthetic fun mono$default (Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lreactor/core/publisher/Mono;
public static synthetic fun mono$default (Lkotlinx/coroutines/CoroutineScope;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lreactor/core/publisher/Mono;
}
@@ -26,14 +33,17 @@ public final class kotlinx/coroutines/reactor/MonoKt {
public final class kotlinx/coroutines/reactor/ReactorContext : kotlin/coroutines/AbstractCoroutineContextElement {
public static final field Key Lkotlinx/coroutines/reactor/ReactorContext$Key;
public fun <init> (Lreactor/util/context/Context;)V
+ public fun <init> (Lreactor/util/context/ContextView;)V
public final fun getContext ()Lreactor/util/context/Context;
+ public fun toString ()Ljava/lang/String;
}
public final class kotlinx/coroutines/reactor/ReactorContext$Key : kotlin/coroutines/CoroutineContext$Key {
}
public final class kotlinx/coroutines/reactor/ReactorContextKt {
- public static final fun asCoroutineContext (Lreactor/util/context/Context;)Lkotlinx/coroutines/reactor/ReactorContext;
+ public static final synthetic fun asCoroutineContext (Lreactor/util/context/Context;)Lkotlinx/coroutines/reactor/ReactorContext;
+ public static final fun asCoroutineContext (Lreactor/util/context/ContextView;)Lkotlinx/coroutines/reactor/ReactorContext;
}
public final class kotlinx/coroutines/reactor/ReactorFlowKt {
diff --git a/reactive/kotlinx-coroutines-reactor/build.gradle.kts b/reactive/kotlinx-coroutines-reactor/build.gradle.kts
index d5fd208a..03af7f4f 100644
--- a/reactive/kotlinx-coroutines-reactor/build.gradle.kts
+++ b/reactive/kotlinx-coroutines-reactor/build.gradle.kts
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
val reactorVersion = version("reactor")
@@ -9,6 +9,10 @@ dependencies {
compile(project(":kotlinx-coroutines-reactive"))
}
+java {
+ targetCompatibility = JavaVersion.VERSION_1_8
+ sourceCompatibility = JavaVersion.VERSION_1_8
+}
tasks {
compileKotlin {
diff --git a/reactive/kotlinx-coroutines-reactor/src/Convert.kt b/reactive/kotlinx-coroutines-reactor/src/Convert.kt
index dc264f8d..3063d1dd 100644
--- a/reactive/kotlinx-coroutines-reactor/src/Convert.kt
+++ b/reactive/kotlinx-coroutines-reactor/src/Convert.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.reactor
@@ -21,7 +21,6 @@ import kotlin.coroutines.*
*
* @param context -- the coroutine context from which the resulting mono is going to be signalled
*/
-@ExperimentalCoroutinesApi
public fun Job.asMono(context: CoroutineContext): Mono<Unit> = mono(context) { this@asMono.join() }
/**
* Converts this deferred value to the hot reactive mono that signals
@@ -35,19 +34,19 @@ public fun Job.asMono(context: CoroutineContext): Mono<Unit> = mono(context) { t
*
* @param context -- the coroutine context from which the resulting mono is going to be signalled
*/
-@ExperimentalCoroutinesApi
public fun <T> Deferred<T?>.asMono(context: CoroutineContext): Mono<T> = mono(context) { this@asMono.await() }
/**
* Converts a stream of elements received from the channel to the hot reactive flux.
*
- * Every subscriber receives values from this channel in **fan-out** fashion. If the are multiple subscribers,
- * they'll receive values in round-robin way.
+ * Every subscriber receives values from this channel in a **fan-out** fashion. If the are multiple subscribers,
+ * they'll receive values in a round-robin way.
* @param context -- the coroutine context from which the resulting flux is going to be signalled
+ * @suppress
*/
@Deprecated(message = "Deprecated in the favour of consumeAsFlow()",
- level = DeprecationLevel.WARNING,
- replaceWith = ReplaceWith("this.consumeAsFlow().asFlux()"))
+ level = DeprecationLevel.ERROR,
+ replaceWith = ReplaceWith("this.consumeAsFlow().asFlux(context)", imports = ["kotlinx.coroutines.flow.consumeAsFlow"]))
public fun <T> ReceiveChannel<T>.asFlux(context: CoroutineContext = EmptyCoroutineContext): Flux<T> = flux(context) {
for (t in this@asFlux)
send(t)
diff --git a/reactive/kotlinx-coroutines-reactor/src/Flux.kt b/reactive/kotlinx-coroutines-reactor/src/Flux.kt
index addc528c..1e408d83 100644
--- a/reactive/kotlinx-coroutines-reactor/src/Flux.kt
+++ b/reactive/kotlinx-coroutines-reactor/src/Flux.kt
@@ -1,10 +1,7 @@
-
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
-@file:Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER")
-
package kotlinx.coroutines.reactor
import kotlinx.coroutines.*
@@ -15,25 +12,23 @@ import reactor.core.*
import reactor.core.publisher.*
import reactor.util.context.*
import kotlin.coroutines.*
-import kotlin.internal.*
/**
- * Creates cold reactive [Flux] that runs a given [block] in a coroutine.
+ * Creates a cold reactive [Flux] that runs the given [block] in a coroutine.
* Every time the returned flux is subscribed, it starts a new coroutine in the specified [context].
- * Coroutine emits ([Subscriber.onNext]) values with `send`, completes ([Subscriber.onComplete])
- * when the coroutine completes or channel is explicitly closed and emits error ([Subscriber.onError])
- * if coroutine throws an exception or closes channel with a cause.
- * Unsubscribing cancels running coroutine.
+ * The coroutine emits ([Subscriber.onNext]) values with [send][ProducerScope.send], completes ([Subscriber.onComplete])
+ * when the coroutine completes, or, in case the coroutine throws an exception or the channel is closed,
+ * emits the error ([Subscriber.onError]) and closes the channel with the cause.
+ * Unsubscribing cancels the running coroutine.
*
- * Invocations of `send` are suspended appropriately when subscribers apply back-pressure and to ensure that
- * `onNext` is not invoked concurrently.
- *
- * Method throws [IllegalArgumentException] if provided [context] contains a [Job] instance.
+ * Invocations of [send][ProducerScope.send] are suspended appropriately when subscribers apply back-pressure and to
+ * ensure that [onNext][Subscriber.onNext] is not invoked concurrently.
*
* **Note: This is an experimental api.** Behaviour of publishers that work as children in a parent scope with respect
* to cancellation and error handling may change in the future.
+ *
+ * @throws IllegalArgumentException if the provided [context] contains a [Job] instance.
*/
-@ExperimentalCoroutinesApi
public fun <T> flux(
context: CoroutineContext = EmptyCoroutineContext,
@BuilderInference block: suspend ProducerScope<T>.() -> Unit
@@ -43,40 +38,61 @@ public fun <T> flux(
return Flux.from(reactorPublish(GlobalScope, context, block))
}
-@Deprecated(
- message = "CoroutineScope.flux is deprecated in favour of top-level flux",
- level = DeprecationLevel.ERROR,
- replaceWith = ReplaceWith("flux(context, block)")
-) // Since 1.3.0, will be error in 1.3.1 and hidden in 1.4.0. Binary compatibility with Spring
-@LowPriorityInOverloadResolution
-public fun <T> CoroutineScope.flux(
- context: CoroutineContext = EmptyCoroutineContext,
- @BuilderInference block: suspend ProducerScope<T>.() -> Unit
-): Flux<T> =
- Flux.from(reactorPublish(this, context, block))
-
private fun <T> reactorPublish(
scope: CoroutineScope,
context: CoroutineContext = EmptyCoroutineContext,
@BuilderInference block: suspend ProducerScope<T>.() -> Unit
-): Publisher<T> = Publisher { subscriber ->
- // specification requires NPE on null subscriber
- if (subscriber == null) throw NullPointerException("Subscriber cannot be null")
- require(subscriber is CoreSubscriber) { "Subscriber is not an instance of CoreSubscriber, context can not be extracted." }
+): Publisher<T> = Publisher onSubscribe@{ subscriber: Subscriber<in T>? ->
+ if (subscriber !is CoreSubscriber) {
+ subscriber.reject(IllegalArgumentException("Subscriber is not an instance of CoreSubscriber, context can not be extracted."))
+ return@onSubscribe
+ }
val currentContext = subscriber.currentContext()
- val reactorContext = (context[ReactorContext]?.context?.putAll(currentContext) ?: currentContext).asCoroutineContext()
+ val reactorContext = context.extendReactorContext(currentContext)
val newContext = scope.newCoroutineContext(context + reactorContext)
val coroutine = PublisherCoroutine(newContext, subscriber, REACTOR_HANDLER)
subscriber.onSubscribe(coroutine) // do it first (before starting coroutine), to avoid unnecessary suspensions
coroutine.start(CoroutineStart.DEFAULT, coroutine, block)
}
-private val REACTOR_HANDLER: (Throwable, CoroutineContext) -> Unit = { e, ctx ->
- if (e !is CancellationException) {
+private val REACTOR_HANDLER: (Throwable, CoroutineContext) -> Unit = { cause, ctx ->
+ if (cause !is CancellationException) {
try {
- Operators.onOperatorError(e, ctx[ReactorContext]?.context ?: Context.empty())
+ Operators.onOperatorError(cause, ctx[ReactorContext]?.context ?: Context.empty())
} catch (e: Throwable) {
- handleCoroutineException(ctx, e)
+ cause.addSuppressed(e)
+ handleCoroutineException(ctx, cause)
}
}
}
+
+/** The proper way to reject the subscriber, according to
+ * [the reactive spec](https://github.com/reactive-streams/reactive-streams-jvm/blob/v1.0.3/README.md#1.9)
+ */
+private fun <T> Subscriber<T>?.reject(t: Throwable) {
+ if (this == null)
+ throw NullPointerException("The subscriber can not be null")
+ onSubscribe(object: Subscription {
+ override fun request(n: Long) {
+ // intentionally left blank
+ }
+ override fun cancel() {
+ // intentionally left blank
+ }
+ })
+ onError(t)
+}
+
+/**
+ * @suppress
+ */
+@Deprecated(
+ message = "CoroutineScope.flux is deprecated in favour of top-level flux",
+ level = DeprecationLevel.HIDDEN,
+ replaceWith = ReplaceWith("flux(context, block)")
+) // Since 1.3.0, will be error in 1.3.1 and hidden in 1.4.0. Binary compatibility with Spring
+public fun <T> CoroutineScope.flux(
+ context: CoroutineContext = EmptyCoroutineContext,
+ @BuilderInference block: suspend ProducerScope<T>.() -> Unit
+): Flux<T> =
+ Flux.from(reactorPublish(this, context, block))
diff --git a/reactive/kotlinx-coroutines-reactor/src/Migration.kt b/reactive/kotlinx-coroutines-reactor/src/Migration.kt
index f0c24bb0..8da0db2d 100644
--- a/reactive/kotlinx-coroutines-reactor/src/Migration.kt
+++ b/reactive/kotlinx-coroutines-reactor/src/Migration.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
@file:JvmName("FlowKt")
@@ -9,9 +9,10 @@ package kotlinx.coroutines.reactor
import kotlinx.coroutines.flow.*
import reactor.core.publisher.*
+/** @suppress **/
@Deprecated(
message = "Replaced in favor of ReactiveFlow extension, please import kotlinx.coroutines.reactor.* instead of kotlinx.coroutines.reactor.FlowKt",
- level = DeprecationLevel.ERROR
-)
+ level = DeprecationLevel.HIDDEN
+) // Compatibility with Spring 5.2-RC
@JvmName("asFlux")
public fun <T : Any> Flow<T>.asFluxDeprecated(): Flux<T> = asFlux()
diff --git a/reactive/kotlinx-coroutines-reactor/src/Mono.kt b/reactive/kotlinx-coroutines-reactor/src/Mono.kt
index 503c891e..e86d51c6 100644
--- a/reactive/kotlinx-coroutines-reactor/src/Mono.kt
+++ b/reactive/kotlinx-coroutines-reactor/src/Mono.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
@file:Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER")
@@ -7,21 +7,23 @@
package kotlinx.coroutines.reactor
import kotlinx.coroutines.*
+import kotlinx.coroutines.reactive.*
+import org.reactivestreams.*
import reactor.core.*
import reactor.core.publisher.*
import kotlin.coroutines.*
-import kotlin.internal.*
+import kotlinx.coroutines.internal.*
/**
- * Creates cold [mono][Mono] that will run a given [block] in a coroutine and emits its result.
+ * Creates a cold [mono][Mono] that runs a given [block] in a coroutine and emits its result.
* Every time the returned mono is subscribed, it starts a new coroutine.
- * If [block] result is `null`, [MonoSink.success] is invoked without a value.
- * Unsubscribing cancels running coroutine.
+ * If the result of [block] is `null`, [MonoSink.success] is invoked without a value.
+ * Unsubscribing cancels the running coroutine.
*
* Coroutine context can be specified with [context] argument.
* If the context does not have any dispatcher nor any other [ContinuationInterceptor], then [Dispatchers.Default] is used.
*
- * Method throws [IllegalArgumentException] if provided [context] contains a [Job] instance.
+ * @throws IllegalArgumentException if the provided [context] contains a [Job] instance.
*/
public fun <T> mono(
context: CoroutineContext = EmptyCoroutineContext,
@@ -32,23 +34,56 @@ public fun <T> mono(
return monoInternal(GlobalScope, context, block)
}
-@Deprecated(
- message = "CoroutineScope.mono is deprecated in favour of top-level mono",
- level = DeprecationLevel.ERROR,
- replaceWith = ReplaceWith("mono(context, block)")
-) // Since 1.3.0, will be error in 1.3.1 and hidden in 1.4.0
-@LowPriorityInOverloadResolution
-public fun <T> CoroutineScope.mono(
- context: CoroutineContext = EmptyCoroutineContext,
- block: suspend CoroutineScope.() -> T?
-): Mono<T> = monoInternal(this, context, block)
+/**
+ * Awaits the single value from the given [Mono] without blocking the thread and returns the resulting value, or, if
+ * this publisher has produced an error, throws the corresponding exception. If the Mono completed without a value,
+ * `null` is returned.
+ *
+ * This suspending function is cancellable.
+ * If the [Job] of the current coroutine is cancelled or completed while the suspending function is waiting, this
+ * function immediately cancels its [Subscription] and resumes with [CancellationException].
+ */
+public suspend fun <T> Mono<T>.awaitSingleOrNull(): T? = suspendCancellableCoroutine { cont ->
+ injectCoroutineContext(cont.context).subscribe(object : Subscriber<T> {
+ private var seenValue = false
+
+ override fun onSubscribe(s: Subscription) {
+ cont.invokeOnCancellation { s.cancel() }
+ s.request(Long.MAX_VALUE)
+ }
+
+ override fun onComplete() {
+ if (!seenValue) cont.resume(null)
+ }
+
+ override fun onNext(t: T) {
+ seenValue = true
+ cont.resume(t)
+ }
+
+ override fun onError(error: Throwable) { cont.resumeWithException(error) }
+ })
+}
+
+/**
+ * Awaits the single value from the given [Mono] without blocking the thread and returns the resulting value, or,
+ * if this Mono has produced an error, throws the corresponding exception.
+ *
+ * This suspending function is cancellable.
+ * If the [Job] of the current coroutine is cancelled or completed while the suspending function is waiting, this
+ * function immediately cancels its [Subscription] and resumes with [CancellationException].
+ *
+ * @throws NoSuchElementException if the Mono does not emit any value
+ */
+// TODO: consider using https://github.com/Kotlin/kotlinx.coroutines/issues/2607 once that lands
+public suspend fun <T> Mono<T>.awaitSingle(): T = awaitSingleOrNull() ?: throw NoSuchElementException()
private fun <T> monoInternal(
scope: CoroutineScope, // support for legacy mono in scope
context: CoroutineContext,
block: suspend CoroutineScope.() -> T?
): Mono<T> = Mono.create { sink ->
- val reactorContext = (context[ReactorContext]?.context?.putAll(sink.currentContext()) ?: sink.currentContext()).asCoroutineContext()
+ val reactorContext = context.extendReactorContext(sink.currentContext())
val newContext = scope.newCoroutineContext(context + reactorContext)
val coroutine = MonoCoroutine(newContext, sink)
sink.onDispose(coroutine)
@@ -58,7 +93,7 @@ private fun <T> monoInternal(
private class MonoCoroutine<in T>(
parentContext: CoroutineContext,
private val sink: MonoSink<T>
-) : AbstractCoroutine<T>(parentContext, true), Disposable {
+) : AbstractCoroutine<T>(parentContext, false, true), Disposable {
@Volatile
private var disposed = false
@@ -67,17 +102,18 @@ private class MonoCoroutine<in T>(
}
override fun onCancelled(cause: Throwable, handled: Boolean) {
- try {
- /*
- * sink.error handles exceptions on its own and, by default, handling of undeliverable exceptions is a no-op.
- * Guard potentially non-empty handlers against meaningless cancellation exceptions
- */
- if (getCancellationException() !== cause) {
+ /** Cancellation exceptions that were caused by [dispose], that is, came from downstream, are not errors. */
+ val unwrappedCause = unwrap(cause)
+ if (getCancellationException() !== unwrappedCause || !disposed) {
+ try {
+ /** If [sink] turns out to already be in a terminal state, this exception will be passed through the
+ * [Hooks.onOperatorError] hook, which is the way to signal undeliverable exceptions in Reactor. */
sink.error(cause)
+ } catch (e: Throwable) {
+ // In case of improper error implementation or fatal exceptions
+ cause.addSuppressed(e)
+ handleCoroutineException(context, cause)
}
- } catch (e: Throwable) {
- // In case of improper error implementation or fatal exceptions
- handleCoroutineException(context, cause)
}
}
@@ -88,3 +124,136 @@ private class MonoCoroutine<in T>(
override fun isDisposed(): Boolean = disposed
}
+
+/**
+ * @suppress
+ */
+@Deprecated(
+ message = "CoroutineScope.mono is deprecated in favour of top-level mono",
+ level = DeprecationLevel.HIDDEN,
+ replaceWith = ReplaceWith("mono(context, block)")
+) // Since 1.3.0, will be error in 1.3.1 and hidden in 1.4.0
+public fun <T> CoroutineScope.mono(
+ context: CoroutineContext = EmptyCoroutineContext,
+ block: suspend CoroutineScope.() -> T?
+): Mono<T> = monoInternal(this, context, block)
+
+/**
+ * This is a lint function that was added already deprecated in order to guard against confusing usages on [Mono].
+ * On [Publisher] instances other than [Mono], this function is not deprecated.
+ *
+ * Both [awaitFirst] and [awaitSingle] await the first value, or throw [NoSuchElementException] if there is none, but
+ * the name [Mono.awaitSingle] better reflects the semantics of [Mono].
+ *
+ * For example, consider this code:
+ * ```
+ * myDbClient.findById(uniqueId).awaitFirst() // findById returns a `Mono`
+ * ```
+ * It looks like more than one value could be returned from `findById` and [awaitFirst] discards the extra elements,
+ * when in fact, at most a single value can be present.
+ *
+ * @suppress
+ */
+@Deprecated(
+ message = "Mono produces at most one value, so the semantics of dropping the remaining elements are not useful. " +
+ "Please use awaitSingle() instead.",
+ level = DeprecationLevel.WARNING,
+ replaceWith = ReplaceWith("this.awaitSingle()")
+) // Warning since 1.5, error in 1.6
+public suspend fun <T> Mono<T>.awaitFirst(): T = awaitSingle()
+
+/**
+ * This is a lint function that was added already deprecated in order to guard against confusing usages on [Mono].
+ * On [Publisher] instances other than [Mono], this function is not deprecated.
+ *
+ * Both [awaitFirstOrDefault] and [awaitSingleOrNull] await the first value, or return some special value if there
+ * is none, but the name [Mono.awaitSingleOrNull] better reflects the semantics of [Mono].
+ *
+ * For example, consider this code:
+ * ```
+ * myDbClient.findById(uniqueId).awaitFirstOrDefault(default) // findById returns a `Mono`
+ * ```
+ * It looks like more than one value could be returned from `findById` and [awaitFirstOrDefault] discards the extra
+ * elements, when in fact, at most a single value can be present.
+ *
+ * @suppress
+ */
+@Deprecated(
+ message = "Mono produces at most one value, so the semantics of dropping the remaining elements are not useful. " +
+ "Please use awaitSingleOrNull() instead.",
+ level = DeprecationLevel.WARNING,
+ replaceWith = ReplaceWith("this.awaitSingleOrNull() ?: default")
+) // Warning since 1.5, error in 1.6
+public suspend fun <T> Mono<T>.awaitFirstOrDefault(default: T): T = awaitSingleOrNull() ?: default
+
+/**
+ * This is a lint function that was added already deprecated in order to guard against confusing usages on [Mono].
+ * On [Publisher] instances other than [Mono], this function is not deprecated.
+ *
+ * Both [awaitFirstOrNull] and [awaitSingleOrNull] await the first value, or return some special value if there
+ * is none, but the name [Mono.awaitSingleOrNull] better reflects the semantics of [Mono].
+ *
+ * For example, consider this code:
+ * ```
+ * myDbClient.findById(uniqueId).awaitFirstOrNull() // findById returns a `Mono`
+ * ```
+ * It looks like more than one value could be returned from `findById` and [awaitFirstOrNull] discards the extra
+ * elements, when in fact, at most a single value can be present.
+ *
+ * @suppress
+ */
+@Deprecated(
+ message = "Mono produces at most one value, so the semantics of dropping the remaining elements are not useful. " +
+ "Please use awaitSingleOrNull() instead.",
+ level = DeprecationLevel.WARNING,
+ replaceWith = ReplaceWith("this.awaitSingleOrNull()")
+) // Warning since 1.5, error in 1.6
+public suspend fun <T> Mono<T>.awaitFirstOrNull(): T? = awaitSingleOrNull()
+
+/**
+ * This is a lint function that was added already deprecated in order to guard against confusing usages on [Mono].
+ * On [Publisher] instances other than [Mono], this function is not deprecated.
+ *
+ * Both [awaitFirstOrElse] and [awaitSingleOrNull] await the first value, or return some special value if there
+ * is none, but the name [Mono.awaitSingleOrNull] better reflects the semantics of [Mono].
+ *
+ * For example, consider this code:
+ * ```
+ * myDbClient.findById(uniqueId).awaitFirstOrElse(defaultValue) // findById returns a `Mono`
+ * ```
+ * It looks like more than one value could be returned from `findById` and [awaitFirstOrElse] discards the extra
+ * elements, when in fact, at most a single value can be present.
+ *
+ * @suppress
+ */
+@Deprecated(
+ message = "Mono produces at most one value, so the semantics of dropping the remaining elements are not useful. " +
+ "Please use awaitSingleOrNull() instead.",
+ level = DeprecationLevel.WARNING,
+ replaceWith = ReplaceWith("this.awaitSingleOrNull() ?: defaultValue()")
+) // Warning since 1.5, error in 1.6
+public suspend fun <T> Mono<T>.awaitFirstOrElse(defaultValue: () -> T): T = awaitSingleOrNull() ?: defaultValue()
+
+/**
+ * This is a lint function that was added already deprecated in order to guard against confusing usages on [Mono].
+ * On [Publisher] instances other than [Mono], this function is not deprecated.
+ *
+ * Both [awaitLast] and [awaitSingle] await the single value, or throw [NoSuchElementException] if there is none, but
+ * the name [Mono.awaitSingle] better reflects the semantics of [Mono].
+ *
+ * For example, consider this code:
+ * ```
+ * myDbClient.findById(uniqueId).awaitLast() // findById returns a `Mono`
+ * ```
+ * It looks like more than one value could be returned from `findById` and [awaitLast] discards the initial elements,
+ * when in fact, at most a single value can be present.
+ *
+ * @suppress
+ */
+@Deprecated(
+ message = "Mono produces at most one value, so the last element is the same as the first. " +
+ "Please use awaitSingle() instead.",
+ level = DeprecationLevel.WARNING,
+ replaceWith = ReplaceWith("this.awaitSingle()")
+) // Warning since 1.5, error in 1.6
+public suspend fun <T> Mono<T>.awaitLast(): T = awaitSingle()
diff --git a/reactive/kotlinx-coroutines-reactor/src/ReactorContext.kt b/reactive/kotlinx-coroutines-reactor/src/ReactorContext.kt
index 69467ad8..d9228409 100644
--- a/reactive/kotlinx-coroutines-reactor/src/ReactorContext.kt
+++ b/reactive/kotlinx-coroutines-reactor/src/ReactorContext.kt
@@ -1,60 +1,80 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.reactor
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import reactor.util.context.Context
import kotlin.coroutines.*
import kotlinx.coroutines.reactive.*
+import reactor.util.context.*
/**
- * Wraps Reactor's [Context] into [CoroutineContext] element for seamless integration Reactor and kotlinx.coroutines.
- * [Context.asCoroutineContext] is defined to add Reactor's [Context] elements as part of [CoroutineContext].
- * Coroutine context element that propagates information about Reactor's [Context] through coroutines.
+ * Wraps Reactor's [Context] into a [CoroutineContext] element for seamless integration between
+ * Reactor and kotlinx.coroutines.
+ * [Context.asCoroutineContext] puts Reactor's [Context] elements into a [CoroutineContext],
+ * which can be used to propagate the information about Reactor's [Context] through coroutines.
*
- * This context element is implicitly propagated through subscriber's context by all Reactive integrations, such as [mono], [flux],
- * [Publisher.asFlow][asFlow], [Flow.asPublisher][asPublisher] and [Flow.asFlux][asFlux].
- * Functions that subscribe to the reactive stream (e.g. [Publisher.awaitFirst][awaitFirst]) also propagate [ReactorContext] to the
- * subscriber's [Context].
+ * This context element is implicitly propagated through subscribers' context by all Reactive integrations,
+ * such as [mono], [flux], [Publisher.asFlow][asFlow], [Flow.asPublisher][asPublisher] and [Flow.asFlux][asFlux].
+ * Functions that subscribe to a reactive stream
+ * (e.g. [Publisher.awaitFirst][kotlinx.coroutines.reactive.awaitFirst]), too, propagate [ReactorContext]
+ * to the subscriber's [Context].
**
* ### Examples of Reactive context integration.
*
* #### Propagating ReactorContext to Reactor's Context
* ```
* val flux = myDatabaseService.getUsers()
- * .subscriberContext() { ctx -> println(ctx); ctx }
- * flux.await() // Will print "null"
+ * .contextWrite { ctx -> println(ctx); ctx }
+ * flux.awaitFirst() // Will print "null"
*
* // Now add ReactorContext
* withContext(Context.of("answer", "42").asCoroutineContext()) {
- * flux.await() // Will print "Context{'key'='value'}"
+ * flux.awaitFirst() // Will print "Context{'key'='value'}"
* }
* ```
*
* #### Propagating subscriber's Context to ReactorContext:
* ```
* val flow = flow {
- * println("Reactor context in Flow: " + coroutineContext[ReactorContext])
+ * println("Reactor context in Flow: " + currentCoroutineContext()[ReactorContext])
* }
* // No context
* flow.asFlux()
* .subscribe() // Will print 'Reactor context in Flow: null'
* // Add subscriber's context
* flow.asFlux()
- * .subscriberContext { ctx -> ctx.put("answer", 42) }
+ * .contextWrite { ctx -> ctx.put("answer", 42) }
* .subscribe() // Will print "Reactor context in Flow: Context{'answer'=42}"
* ```
*/
-@ExperimentalCoroutinesApi
public class ReactorContext(public val context: Context) : AbstractCoroutineContextElement(ReactorContext) {
+
+ // `Context.of` is zero-cost if the argument is a `Context`
+ public constructor(contextView: ContextView): this(Context.of(contextView))
+
public companion object Key : CoroutineContext.Key<ReactorContext>
+
+ override fun toString(): String = context.toString()
}
/**
- * Wraps the given [Context] into [ReactorContext], so it can be added to coroutine's context
+ * Wraps the given [ContextView] into [ReactorContext], so it can be added to the coroutine's context
+ * and later used via `coroutineContext[ReactorContext]`.
+ */
+public fun ContextView.asCoroutineContext(): ReactorContext = ReactorContext(this)
+
+/**
+ * Wraps the given [Context] into [ReactorContext], so it can be added to the coroutine's context
* and later used via `coroutineContext[ReactorContext]`.
+ * @suppress
+ */
+@Deprecated("The more general version for ContextView should be used instead", level = DeprecationLevel.HIDDEN)
+public fun Context.asCoroutineContext(): ReactorContext = readOnly().asCoroutineContext() // `readOnly()` is zero-cost.
+
+/**
+ * Updates the Reactor context in this [CoroutineContext], adding (or possibly replacing) some values.
*/
-@ExperimentalCoroutinesApi
-public fun Context.asCoroutineContext(): ReactorContext = ReactorContext(this)
+internal fun CoroutineContext.extendReactorContext(extensions: ContextView): CoroutineContext =
+ (this[ReactorContext]?.context?.putAll(extensions) ?: extensions).asCoroutineContext()
diff --git a/reactive/kotlinx-coroutines-reactor/src/ReactorContextInjector.kt b/reactive/kotlinx-coroutines-reactor/src/ReactorContextInjector.kt
index a9d140a9..f65d2ec2 100644
--- a/reactive/kotlinx-coroutines-reactor/src/ReactorContextInjector.kt
+++ b/reactive/kotlinx-coroutines-reactor/src/ReactorContextInjector.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.reactor
@@ -18,8 +18,8 @@ internal class ReactorContextInjector : ContextInjector {
override fun <T> injectCoroutineContext(publisher: Publisher<T>, coroutineContext: CoroutineContext): Publisher<T> {
val reactorContext = coroutineContext[ReactorContext]?.context ?: return publisher
return when(publisher) {
- is Mono -> publisher.subscriberContext(reactorContext)
- is Flux -> publisher.subscriberContext(reactorContext)
+ is Mono -> publisher.contextWrite(reactorContext)
+ is Flux -> publisher.contextWrite(reactorContext)
else -> publisher
}
}
diff --git a/reactive/kotlinx-coroutines-reactor/src/ReactorFlow.kt b/reactive/kotlinx-coroutines-reactor/src/ReactorFlow.kt
index a478ab1e..0fc743f9 100644
--- a/reactive/kotlinx-coroutines-reactor/src/ReactorFlow.kt
+++ b/reactive/kotlinx-coroutines-reactor/src/ReactorFlow.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.reactor
diff --git a/reactive/kotlinx-coroutines-reactor/src/Scheduler.kt b/reactive/kotlinx-coroutines-reactor/src/Scheduler.kt
index 4fb55143..03b052b6 100644
--- a/reactive/kotlinx-coroutines-reactor/src/Scheduler.kt
+++ b/reactive/kotlinx-coroutines-reactor/src/Scheduler.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.reactor
diff --git a/reactive/kotlinx-coroutines-reactor/test/ConvertTest.kt b/reactive/kotlinx-coroutines-reactor/test/ConvertTest.kt
index 82664a2d..8af6d3c4 100644
--- a/reactive/kotlinx-coroutines-reactor/test/ConvertTest.kt
+++ b/reactive/kotlinx-coroutines-reactor/test/ConvertTest.kt
@@ -6,6 +6,7 @@ package kotlinx.coroutines.reactor
import kotlinx.coroutines.*
import kotlinx.coroutines.channels.*
+import kotlinx.coroutines.flow.*
import kotlinx.coroutines.reactive.*
import org.junit.*
import org.junit.Test
@@ -96,7 +97,7 @@ class ConvertTest : TestBase() {
delay(50)
send("K")
}
- val flux = c.asFlux(Dispatchers.Unconfined)
+ val flux = c.consumeAsFlow().asFlux(Dispatchers.Unconfined)
checkMonoValue(flux.reduce { t1, t2 -> t1 + t2 }) {
assertEquals("OK", it)
}
@@ -110,7 +111,7 @@ class ConvertTest : TestBase() {
delay(50)
throw TestException("K")
}
- val flux = c.asFlux(Dispatchers.Unconfined)
+ val flux = c.consumeAsFlow().asFlux(Dispatchers.Unconfined)
val mono = mono(Dispatchers.Unconfined) {
var result = ""
try {
@@ -125,4 +126,4 @@ class ConvertTest : TestBase() {
assertEquals("OK", it)
}
}
-} \ No newline at end of file
+}
diff --git a/reactive/kotlinx-coroutines-reactor/test/FlowAsFluxTest.kt b/reactive/kotlinx-coroutines-reactor/test/FlowAsFluxTest.kt
index cecc8959..d8807385 100644
--- a/reactive/kotlinx-coroutines-reactor/test/FlowAsFluxTest.kt
+++ b/reactive/kotlinx-coroutines-reactor/test/FlowAsFluxTest.kt
@@ -14,12 +14,12 @@ import kotlin.test.*
class FlowAsFluxTest : TestBase() {
@Test
fun testFlowAsFluxContextPropagation() {
- val flux = flow<String> {
- (1..4).forEach { i -> emit(createMono(i).awaitFirst()) }
+ val flux = flow {
+ (1..4).forEach { i -> emit(createMono(i).awaitSingle()) }
}
.asFlux()
- .subscriberContext(Context.of(1, "1"))
- .subscriberContext(Context.of(2, "2", 3, "3", 4, "4"))
+ .contextWrite(Context.of(1, "1"))
+ .contextWrite(Context.of(2, "2", 3, "3", 4, "4"))
val list = flux.collectList().block()!!
assertEquals(listOf("1", "2", "3", "4"), list)
}
@@ -36,7 +36,7 @@ class FlowAsFluxTest : TestBase() {
it.next("OK")
it.complete()
}
- .subscriberContext { ctx ->
+ .contextWrite { ctx ->
expect(2)
assertEquals("CTX", ctx.get(1))
ctx
@@ -58,7 +58,7 @@ class FlowAsFluxTest : TestBase() {
it.next("OK")
it.complete()
}
- .subscriberContext { ctx ->
+ .contextWrite { ctx ->
expect(2)
assertEquals("CTX", ctx.get(1))
ctx
diff --git a/reactive/kotlinx-coroutines-reactor/test/FluxTest.kt b/reactive/kotlinx-coroutines-reactor/test/FluxTest.kt
index 31f5f5d9..d059eb66 100644
--- a/reactive/kotlinx-coroutines-reactor/test/FluxTest.kt
+++ b/reactive/kotlinx-coroutines-reactor/test/FluxTest.kt
@@ -5,9 +5,9 @@
package kotlinx.coroutines.reactor
import kotlinx.coroutines.*
+import kotlinx.coroutines.channels.*
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.reactive.*
-import org.junit.*
import org.junit.Test
import kotlin.test.*
@@ -141,4 +141,39 @@ class FluxTest : TestBase() {
.collect { }
}
}
+
+ /** Tests that `trySend` doesn't throw in `flux`. */
+ @Test
+ fun testTrySendNotThrowing() = runTest {
+ var producerScope: ProducerScope<Int>? = null
+ expect(1)
+ val flux = flux<Int>(Dispatchers.Unconfined) {
+ producerScope = this
+ expect(3)
+ delay(Long.MAX_VALUE)
+ }
+ val job = launch(start = CoroutineStart.UNDISPATCHED) {
+ expect(2)
+ flux.awaitFirstOrNull()
+ expectUnreached()
+ }
+ job.cancel()
+ expect(4)
+ val result = producerScope!!.trySend(1)
+ assertTrue(result.isFailure)
+ finish(5)
+ }
+
+ /** Tests that all methods on `flux` fail without closing the channel when attempting to emit `null`. */
+ @Test
+ fun testEmittingNull() = runTest {
+ val flux = flux {
+ assertFailsWith<NullPointerException> { send(null) }
+ assertFailsWith<NullPointerException> { trySend(null) }
+ @Suppress("DEPRECATION")
+ assertFailsWith<NullPointerException> { offer(null) }
+ send("OK")
+ }
+ assertEquals("OK", flux.awaitFirstOrNull())
+ }
} \ No newline at end of file
diff --git a/reactive/kotlinx-coroutines-reactor/test/MonoTest.kt b/reactive/kotlinx-coroutines-reactor/test/MonoTest.kt
index 551988b8..421295d1 100644
--- a/reactive/kotlinx-coroutines-reactor/test/MonoTest.kt
+++ b/reactive/kotlinx-coroutines-reactor/test/MonoTest.kt
@@ -5,6 +5,7 @@
package kotlinx.coroutines.reactor
import kotlinx.coroutines.*
+import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.reactive.*
import org.junit.*
@@ -12,7 +13,6 @@ import org.junit.Test
import org.reactivestreams.*
import reactor.core.publisher.*
import reactor.util.context.*
-import java.time.*
import java.time.Duration.*
import java.util.function.*
import kotlin.test.*
@@ -21,6 +21,7 @@ class MonoTest : TestBase() {
@Before
fun setup() {
ignoreLostThreads("timer-", "parallel-")
+ Hooks.onErrorDropped { expectUnreached() }
}
@Test
@@ -113,6 +114,52 @@ class MonoTest : TestBase() {
@Test
fun testMonoAwait() = runBlocking {
assertEquals("OK", Mono.just("O").awaitSingle() + "K")
+ assertEquals("OK", Mono.just("O").awaitSingleOrNull() + "K")
+ assertFailsWith<NoSuchElementException>{ Mono.empty<String>().awaitSingle() }
+ assertNull(Mono.empty<Int>().awaitSingleOrNull())
+ }
+
+ /** Tests that the versions of the await methods specialized for Mono for deprecation behave correctly and we don't
+ * break any code by introducing them. */
+ @Test
+ @Suppress("DEPRECATION")
+ fun testDeprecatedAwaitMethods() = runBlocking {
+ val filledMono = mono<String> { "OK" }
+ assertEquals("OK", filledMono.awaitFirst())
+ assertEquals("OK", filledMono.awaitFirstOrDefault("!"))
+ assertEquals("OK", filledMono.awaitFirstOrNull())
+ assertEquals("OK", filledMono.awaitFirstOrElse { "ELSE" })
+ assertEquals("OK", filledMono.awaitLast())
+ assertEquals("OK", filledMono.awaitSingleOrDefault("!"))
+ assertEquals("OK", filledMono.awaitSingleOrElse { "ELSE" })
+ val emptyMono = mono<String> { null }
+ assertFailsWith<NoSuchElementException> { emptyMono.awaitFirst() }
+ assertEquals("OK", emptyMono.awaitFirstOrDefault("OK"))
+ assertNull(emptyMono.awaitFirstOrNull())
+ assertEquals("ELSE", emptyMono.awaitFirstOrElse { "ELSE" })
+ assertFailsWith<NoSuchElementException> { emptyMono.awaitLast() }
+ assertEquals("OK", emptyMono.awaitSingleOrDefault("OK"))
+ assertEquals("ELSE", emptyMono.awaitSingleOrElse { "ELSE" })
+ }
+
+ /** Tests that calls to [awaitSingleOrNull] (and, thus, to the rest of such functions) throw [CancellationException]
+ * and unsubscribe from the publisher when their [Job] is cancelled. */
+ @Test
+ fun testAwaitCancellation() = runTest {
+ expect(1)
+ val mono = mono { delay(Long.MAX_VALUE) }.doOnSubscribe { expect(3) }.doOnCancel { expect(5) }
+ val job = launch(start = CoroutineStart.UNDISPATCHED) {
+ try {
+ expect(2)
+ mono.awaitSingleOrNull()
+ } catch (e: CancellationException) {
+ expect(6)
+ throw e
+ }
+ }
+ expect(4)
+ job.cancelAndJoin()
+ finish(7)
}
@Test
@@ -235,7 +282,7 @@ class MonoTest : TestBase() {
} finally {
throw TestException() // would not be able to handle it since mono is disposed
}
- }.subscriberContext { Context.of("reactor.onOperatorError.local", handler) }
+ }.contextWrite { Context.of("reactor.onOperatorError.local", handler) }
mono.subscribe(object : Subscriber<Unit> {
override fun onSubscribe(s: Subscription) {
expect(2)
@@ -262,7 +309,7 @@ class MonoTest : TestBase() {
.interval(ofMillis(1))
.switchMap {
mono(coroutineContext) {
- timeBomb().awaitFirst()
+ timeBomb().awaitSingle()
}
}
.onErrorReturn({
@@ -273,16 +320,111 @@ class MonoTest : TestBase() {
finish(2)
}
- private fun timeBomb() = Mono.delay(Duration.ofMillis(1)).doOnSuccess { throw Exception("something went wrong") }
+ private fun timeBomb() = Mono.delay(ofMillis(1)).doOnSuccess { throw Exception("something went wrong") }
@Test
fun testLeakedException() = runBlocking {
// Test exception is not reported to global handler
val flow = mono<Unit> { throw TestException() }.toFlux().asFlow()
repeat(10000) {
- combine(flow, flow) { _, _ -> Unit }
+ combine(flow, flow) { _, _ -> }
.catch {}
.collect { }
}
}
+
+ /** Test that cancelling a [mono] due to a timeout does throw an exception. */
+ @Test
+ fun testTimeout() {
+ val mono = mono {
+ withTimeout(1) { delay(100) }
+ }
+ try {
+ mono.doOnSubscribe { expect(1) }
+ .doOnNext { expectUnreached() }
+ .doOnSuccess { expectUnreached() }
+ .doOnError { expect(2) }
+ .doOnCancel { expectUnreached() }
+ .block()
+ } catch (e: CancellationException) {
+ expect(3)
+ }
+ finish(4)
+ }
+
+ /** Test that when the reason for cancellation of a [mono] is that the downstream doesn't want its results anymore,
+ * this is considered normal behavior and exceptions are not propagated. */
+ @Test
+ fun testDownstreamCancellationDoesNotThrow() = runTest {
+ var i = 0
+ /** Attach a hook that handles exceptions from publishers that are known to be disposed of. We don't expect it
+ * to be fired in this case, as the reason for the publisher in this test to accept an exception is simply
+ * cancellation from the downstream. */
+ Hooks.onOperatorError("testDownstreamCancellationDoesNotThrow") { t, a ->
+ expectUnreached()
+ t
+ }
+ /** A Mono that doesn't emit a value and instead waits indefinitely. */
+ val mono = mono(Dispatchers.Unconfined) { expect(5 * i + 3); delay(Long.MAX_VALUE) }
+ .doOnSubscribe { expect(5 * i + 2) }
+ .doOnNext { expectUnreached() }
+ .doOnSuccess { expectUnreached() }
+ .doOnError { expectUnreached() }
+ .doOnCancel { expect(5 * i + 4) }
+ val n = 1000
+ repeat(n) {
+ i = it
+ expect(5 * i + 1)
+ mono.awaitCancelAndJoin()
+ expect(5 * i + 5)
+ }
+ finish(5 * n + 1)
+ Hooks.resetOnOperatorError("testDownstreamCancellationDoesNotThrow")
+ }
+
+ /** Test that, when [Mono] is cancelled by the downstream and throws during handling the cancellation, the resulting
+ * error is propagated to [Hooks.onOperatorError]. */
+ @Test
+ fun testRethrowingDownstreamCancellation() = runTest {
+ var i = 0
+ /** Attach a hook that handles exceptions from publishers that are known to be disposed of. We expect it
+ * to be fired in this case. */
+ Hooks.onOperatorError("testDownstreamCancellationDoesNotThrow") { t, a ->
+ expect(i * 6 + 5)
+ t
+ }
+ /** A Mono that doesn't emit a value and instead waits indefinitely, and, when cancelled, throws. */
+ val mono = mono(Dispatchers.Unconfined) {
+ expect(i * 6 + 3)
+ try {
+ delay(Long.MAX_VALUE)
+ } catch (e: CancellationException) {
+ throw TestException()
+ }
+ }
+ .doOnSubscribe { expect(i * 6 + 2) }
+ .doOnNext { expectUnreached() }
+ .doOnSuccess { expectUnreached() }
+ .doOnError { expectUnreached() }
+ .doOnCancel { expect(i * 6 + 4) }
+ val n = 1000
+ repeat(n) {
+ i = it
+ expect(i * 6 + 1)
+ mono.awaitCancelAndJoin()
+ expect(i * 6 + 6)
+ }
+ finish(n * 6 + 1)
+ Hooks.resetOnOperatorError("testDownstreamCancellationDoesNotThrow")
+ }
+
+ /** Run the given [Mono], cancel it, wait for the cancellation handler to finish, and return only then.
+ *
+ * Will not work in the general case, but here, when the publisher uses [Dispatchers.Unconfined], this seems to
+ * ensure that the cancellation handler will have nowhere to execute but serially with the cancellation. */
+ private suspend fun <T> Mono<T>.awaitCancelAndJoin() = coroutineScope {
+ async(start = CoroutineStart.UNDISPATCHED) {
+ awaitSingleOrNull()
+ }.cancelAndJoin()
+ }
}
diff --git a/reactive/kotlinx-coroutines-reactor/test/ReactorContextTest.kt b/reactive/kotlinx-coroutines-reactor/test/ReactorContextTest.kt
index 3681261b..577238be 100644
--- a/reactive/kotlinx-coroutines-reactor/test/ReactorContextTest.kt
+++ b/reactive/kotlinx-coroutines-reactor/test/ReactorContextTest.kt
@@ -18,9 +18,9 @@ class ReactorContextTest : TestBase() {
buildString {
(1..7).forEach { append(ctx.getOrDefault(it, "noValue")) }
}
- } .subscriberContext(Context.of(2, "2", 3, "3", 4, "4", 5, "5"))
- .subscriberContext { ctx -> ctx.put(6, "6") }
- assertEquals(mono.awaitFirst(), "1234567")
+ } .contextWrite(Context.of(2, "2", 3, "3", 4, "4", 5, "5"))
+ .contextWrite { ctx -> ctx.put(6, "6") }
+ assertEquals(mono.awaitSingle(), "1234567")
}
@Test
@@ -29,8 +29,8 @@ class ReactorContextTest : TestBase() {
val ctx = reactorContext()
(1..7).forEach { send(ctx.getOrDefault(it, "noValue")) }
}
- .subscriberContext(Context.of(2, "2", 3, "3", 4, "4", 5, "5"))
- .subscriberContext { ctx -> ctx.put(6, "6") }
+ .contextWrite(Context.of(2, "2", 3, "3", 4, "4", 5, "5"))
+ .contextWrite { ctx -> ctx.put(6, "6") }
val list = flux.collectList().block()!!
assertEquals((1..7).map { it.toString() }, list)
}
@@ -42,23 +42,19 @@ class ReactorContextTest : TestBase() {
buildString {
(1..3).forEach { append(ctx.getOrDefault(it, "noValue")) }
}
- } .subscriberContext(Context.of(2, "2"))
- .awaitFirst()
+ } .contextWrite(Context.of(2, "2"))
+ .awaitSingle()
assertEquals(result, "123")
}
@Test
fun testMonoAwaitContextPropagation() = runBlocking(Context.of(7, "7").asCoroutineContext()) {
- assertEquals(createMono().awaitFirst(), "7")
- assertEquals(createMono().awaitFirstOrDefault("noValue"), "7")
- assertEquals(createMono().awaitFirstOrNull(), "7")
- assertEquals(createMono().awaitFirstOrElse { "noValue" }, "7")
- assertEquals(createMono().awaitLast(), "7")
assertEquals(createMono().awaitSingle(), "7")
+ assertEquals(createMono().awaitSingleOrNull(), "7")
}
@Test
- fun testFluxAwaitContextPropagation() = runBlocking<Unit>(
+ fun testFluxAwaitContextPropagation() = runBlocking(
Context.of(1, "1", 2, "2", 3, "3").asCoroutineContext()
) {
assertEquals(createFlux().awaitFirst(), "1")
diff --git a/reactive/kotlinx-coroutines-rx2/README.md b/reactive/kotlinx-coroutines-rx2/README.md
index f0fbeb00..d93f569a 100644
--- a/reactive/kotlinx-coroutines-rx2/README.md
+++ b/reactive/kotlinx-coroutines-rx2/README.md
@@ -25,9 +25,8 @@ Suspending extension functions and suspending iteration:
| **Name** | **Description**
| -------- | ---------------
| [CompletableSource.await][io.reactivex.CompletableSource.await] | Awaits for completion of the completable value
-| [MaybeSource.await][io.reactivex.MaybeSource.await] | Awaits for the value of the maybe and returns it or null
-| [MaybeSource.awaitOrDefault][io.reactivex.MaybeSource.awaitOrDefault] | Awaits for the value of the maybe and returns it or default
-| [MaybeSource.openSubscription][io.reactivex.MaybeSource.openSubscription] | Subscribes to maybe and returns [ReceiveChannel]
+| [MaybeSource.awaitSingle][io.reactivex.MaybeSource.awaitSingle] | Awaits for the value of the maybe and returns it or throws an exception
+| [MaybeSource.awaitSingleOrNull][io.reactivex.MaybeSource.awaitSingleOrNull] | Awaits for the value of the maybe and returns it or null
| [SingleSource.await][io.reactivex.SingleSource.await] | Awaits for completion of the single value and returns it
| [ObservableSource.awaitFirst][io.reactivex.ObservableSource.awaitFirst] | Awaits for the first value from the given observable
| [ObservableSource.awaitFirstOrDefault][io.reactivex.ObservableSource.awaitFirstOrDefault] | Awaits for the first value from the given observable or default
@@ -35,7 +34,6 @@ Suspending extension functions and suspending iteration:
| [ObservableSource.awaitFirstOrNull][io.reactivex.ObservableSource.awaitFirstOrNull] | Awaits for the first value from the given observable or null
| [ObservableSource.awaitLast][io.reactivex.ObservableSource.awaitFirst] | Awaits for the last value from the given observable
| [ObservableSource.awaitSingle][io.reactivex.ObservableSource.awaitSingle] | Awaits for the single value from the given observable
-| [ObservableSource.openSubscription][io.reactivex.ObservableSource.openSubscription] | Subscribes to observable and returns [ReceiveChannel]
Note that `Flowable` is a subclass of [Reactive Streams](https://www.reactive-streams.org)
`Publisher` and extensions for it are covered by
@@ -47,43 +45,46 @@ Conversion functions:
| -------- | ---------------
| [Job.asCompletable][kotlinx.coroutines.Job.asCompletable] | Converts job to hot completable
| [Deferred.asSingle][kotlinx.coroutines.Deferred.asSingle] | Converts deferred value to hot single
-| [ReceiveChannel.asObservable][kotlinx.coroutines.channels.ReceiveChannel.asObservable] | Converts streaming channel to hot observable
| [Scheduler.asCoroutineDispatcher][io.reactivex.Scheduler.asCoroutineDispatcher] | Converts scheduler to [CoroutineDispatcher]
<!--- MODULE kotlinx-coroutines-core -->
<!--- INDEX kotlinx.coroutines -->
+
[CoroutineScope]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-scope/index.html
[CoroutineDispatcher]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-dispatcher/index.html
+
<!--- INDEX kotlinx.coroutines.channels -->
+
[ProducerScope]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-producer-scope/index.html
-[ReceiveChannel]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-receive-channel/index.html
+
<!--- INDEX kotlinx.coroutines.flow -->
+
[Flow]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/-flow/index.html
+
<!--- MODULE kotlinx-coroutines-rx2 -->
<!--- INDEX kotlinx.coroutines.rx2 -->
+
[rxCompletable]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-rx2/kotlinx.coroutines.rx2/rx-completable.html
[rxMaybe]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-rx2/kotlinx.coroutines.rx2/rx-maybe.html
[rxSingle]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-rx2/kotlinx.coroutines.rx2/rx-single.html
[rxObservable]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-rx2/kotlinx.coroutines.rx2/rx-observable.html
[rxFlowable]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-rx2/kotlinx.coroutines.rx2/rx-flowable.html
-[Flow.asFlowable]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-rx2/kotlinx.coroutines.rx2/kotlinx.coroutines.flow.-flow/as-flowable.html
-[Flow.asObservable]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-rx2/kotlinx.coroutines.rx2/kotlinx.coroutines.flow.-flow/as-observable.html
-[ObservableSource.asFlow]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-rx2/kotlinx.coroutines.rx2/io.reactivex.-observable-source/as-flow.html
-[io.reactivex.CompletableSource.await]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-rx2/kotlinx.coroutines.rx2/io.reactivex.-completable-source/await.html
-[io.reactivex.MaybeSource.await]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-rx2/kotlinx.coroutines.rx2/io.reactivex.-maybe-source/await.html
-[io.reactivex.MaybeSource.awaitOrDefault]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-rx2/kotlinx.coroutines.rx2/io.reactivex.-maybe-source/await-or-default.html
-[io.reactivex.MaybeSource.openSubscription]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-rx2/kotlinx.coroutines.rx2/io.reactivex.-maybe-source/open-subscription.html
-[io.reactivex.SingleSource.await]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-rx2/kotlinx.coroutines.rx2/io.reactivex.-single-source/await.html
-[io.reactivex.ObservableSource.awaitFirst]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-rx2/kotlinx.coroutines.rx2/io.reactivex.-observable-source/await-first.html
-[io.reactivex.ObservableSource.awaitFirstOrDefault]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-rx2/kotlinx.coroutines.rx2/io.reactivex.-observable-source/await-first-or-default.html
-[io.reactivex.ObservableSource.awaitFirstOrElse]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-rx2/kotlinx.coroutines.rx2/io.reactivex.-observable-source/await-first-or-else.html
-[io.reactivex.ObservableSource.awaitFirstOrNull]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-rx2/kotlinx.coroutines.rx2/io.reactivex.-observable-source/await-first-or-null.html
-[io.reactivex.ObservableSource.awaitSingle]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-rx2/kotlinx.coroutines.rx2/io.reactivex.-observable-source/await-single.html
-[io.reactivex.ObservableSource.openSubscription]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-rx2/kotlinx.coroutines.rx2/io.reactivex.-observable-source/open-subscription.html
-[kotlinx.coroutines.Job.asCompletable]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-rx2/kotlinx.coroutines.rx2/kotlinx.coroutines.-job/as-completable.html
-[kotlinx.coroutines.Deferred.asSingle]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-rx2/kotlinx.coroutines.rx2/kotlinx.coroutines.-deferred/as-single.html
-[kotlinx.coroutines.channels.ReceiveChannel.asObservable]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-rx2/kotlinx.coroutines.rx2/kotlinx.coroutines.channels.-receive-channel/as-observable.html
-[io.reactivex.Scheduler.asCoroutineDispatcher]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-rx2/kotlinx.coroutines.rx2/io.reactivex.-scheduler/as-coroutine-dispatcher.html
+[Flow.asFlowable]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-rx2/kotlinx.coroutines.rx2/as-flowable.html
+[Flow.asObservable]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-rx2/kotlinx.coroutines.rx2/as-observable.html
+[ObservableSource.asFlow]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-rx2/kotlinx.coroutines.rx2/as-flow.html
+[io.reactivex.CompletableSource.await]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-rx2/kotlinx.coroutines.rx2/await.html
+[io.reactivex.MaybeSource.awaitSingle]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-rx2/kotlinx.coroutines.rx2/await-single.html
+[io.reactivex.MaybeSource.awaitSingleOrNull]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-rx2/kotlinx.coroutines.rx2/await-single-or-null.html
+[io.reactivex.SingleSource.await]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-rx2/kotlinx.coroutines.rx2/await.html
+[io.reactivex.ObservableSource.awaitFirst]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-rx2/kotlinx.coroutines.rx2/await-first.html
+[io.reactivex.ObservableSource.awaitFirstOrDefault]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-rx2/kotlinx.coroutines.rx2/await-first-or-default.html
+[io.reactivex.ObservableSource.awaitFirstOrElse]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-rx2/kotlinx.coroutines.rx2/await-first-or-else.html
+[io.reactivex.ObservableSource.awaitFirstOrNull]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-rx2/kotlinx.coroutines.rx2/await-first-or-null.html
+[io.reactivex.ObservableSource.awaitSingle]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-rx2/kotlinx.coroutines.rx2/await-single.html
+[kotlinx.coroutines.Job.asCompletable]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-rx2/kotlinx.coroutines.rx2/as-completable.html
+[kotlinx.coroutines.Deferred.asSingle]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-rx2/kotlinx.coroutines.rx2/as-single.html
+[io.reactivex.Scheduler.asCoroutineDispatcher]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-rx2/kotlinx.coroutines.rx2/as-coroutine-dispatcher.html
+
<!--- END -->
# Package kotlinx.coroutines.rx2
diff --git a/reactive/kotlinx-coroutines-rx2/api/kotlinx-coroutines-rx2.api b/reactive/kotlinx-coroutines-rx2/api/kotlinx-coroutines-rx2.api
index 4370325f..c27ef4d7 100644
--- a/reactive/kotlinx-coroutines-rx2/api/kotlinx-coroutines-rx2.api
+++ b/reactive/kotlinx-coroutines-rx2/api/kotlinx-coroutines-rx2.api
@@ -8,21 +8,23 @@ public final class kotlinx/coroutines/rx2/RxAwaitKt {
public static final fun awaitFirstOrNull (Lio/reactivex/ObservableSource;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static final fun awaitLast (Lio/reactivex/ObservableSource;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static final fun awaitOrDefault (Lio/reactivex/MaybeSource;Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+ public static final fun awaitSingle (Lio/reactivex/MaybeSource;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static final fun awaitSingle (Lio/reactivex/ObservableSource;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+ public static final fun awaitSingleOrNull (Lio/reactivex/MaybeSource;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
}
public final class kotlinx/coroutines/rx2/RxChannelKt {
public static final fun collect (Lio/reactivex/MaybeSource;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static final fun collect (Lio/reactivex/ObservableSource;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
- public static final fun consumeEach (Lio/reactivex/MaybeSource;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
- public static final fun consumeEach (Lio/reactivex/ObservableSource;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static final fun openSubscription (Lio/reactivex/MaybeSource;)Lkotlinx/coroutines/channels/ReceiveChannel;
public static final fun openSubscription (Lio/reactivex/ObservableSource;)Lkotlinx/coroutines/channels/ReceiveChannel;
+ public static final fun toChannel (Lio/reactivex/MaybeSource;)Lkotlinx/coroutines/channels/ReceiveChannel;
+ public static final fun toChannel (Lio/reactivex/ObservableSource;)Lkotlinx/coroutines/channels/ReceiveChannel;
}
public final class kotlinx/coroutines/rx2/RxCompletableKt {
public static final fun rxCompletable (Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;)Lio/reactivex/Completable;
- public static final fun rxCompletable (Lkotlinx/coroutines/CoroutineScope;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;)Lio/reactivex/Completable;
+ public static final synthetic fun rxCompletable (Lkotlinx/coroutines/CoroutineScope;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;)Lio/reactivex/Completable;
public static synthetic fun rxCompletable$default (Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lio/reactivex/Completable;
public static synthetic fun rxCompletable$default (Lkotlinx/coroutines/CoroutineScope;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lio/reactivex/Completable;
}
@@ -47,21 +49,21 @@ public final class kotlinx/coroutines/rx2/RxConvertKt {
public final class kotlinx/coroutines/rx2/RxFlowableKt {
public static final fun rxFlowable (Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;)Lio/reactivex/Flowable;
- public static final fun rxFlowable (Lkotlinx/coroutines/CoroutineScope;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;)Lio/reactivex/Flowable;
+ public static final synthetic fun rxFlowable (Lkotlinx/coroutines/CoroutineScope;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;)Lio/reactivex/Flowable;
public static synthetic fun rxFlowable$default (Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lio/reactivex/Flowable;
public static synthetic fun rxFlowable$default (Lkotlinx/coroutines/CoroutineScope;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lio/reactivex/Flowable;
}
public final class kotlinx/coroutines/rx2/RxMaybeKt {
public static final fun rxMaybe (Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;)Lio/reactivex/Maybe;
- public static final fun rxMaybe (Lkotlinx/coroutines/CoroutineScope;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;)Lio/reactivex/Maybe;
+ public static final synthetic fun rxMaybe (Lkotlinx/coroutines/CoroutineScope;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;)Lio/reactivex/Maybe;
public static synthetic fun rxMaybe$default (Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lio/reactivex/Maybe;
public static synthetic fun rxMaybe$default (Lkotlinx/coroutines/CoroutineScope;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lio/reactivex/Maybe;
}
public final class kotlinx/coroutines/rx2/RxObservableKt {
public static final fun rxObservable (Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;)Lio/reactivex/Observable;
- public static final fun rxObservable (Lkotlinx/coroutines/CoroutineScope;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;)Lio/reactivex/Observable;
+ public static final synthetic fun rxObservable (Lkotlinx/coroutines/CoroutineScope;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;)Lio/reactivex/Observable;
public static synthetic fun rxObservable$default (Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lio/reactivex/Observable;
public static synthetic fun rxObservable$default (Lkotlinx/coroutines/CoroutineScope;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lio/reactivex/Observable;
}
@@ -72,7 +74,7 @@ public final class kotlinx/coroutines/rx2/RxSchedulerKt {
public final class kotlinx/coroutines/rx2/RxSingleKt {
public static final fun rxSingle (Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;)Lio/reactivex/Single;
- public static final fun rxSingle (Lkotlinx/coroutines/CoroutineScope;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;)Lio/reactivex/Single;
+ public static final synthetic fun rxSingle (Lkotlinx/coroutines/CoroutineScope;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;)Lio/reactivex/Single;
public static synthetic fun rxSingle$default (Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lio/reactivex/Single;
public static synthetic fun rxSingle$default (Lkotlinx/coroutines/CoroutineScope;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lio/reactivex/Single;
}
diff --git a/reactive/kotlinx-coroutines-rx2/build.gradle b/reactive/kotlinx-coroutines-rx2/build.gradle
index 6d2c4abc..b6fd9327 100644
--- a/reactive/kotlinx-coroutines-rx2/build.gradle
+++ b/reactive/kotlinx-coroutines-rx2/build.gradle
@@ -1,18 +1,21 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
+import org.jetbrains.dokka.gradle.DokkaTaskPartial
dependencies {
- compile project(':kotlinx-coroutines-reactive')
- testCompile project(':kotlinx-coroutines-reactive').sourceSets.test.output
- testCompile "org.reactivestreams:reactive-streams-tck:$reactive_streams_version"
- compile "io.reactivex.rxjava2:rxjava:$rxjava2_version"
+ api project(':kotlinx-coroutines-reactive')
+ testImplementation project(':kotlinx-coroutines-reactive').sourceSets.test.output
+ testImplementation "org.reactivestreams:reactive-streams-tck:$reactive_streams_version"
+ api "io.reactivex.rxjava2:rxjava:$rxjava2_version"
}
-tasks.withType(dokka.getClass()) {
- externalDocumentationLink {
- url = new URL('http://reactivex.io/RxJava/2.x/javadoc/')
- packageListUrl = projectDir.toPath().resolve("package.list").toUri().toURL()
+tasks.withType(DokkaTaskPartial.class) {
+ dokkaSourceSets.configureEach {
+ externalDocumentationLink {
+ url.set(new URL('http://reactivex.io/RxJava/2.x/javadoc/'))
+ packageListUrl.set(projectDir.toPath().resolve("package.list").toUri().toURL())
+ }
}
}
diff --git a/reactive/kotlinx-coroutines-rx2/src/RxAwait.kt b/reactive/kotlinx-coroutines-rx2/src/RxAwait.kt
index d9435b6b..0e0b47eb 100644
--- a/reactive/kotlinx-coroutines-rx2/src/RxAwait.kt
+++ b/reactive/kotlinx-coroutines-rx2/src/RxAwait.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.rx2
@@ -15,11 +15,12 @@ import kotlin.coroutines.*
// ------------------------ CompletableSource ------------------------
/**
- * Awaits for completion of this completable without blocking a thread.
- * Returns `Unit` or throws the corresponding exception if this completable had produced error.
+ * Awaits for completion of this completable without blocking the thread.
+ * Returns `Unit`, or throws the corresponding exception if this completable produces an error.
*
* This suspending function is cancellable. If the [Job] of the invoking coroutine is cancelled or completed while this
- * suspending function is suspended, this function immediately resumes with [CancellationException].
+ * suspending function is suspended, this function immediately resumes with [CancellationException] and disposes of its
+ * subscription.
*/
public suspend fun CompletableSource.await(): Unit = suspendCancellableCoroutine { cont ->
subscribe(object : CompletableObserver {
@@ -32,6 +33,37 @@ public suspend fun CompletableSource.await(): Unit = suspendCancellableCoroutine
// ------------------------ MaybeSource ------------------------
/**
+ * Awaits for completion of the [MaybeSource] without blocking the thread.
+ * Returns the resulting value, or `null` if no value is produced, or throws the corresponding exception if this
+ * [MaybeSource] produces an error.
+ *
+ * This suspending function is cancellable.
+ * If the [Job] of the current coroutine is cancelled or completed while this suspending function is waiting, this
+ * function immediately resumes with [CancellationException] and disposes of its subscription.
+ */
+@Suppress("UNCHECKED_CAST")
+public suspend fun <T> MaybeSource<T>.awaitSingleOrNull(): T? = suspendCancellableCoroutine { cont ->
+ subscribe(object : MaybeObserver<T> {
+ override fun onSubscribe(d: Disposable) { cont.disposeOnCancellation(d) }
+ override fun onComplete() { cont.resume(null) }
+ override fun onSuccess(t: T) { cont.resume(t) }
+ override fun onError(error: Throwable) { cont.resumeWithException(error) }
+ })
+}
+
+/**
+ * Awaits for completion of the [MaybeSource] without blocking the thread.
+ * Returns the resulting value, or throws if either no value is produced or this [MaybeSource] produces an error.
+ *
+ * This suspending function is cancellable.
+ * If the [Job] of the current coroutine is cancelled or completed while this suspending function is waiting, this
+ * function immediately resumes with [CancellationException] and disposes of its subscription.
+ *
+ * @throws NoSuchElementException if no elements were produced by this [MaybeSource].
+ */
+public suspend fun <T> MaybeSource<T>.awaitSingle(): T = awaitSingleOrNull() ?: throw NoSuchElementException()
+
+/**
* Awaits for completion of the maybe without blocking a thread.
* Returns the resulting value, null if no value was produced or throws the corresponding exception if this
* maybe had produced error.
@@ -39,9 +71,19 @@ public suspend fun CompletableSource.await(): Unit = suspendCancellableCoroutine
* This suspending function is cancellable.
* If the [Job] of the current coroutine is cancelled or completed while this suspending function is waiting, this function
* immediately resumes with [CancellationException].
+ *
+ * ### Deprecation
+ *
+ * Deprecated in favor of [awaitSingleOrNull] in order to reflect that `null` can be returned to denote the absence of
+ * a value, as opposed to throwing in such case.
+ * @suppress
*/
-@Suppress("UNCHECKED_CAST")
-public suspend fun <T> MaybeSource<T>.await(): T? = (this as MaybeSource<T?>).awaitOrDefault(null)
+@Deprecated(
+ message = "Deprecated in favor of awaitSingleOrNull()",
+ level = DeprecationLevel.WARNING,
+ replaceWith = ReplaceWith("this.awaitSingleOrNull()")
+) // Warning since 1.5, error in 1.6, hidden in 1.7
+public suspend fun <T> MaybeSource<T>.await(): T? = awaitSingleOrNull()
/**
* Awaits for completion of the maybe without blocking a thread.
@@ -51,25 +93,29 @@ public suspend fun <T> MaybeSource<T>.await(): T? = (this as MaybeSource<T?>).aw
* This suspending function is cancellable.
* If the [Job] of the current coroutine is cancelled or completed while this suspending function is waiting, this function
* immediately resumes with [CancellationException].
+ *
+ * ### Deprecation
+ *
+ * Deprecated in favor of [awaitSingleOrNull] for naming consistency (see the deprecation of [MaybeSource.await] for
+ * details).
+ * @suppress
*/
-public suspend fun <T> MaybeSource<T>.awaitOrDefault(default: T): T = suspendCancellableCoroutine { cont ->
- subscribe(object : MaybeObserver<T> {
- override fun onSubscribe(d: Disposable) { cont.disposeOnCancellation(d) }
- override fun onComplete() { cont.resume(default) }
- override fun onSuccess(t: T) { cont.resume(t) }
- override fun onError(error: Throwable) { cont.resumeWithException(error) }
- })
-}
+@Deprecated(
+ message = "Deprecated in favor of awaitSingleOrNull()",
+ level = DeprecationLevel.WARNING,
+ replaceWith = ReplaceWith("this.awaitSingleOrNull() ?: default")
+) // Warning since 1.5, error in 1.6, hidden in 1.7
+public suspend fun <T> MaybeSource<T>.awaitOrDefault(default: T): T = awaitSingleOrNull() ?: default
// ------------------------ SingleSource ------------------------
/**
- * Awaits for completion of the single value without blocking a thread.
- * Returns the resulting value or throws the corresponding exception if this single had produced error.
+ * Awaits for completion of the single value response without blocking the thread.
+ * Returns the resulting value, or throws the corresponding exception if this response produces an error.
*
* This suspending function is cancellable.
- * If the [Job] of the current coroutine is cancelled or completed while this suspending function is waiting, this function
- * immediately resumes with [CancellationException].
+ * If the [Job] of the current coroutine is cancelled or completed while the suspending function is waiting, this
+ * function immediately disposes of its subscription and resumes with [CancellationException].
*/
public suspend fun <T> SingleSource<T>.await(): T = suspendCancellableCoroutine { cont ->
subscribe(object : SingleObserver<T> {
@@ -82,69 +128,73 @@ public suspend fun <T> SingleSource<T>.await(): T = suspendCancellableCoroutine
// ------------------------ ObservableSource ------------------------
/**
- * Awaits for the first value from the given observable without blocking a thread.
- * Returns the resulting value or throws the corresponding exception if this observable had produced error.
+ * Awaits the first value from the given [Observable] without blocking the thread and returns the resulting value, or,
+ * if the observable has produced an error, throws the corresponding exception.
*
* This suspending function is cancellable.
- * If the [Job] of the current coroutine is cancelled or completed while this suspending function is waiting, this function
- * immediately resumes with [CancellationException].
+ * If the [Job] of the current coroutine is cancelled or completed while the suspending function is waiting, this
+ * function immediately disposes of its subscription and resumes with [CancellationException].
*
- * @throws NoSuchElementException if observable does not emit any value
+ * @throws NoSuchElementException if the observable does not emit any value
*/
public suspend fun <T> ObservableSource<T>.awaitFirst(): T = awaitOne(Mode.FIRST)
/**
- * Awaits for the first value from the given observable or the [default] value if none is emitted without blocking a
- * thread and returns the resulting value or throws the corresponding exception if this observable had produced error.
+ * Awaits the first value from the given [Observable], or returns the [default] value if none is emitted, without
+ * blocking the thread, and returns the resulting value, or, if this observable has produced an error, throws the
+ * corresponding exception.
*
* This suspending function is cancellable.
- * If the [Job] of the current coroutine is cancelled or completed while this suspending function is waiting, this function
- * immediately resumes with [CancellationException].
+ * If the [Job] of the current coroutine is cancelled or completed while the suspending function is waiting, this
+ * function immediately disposes of its subscription and resumes with [CancellationException].
*/
public suspend fun <T> ObservableSource<T>.awaitFirstOrDefault(default: T): T = awaitOne(Mode.FIRST_OR_DEFAULT, default)
/**
- * Awaits for the first value from the given observable or `null` value if none is emitted without blocking a
- * thread and returns the resulting value or throws the corresponding exception if this observable had produced error.
+ * Awaits the first value from the given [Observable], or returns `null` if none is emitted, without blocking the
+ * thread, and returns the resulting value, or, if this observable has produced an error, throws the corresponding
+ * exception.
*
* This suspending function is cancellable.
- * If the [Job] of the current coroutine is cancelled or completed while this suspending function is waiting, this function
- * immediately resumes with [CancellationException].
+ * If the [Job] of the current coroutine is cancelled or completed while the suspending function is waiting, this
+ * function immediately disposes of its subscription and resumes with [CancellationException].
*/
public suspend fun <T> ObservableSource<T>.awaitFirstOrNull(): T? = awaitOne(Mode.FIRST_OR_DEFAULT)
/**
- * Awaits for the first value from the given observable or call [defaultValue] to get a value if none is emitted without blocking a
- * thread and returns the resulting value or throws the corresponding exception if this observable had produced error.
+ * Awaits the first value from the given [Observable], or calls [defaultValue] to get a value if none is emitted,
+ * without blocking the thread, and returns the resulting value, or, if this observable has produced an error, throws
+ * the corresponding exception.
*
* This suspending function is cancellable.
- * If the [Job] of the current coroutine is cancelled or completed while this suspending function is waiting, this function
- * immediately resumes with [CancellationException].
+ * If the [Job] of the current coroutine is cancelled or completed while the suspending function is waiting, this
+ * function immediately disposes of its subscription and resumes with [CancellationException].
*/
-public suspend fun <T> ObservableSource<T>.awaitFirstOrElse(defaultValue: () -> T): T = awaitOne(Mode.FIRST_OR_DEFAULT) ?: defaultValue()
+public suspend fun <T> ObservableSource<T>.awaitFirstOrElse(defaultValue: () -> T): T =
+ awaitOne(Mode.FIRST_OR_DEFAULT) ?: defaultValue()
/**
- * Awaits for the last value from the given observable without blocking a thread.
- * Returns the resulting value or throws the corresponding exception if this observable had produced error.
+ * Awaits the last value from the given [Observable] without blocking the thread and
+ * returns the resulting value, or, if this observable has produced an error, throws the corresponding exception.
*
* This suspending function is cancellable.
- * If the [Job] of the current coroutine is cancelled or completed while this suspending function is waiting, this function
- * immediately resumes with [CancellationException].
+ * If the [Job] of the current coroutine is cancelled or completed while the suspending function is waiting, this
+ * function immediately disposes of its subscription and resumes with [CancellationException].
*
- * @throws NoSuchElementException if observable does not emit any value
+ * @throws NoSuchElementException if the observable does not emit any value
*/
public suspend fun <T> ObservableSource<T>.awaitLast(): T = awaitOne(Mode.LAST)
/**
- * Awaits for the single value from the given observable without blocking a thread.
- * Returns the resulting value or throws the corresponding exception if this observable had produced error.
+ * Awaits the single value from the given observable without blocking the thread and returns the resulting value, or,
+ * if this observable has produced an error, throws the corresponding exception.
*
* This suspending function is cancellable.
- * If the [Job] of the current coroutine is cancelled or completed while this suspending function is waiting, this function
- * immediately resumes with [CancellationException].
+ * If the [Job] of the current coroutine is cancelled or completed while the suspending function is waiting, this
+ * function immediately disposes of its subscription and resumes with [CancellationException].
*
- * @throws NoSuchElementException if observable does not emit any value
- * @throws IllegalArgumentException if observable emits more than one value
+ * @throws NoSuchElementException if the observable does not emit any value
+ * @throws IllegalArgumentException if the observable emits more than one value
*/
public suspend fun <T> ObservableSource<T>.awaitSingle(): T = awaitOne(Mode.SINGLE)
diff --git a/reactive/kotlinx-coroutines-rx2/src/RxCancellable.kt b/reactive/kotlinx-coroutines-rx2/src/RxCancellable.kt
index f7596f27..3e39033e 100644
--- a/reactive/kotlinx-coroutines-rx2/src/RxCancellable.kt
+++ b/reactive/kotlinx-coroutines-rx2/src/RxCancellable.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.rx2
@@ -20,6 +20,7 @@ internal fun handleUndeliverableException(cause: Throwable, context: CoroutineCo
try {
RxJavaPlugins.onError(cause)
} catch (e: Throwable) {
+ cause.addSuppressed(e)
handleCoroutineException(context, cause)
}
}
diff --git a/reactive/kotlinx-coroutines-rx2/src/RxChannel.kt b/reactive/kotlinx-coroutines-rx2/src/RxChannel.kt
index 633693e7..bb093b07 100644
--- a/reactive/kotlinx-coroutines-rx2/src/RxChannel.kt
+++ b/reactive/kotlinx-coroutines-rx2/src/RxChannel.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.rx2
@@ -9,6 +9,8 @@ import io.reactivex.disposables.*
import kotlinx.atomicfu.*
import kotlinx.coroutines.channels.*
import kotlinx.coroutines.internal.*
+import kotlinx.coroutines.flow.*
+import kotlinx.coroutines.reactive.*
/**
* Subscribes to this [MaybeSource] and returns a channel to receive elements emitted by it.
@@ -16,8 +18,9 @@ import kotlinx.coroutines.internal.*
*
* This API is deprecated in the favour of [Flow].
* [MaybeSource] doesn't have a corresponding [Flow] adapter, so it should be transformed to [Observable] first.
+ * @suppress
*/
-@Deprecated(message = "Deprecated in the favour of Flow", level = DeprecationLevel.WARNING) // Will be hidden in 1.4
+@Deprecated(message = "Deprecated in the favour of Flow", level = DeprecationLevel.ERROR) // Will be hidden in 1.5
public fun <T> MaybeSource<T>.openSubscription(): ReceiveChannel<T> {
val channel = SubscriptionChannel<T>()
subscribe(channel)
@@ -30,37 +33,46 @@ public fun <T> MaybeSource<T>.openSubscription(): ReceiveChannel<T> {
*
* This API is deprecated in the favour of [Flow].
* [ObservableSource] doesn't have a corresponding [Flow] adapter, so it should be transformed to [Observable] first.
+ * @suppress
*/
-@Deprecated(message = "Deprecated in the favour of Flow", level = DeprecationLevel.WARNING) // Will be hidden in 1.4
+@Deprecated(message = "Deprecated in the favour of Flow", level = DeprecationLevel.ERROR) // Will be hidden in 1.5
public fun <T> ObservableSource<T>.openSubscription(): ReceiveChannel<T> {
val channel = SubscriptionChannel<T>()
subscribe(channel)
return channel
}
-// Will be promoted to error in 1.3.0, removed in 1.4.0
-@Deprecated(message = "Use collect instead", level = DeprecationLevel.ERROR, replaceWith = ReplaceWith("this.collect(action)"))
-public suspend inline fun <T> MaybeSource<T>.consumeEach(action: (T) -> Unit): Unit =
- openSubscription().consumeEach(action)
-
-// Will be promoted to error in 1.3.0, removed in 1.4.0
-@Deprecated(message = "Use collect instead", level = DeprecationLevel.ERROR, replaceWith = ReplaceWith("this.collect(action)"))
-public suspend inline fun <T> ObservableSource<T>.consumeEach(action: (T) -> Unit): Unit =
- openSubscription().consumeEach(action)
-
/**
* Subscribes to this [MaybeSource] and performs the specified action for each received element.
- * Cancels subscription if any exception happens during collect.
+ *
+ * If [action] throws an exception at some point or if the [MaybeSource] raises an error, the exception is rethrown from
+ * [collect].
*/
public suspend inline fun <T> MaybeSource<T>.collect(action: (T) -> Unit): Unit =
- openSubscription().consumeEach(action)
+ toChannel().consumeEach(action)
/**
* Subscribes to this [ObservableSource] and performs the specified action for each received element.
- * Cancels subscription if any exception happens during collect.
+ *
+ * If [action] throws an exception at some point, the subscription is cancelled, and the exception is rethrown from
+ * [collect]. Also, if the [ObservableSource] signals an error, that error is rethrown from [collect].
*/
public suspend inline fun <T> ObservableSource<T>.collect(action: (T) -> Unit): Unit =
- openSubscription().consumeEach(action)
+ toChannel().consumeEach(action)
+
+@PublishedApi
+internal fun <T> MaybeSource<T>.toChannel(): ReceiveChannel<T> {
+ val channel = SubscriptionChannel<T>()
+ subscribe(channel)
+ return channel
+}
+
+@PublishedApi
+internal fun <T> ObservableSource<T>.toChannel(): ReceiveChannel<T> {
+ val channel = SubscriptionChannel<T>()
+ subscribe(channel)
+ return channel
+}
@Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER")
private class SubscriptionChannel<T> :
@@ -79,11 +91,12 @@ private class SubscriptionChannel<T> :
}
override fun onSuccess(t: T) {
- offer(t)
+ trySend(t)
+ close(cause = null)
}
override fun onNext(t: T) {
- offer(t)
+ trySend(t) // Safe to ignore return value here, expectedly racing with cancellation
}
override fun onComplete() {
diff --git a/reactive/kotlinx-coroutines-rx2/src/RxCompletable.kt b/reactive/kotlinx-coroutines-rx2/src/RxCompletable.kt
index bc91fa53..3f915382 100644
--- a/reactive/kotlinx-coroutines-rx2/src/RxCompletable.kt
+++ b/reactive/kotlinx-coroutines-rx2/src/RxCompletable.kt
@@ -1,15 +1,12 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
-@file:Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER")
-
package kotlinx.coroutines.rx2
import io.reactivex.*
import kotlinx.coroutines.*
import kotlin.coroutines.*
-import kotlin.internal.*
/**
* Creates cold [Completable] that runs a given [block] in a coroutine and emits its result.
@@ -28,17 +25,6 @@ public fun rxCompletable(
return rxCompletableInternal(GlobalScope, context, block)
}
-@Deprecated(
- message = "CoroutineScope.rxCompletable is deprecated in favour of top-level rxCompletable",
- level = DeprecationLevel.ERROR,
- replaceWith = ReplaceWith("rxCompletable(context, block)")
-) // Since 1.3.0, will be error in 1.3.1 and hidden in 1.4.0
-@LowPriorityInOverloadResolution
-public fun CoroutineScope.rxCompletable(
- context: CoroutineContext = EmptyCoroutineContext,
- block: suspend CoroutineScope.() -> Unit
-): Completable = rxCompletableInternal(this, context, block)
-
private fun rxCompletableInternal(
scope: CoroutineScope, // support for legacy rxCompletable in scope
context: CoroutineContext,
@@ -53,7 +39,7 @@ private fun rxCompletableInternal(
private class RxCompletableCoroutine(
parentContext: CoroutineContext,
private val subscriber: CompletableEmitter
-) : AbstractCoroutine<Unit>(parentContext, true) {
+) : AbstractCoroutine<Unit>(parentContext, false, true) {
override fun onCompleted(value: Unit) {
try {
subscriber.onComplete()
@@ -64,11 +50,25 @@ private class RxCompletableCoroutine(
override fun onCancelled(cause: Throwable, handled: Boolean) {
try {
- if (!subscriber.tryOnError(cause)) {
- handleUndeliverableException(cause, context)
+ if (subscriber.tryOnError(cause)) {
+ return
}
} catch (e: Throwable) {
- handleUndeliverableException(e, context)
+ cause.addSuppressed(e)
}
+ handleUndeliverableException(cause, context)
}
}
+
+/**
+ * @suppress
+ */
+@Deprecated(
+ message = "CoroutineScope.rxCompletable is deprecated in favour of top-level rxCompletable",
+ level = DeprecationLevel.HIDDEN,
+ replaceWith = ReplaceWith("rxCompletable(context, block)")
+) // Since 1.3.0, will be error in 1.3.1 and hidden in 1.4.0
+public fun CoroutineScope.rxCompletable(
+ context: CoroutineContext = EmptyCoroutineContext,
+ block: suspend CoroutineScope.() -> Unit
+): Completable = rxCompletableInternal(this, context, block)
diff --git a/reactive/kotlinx-coroutines-rx2/src/RxConvert.kt b/reactive/kotlinx-coroutines-rx2/src/RxConvert.kt
index 41c82ed0..497c922c 100644
--- a/reactive/kotlinx-coroutines-rx2/src/RxConvert.kt
+++ b/reactive/kotlinx-coroutines-rx2/src/RxConvert.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.rx2
@@ -26,7 +26,6 @@ import kotlin.coroutines.*
*
* @param context -- the coroutine context from which the resulting completable is going to be signalled
*/
-@ExperimentalCoroutinesApi
public fun Job.asCompletable(context: CoroutineContext): Completable = rxCompletable(context) {
this@asCompletable.join()
}
@@ -43,7 +42,6 @@ public fun Job.asCompletable(context: CoroutineContext): Completable = rxComplet
*
* @param context -- the coroutine context from which the resulting maybe is going to be signalled
*/
-@ExperimentalCoroutinesApi
public fun <T> Deferred<T?>.asMaybe(context: CoroutineContext): Maybe<T> = rxMaybe(context) {
this@asMaybe.await()
}
@@ -60,7 +58,6 @@ public fun <T> Deferred<T?>.asMaybe(context: CoroutineContext): Maybe<T> = rxMay
*
* @param context -- the coroutine context from which the resulting single is going to be signalled
*/
-@ExperimentalCoroutinesApi
public fun <T : Any> Deferred<T>.asSingle(context: CoroutineContext): Single<T> = rxSingle(context) {
this@asSingle.await()
}
@@ -75,17 +72,20 @@ public fun <T : Any> Deferred<T>.asSingle(context: CoroutineContext): Single<T>
* resulting flow to specify a user-defined value and to control what happens when data is produced faster
* than consumed, i.e. to control the back-pressure behavior. Check [callbackFlow] for more details.
*/
-@ExperimentalCoroutinesApi
public fun <T: Any> ObservableSource<T>.asFlow(): Flow<T> = callbackFlow {
val disposableRef = AtomicReference<Disposable>()
val observer = object : Observer<T> {
override fun onComplete() { close() }
override fun onSubscribe(d: Disposable) { if (!disposableRef.compareAndSet(null, d)) d.dispose() }
override fun onNext(t: T) {
+ /*
+ * Channel was closed by the downstream, so the exception (if any)
+ * also was handled by the same downstream
+ */
try {
- sendBlocking(t)
- } catch (ignored: Throwable) { // TODO: Replace when this issue is fixed: https://github.com/Kotlin/kotlinx.coroutines/issues/974
- // Is handled by the downstream flow
+ trySendBlocking(t)
+ } catch (e: InterruptedException) {
+ // RxJava interrupts the source
}
}
override fun onError(e: Throwable) { close(e) }
@@ -104,7 +104,6 @@ public fun <T: Any> ObservableSource<T>.asFlow(): Flow<T> = callbackFlow {
* inject additional context into the caller thread. By default, the [Unconfined][Dispatchers.Unconfined] dispatcher
* is used, so calls are performed from an arbitrary thread.
*/
-@ExperimentalCoroutinesApi
public fun <T: Any> Flow<T>.asObservable(context: CoroutineContext = EmptyCoroutineContext) : Observable<T> = Observable.create { emitter ->
/*
* ATOMIC is used here to provide stable behaviour of subscribe+dispose pair even if
@@ -137,7 +136,6 @@ public fun <T: Any> Flow<T>.asObservable(context: CoroutineContext = EmptyCorout
* inject additional context into the caller thread. By default, the [Unconfined][Dispatchers.Unconfined] dispatcher
* is used, so calls are performed from an arbitrary thread.
*/
-@ExperimentalCoroutinesApi
public fun <T: Any> Flow<T>.asFlowable(context: CoroutineContext = EmptyCoroutineContext): Flowable<T> =
Flowable.fromPublisher(asPublisher(context))
@@ -151,6 +149,7 @@ public fun <T : Any> ReceiveChannel<T>.asObservable(context: CoroutineContext):
send(t)
}
+/** @suppress **/
@Suppress("UNUSED") // KT-42513
@JvmOverloads // binary compatibility
@JvmName("from")
@@ -158,6 +157,7 @@ public fun <T : Any> ReceiveChannel<T>.asObservable(context: CoroutineContext):
public fun <T: Any> Flow<T>._asFlowable(context: CoroutineContext = EmptyCoroutineContext): Flowable<T> =
asFlowable(context)
+/** @suppress **/
@Suppress("UNUSED") // KT-42513
@JvmOverloads // binary compatibility
@JvmName("from")
diff --git a/reactive/kotlinx-coroutines-rx2/src/RxFlowable.kt b/reactive/kotlinx-coroutines-rx2/src/RxFlowable.kt
index 78d397c3..c856bb4e 100644
--- a/reactive/kotlinx-coroutines-rx2/src/RxFlowable.kt
+++ b/reactive/kotlinx-coroutines-rx2/src/RxFlowable.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
@file:Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER")
@@ -31,7 +31,6 @@ import kotlin.internal.*
*
* **Note: This is an experimental api.** Behaviour of publishers that work as children in a parent scope with respect
*/
-@ExperimentalCoroutinesApi
public fun <T: Any> rxFlowable(
context: CoroutineContext = EmptyCoroutineContext,
@BuilderInference block: suspend ProducerScope<T>.() -> Unit
@@ -41,9 +40,10 @@ public fun <T: Any> rxFlowable(
return Flowable.fromPublisher(publishInternal(GlobalScope, context, RX_HANDLER, block))
}
+/** @suppress */
@Deprecated(
message = "CoroutineScope.rxFlowable is deprecated in favour of top-level rxFlowable",
- level = DeprecationLevel.ERROR,
+ level = DeprecationLevel.HIDDEN,
replaceWith = ReplaceWith("rxFlowable(context, block)")
) // Since 1.3.0, will be error in 1.3.1 and hidden in 1.4.0
@LowPriorityInOverloadResolution
diff --git a/reactive/kotlinx-coroutines-rx2/src/RxMaybe.kt b/reactive/kotlinx-coroutines-rx2/src/RxMaybe.kt
index bfbcb38d..ab713123 100644
--- a/reactive/kotlinx-coroutines-rx2/src/RxMaybe.kt
+++ b/reactive/kotlinx-coroutines-rx2/src/RxMaybe.kt
@@ -1,15 +1,12 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
-@file:Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER")
-
package kotlinx.coroutines.rx2
import io.reactivex.*
import kotlinx.coroutines.*
import kotlin.coroutines.*
-import kotlin.internal.*
/**
* Creates cold [maybe][Maybe] that will run a given [block] in a coroutine and emits its result.
@@ -29,17 +26,6 @@ public fun <T> rxMaybe(
return rxMaybeInternal(GlobalScope, context, block)
}
-@Deprecated(
- message = "CoroutineScope.rxMaybe is deprecated in favour of top-level rxMaybe",
- level = DeprecationLevel.ERROR,
- replaceWith = ReplaceWith("rxMaybe(context, block)")
-) // Since 1.3.0, will be error in 1.3.1 and hidden in 1.4.0
-@LowPriorityInOverloadResolution
-public fun <T> CoroutineScope.rxMaybe(
- context: CoroutineContext = EmptyCoroutineContext,
- block: suspend CoroutineScope.() -> T?
-): Maybe<T> = rxMaybeInternal(this, context, block)
-
private fun <T> rxMaybeInternal(
scope: CoroutineScope, // support for legacy rxMaybe in scope
context: CoroutineContext,
@@ -54,7 +40,7 @@ private fun <T> rxMaybeInternal(
private class RxMaybeCoroutine<T>(
parentContext: CoroutineContext,
private val subscriber: MaybeEmitter<T>
-) : AbstractCoroutine<T>(parentContext, true) {
+) : AbstractCoroutine<T>(parentContext, false, true) {
override fun onCompleted(value: T) {
try {
if (value == null) subscriber.onComplete() else subscriber.onSuccess(value)
@@ -65,11 +51,23 @@ private class RxMaybeCoroutine<T>(
override fun onCancelled(cause: Throwable, handled: Boolean) {
try {
- if (!subscriber.tryOnError(cause)) {
- handleUndeliverableException(cause, context)
+ if (subscriber.tryOnError(cause)) {
+ return
}
} catch (e: Throwable) {
- handleUndeliverableException(e, context)
+ cause.addSuppressed(e)
}
+ handleUndeliverableException(cause, context)
}
}
+
+/** @suppress */
+@Deprecated(
+ message = "CoroutineScope.rxMaybe is deprecated in favour of top-level rxMaybe",
+ level = DeprecationLevel.HIDDEN,
+ replaceWith = ReplaceWith("rxMaybe(context, block)")
+) // Since 1.3.0, will be error in 1.3.1 and hidden in 1.4.0
+public fun <T> CoroutineScope.rxMaybe(
+ context: CoroutineContext = EmptyCoroutineContext,
+ block: suspend CoroutineScope.() -> T?
+): Maybe<T> = rxMaybeInternal(this, context, block)
diff --git a/reactive/kotlinx-coroutines-rx2/src/RxObservable.kt b/reactive/kotlinx-coroutines-rx2/src/RxObservable.kt
index 2f483879..5f409815 100644
--- a/reactive/kotlinx-coroutines-rx2/src/RxObservable.kt
+++ b/reactive/kotlinx-coroutines-rx2/src/RxObservable.kt
@@ -1,9 +1,7 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
-@file:Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER")
-
package kotlinx.coroutines.rx2
import io.reactivex.*
@@ -11,10 +9,10 @@ import io.reactivex.exceptions.*
import kotlinx.atomicfu.*
import kotlinx.coroutines.*
import kotlinx.coroutines.channels.*
+import kotlinx.coroutines.internal.*
import kotlinx.coroutines.selects.*
import kotlinx.coroutines.sync.*
import kotlin.coroutines.*
-import kotlin.internal.*
/**
* Creates cold [observable][Observable] that will run a given [block] in a coroutine.
@@ -32,7 +30,6 @@ import kotlin.internal.*
* If the context does not have any dispatcher nor any other [ContinuationInterceptor], then [Dispatchers.Default] is used.
* Method throws [IllegalArgumentException] if provided [context] contains a [Job] instance.
*/
-@ExperimentalCoroutinesApi
public fun <T : Any> rxObservable(
context: CoroutineContext = EmptyCoroutineContext,
@BuilderInference block: suspend ProducerScope<T>.() -> Unit
@@ -42,17 +39,6 @@ public fun <T : Any> rxObservable(
return rxObservableInternal(GlobalScope, context, block)
}
-@Deprecated(
- message = "CoroutineScope.rxObservable is deprecated in favour of top-level rxObservable",
- level = DeprecationLevel.ERROR,
- replaceWith = ReplaceWith("rxObservable(context, block)")
-) // Since 1.3.0, will be error in 1.3.1 and hidden in 1.4.0
-@LowPriorityInOverloadResolution
-public fun <T : Any> CoroutineScope.rxObservable(
- context: CoroutineContext = EmptyCoroutineContext,
- @BuilderInference block: suspend ProducerScope<T>.() -> Unit
-): Observable<T> = rxObservableInternal(this, context, block)
-
private fun <T : Any> rxObservableInternal(
scope: CoroutineScope, // support for legacy rxObservable in scope
context: CoroutineContext,
@@ -68,39 +54,35 @@ private const val OPEN = 0 // open channel, still working
private const val CLOSED = -1 // closed, but have not signalled onCompleted/onError yet
private const val SIGNALLED = -2 // already signalled subscriber onCompleted/onError
-private class RxObservableCoroutine<T: Any>(
+private class RxObservableCoroutine<T : Any>(
parentContext: CoroutineContext,
private val subscriber: ObservableEmitter<T>
-) : AbstractCoroutine<Unit>(parentContext, true), ProducerScope<T>, SelectClause2<T, SendChannel<T>> {
+) : AbstractCoroutine<Unit>(parentContext, false, true), ProducerScope<T>, SelectClause2<T, SendChannel<T>> {
override val channel: SendChannel<T> get() = this
- // Mutex is locked when while subscriber.onXXX is being invoked
+ // Mutex is locked while subscriber.onXXX is being invoked
private val mutex = Mutex()
private val _signal = atomic(OPEN)
- override val isClosedForSend: Boolean get() = isCompleted
- override val isFull: Boolean = mutex.isLocked
+ override val isClosedForSend: Boolean get() = !isActive
override fun close(cause: Throwable?): Boolean = cancelCoroutine(cause)
override fun invokeOnClose(handler: (Throwable?) -> Unit) =
throw UnsupportedOperationException("RxObservableCoroutine doesn't support invokeOnClose")
- override fun offer(element: T): Boolean {
- if (!mutex.tryLock()) return false
- doLockedNext(element)
- return true
- }
+ override fun trySend(element: T): ChannelResult<Unit> =
+ if (!mutex.tryLock()) {
+ ChannelResult.failure()
+ } else {
+ when (val throwable = doLockedNext(element)) {
+ null -> ChannelResult.success(Unit)
+ else -> ChannelResult.closed(throwable)
+ }
+ }
public override suspend fun send(element: T) {
- // fast-path -- try send without suspension
- if (offer(element)) return
- // slow-path does suspend
- return sendSuspend(element)
- }
-
- private suspend fun sendSuspend(element: T) {
mutex.lock()
- doLockedNext(element)
+ doLockedNext(element)?.let { throw it }
}
override val onSend: SelectClause2<T, SendChannel<T>>
@@ -108,30 +90,39 @@ private class RxObservableCoroutine<T: Any>(
// registerSelectSend
@Suppress("PARAMETER_NAME_CHANGED_ON_OVERRIDE")
- override fun <R> registerSelectClause2(select: SelectInstance<R>, element: T, block: suspend (SendChannel<T>) -> R) {
+ override fun <R> registerSelectClause2(
+ select: SelectInstance<R>,
+ element: T,
+ block: suspend (SendChannel<T>) -> R
+ ) {
mutex.onLock.registerSelectClause2(select, null) {
- doLockedNext(element)
+ doLockedNext(element)?.let { throw it }
block(this)
}
}
// assert: mutex.isLocked()
- private fun doLockedNext(elem: T) {
+ private fun doLockedNext(elem: T): Throwable? {
// check if already closed for send
if (!isActive) {
doLockedSignalCompleted(completionCause, completionCauseHandled)
- throw getCancellationException()
+ return getCancellationException()
}
// notify subscriber
try {
subscriber.onNext(elem)
} catch (e: Throwable) {
- // If onNext fails with exception, then we cancel coroutine (with this exception) and then rethrow it
- // to abort the corresponding send/offer invocation. From the standpoint of coroutines machinery,
- // this failure is essentially equivalent to a failure of a child coroutine.
- cancelCoroutine(e)
- mutex.unlock()
- throw e
+ val cause = UndeliverableException(e)
+ val causeDelivered = close(cause)
+ unlockAndCheckCompleted()
+ return if (causeDelivered) {
+ // `cause` is the reason this channel is closed
+ cause
+ } else {
+ // Someone else closed the channel during `onNext`. We report `cause` as an undeliverable exception.
+ handleUndeliverableException(cause, context)
+ getCancellationException()
+ }
}
/*
* There is no sense to check for `isActive` before doing `unlock`, because cancellation/completion might
@@ -140,6 +131,7 @@ private class RxObservableCoroutine<T: Any>(
* We have to recheck `isCompleted` after `unlock` anyway.
*/
unlockAndCheckCompleted()
+ return null
}
private fun unlockAndCheckCompleted() {
@@ -153,33 +145,31 @@ private class RxObservableCoroutine<T: Any>(
private fun doLockedSignalCompleted(cause: Throwable?, handled: Boolean) {
// cancellation failures
try {
- if (_signal.value >= CLOSED) {
- _signal.value = SIGNALLED // we'll signal onError/onCompleted (that the final state -- no CAS needed)
+ if (_signal.value == SIGNALLED)
+ return
+ _signal.value = SIGNALLED // we'll signal onError/onCompleted (that the final state -- no CAS needed)
+ @Suppress("INVISIBLE_MEMBER")
+ val unwrappedCause = cause?.let { unwrap(it) }
+ if (unwrappedCause == null) {
try {
- if (cause != null && cause !is CancellationException) {
- /*
- * Reactive frameworks have two types of exceptions: regular and fatal.
- * Regular are passed to onError.
- * Fatal can be passed to onError, but even the standard implementations **can just swallow it** (e.g. see #1297).
- * Such behaviour is inconsistent, leads to silent failures and we can't possibly know whether
- * the cause will be handled by onError (and moreover, it depends on whether a fatal exception was
- * thrown by subscriber or upstream).
- * To make behaviour consistent and least surprising, we always handle fatal exceptions
- * by coroutines machinery, anyway, they should not be present in regular program flow,
- * thus our goal here is just to expose it as soon as possible.
- */
- subscriber.tryOnError(cause)
- if (!handled && cause.isFatal()) {
- handleUndeliverableException(cause, context)
- }
- }
- else {
- subscriber.onComplete()
- }
- } catch (e: Throwable) {
- // Unhandled exception (cannot handle in other way, since we are already complete)
+ subscriber.onComplete()
+ } catch (e: Exception) {
handleUndeliverableException(e, context)
}
+ } else if (unwrappedCause is UndeliverableException && !handled) {
+ /** Such exceptions are not reported to `onError`, as, according to the reactive specifications,
+ * exceptions thrown from the Subscriber methods must be treated as if the Subscriber was already
+ * cancelled. */
+ handleUndeliverableException(cause, context)
+ } else if (unwrappedCause !== getCancellationException() || !subscriber.isDisposed) {
+ try {
+ /** If the subscriber is already in a terminal state, the error will be signalled to
+ * `RxJavaPlugins.onError`. */
+ subscriber.onError(cause)
+ } catch (e: Exception) {
+ cause.addSuppressed(e)
+ handleUndeliverableException(cause, context)
+ }
}
} finally {
mutex.unlock()
@@ -201,9 +191,13 @@ private class RxObservableCoroutine<T: Any>(
}
}
-internal fun Throwable.isFatal() = try {
- Exceptions.throwIfFatal(this) // Rx-consistent behaviour without hardcode
- false
-} catch (e: Throwable) {
- true
-}
+/** @suppress */
+@Deprecated(
+ message = "CoroutineScope.rxObservable is deprecated in favour of top-level rxObservable",
+ level = DeprecationLevel.HIDDEN,
+ replaceWith = ReplaceWith("rxObservable(context, block)")
+) // Since 1.3.0, will be error in 1.3.1 and hidden in 1.4.0
+public fun <T : Any> CoroutineScope.rxObservable(
+ context: CoroutineContext = EmptyCoroutineContext,
+ @BuilderInference block: suspend ProducerScope<T>.() -> Unit
+): Observable<T> = rxObservableInternal(this, context, block)
diff --git a/reactive/kotlinx-coroutines-rx2/src/RxScheduler.kt b/reactive/kotlinx-coroutines-rx2/src/RxScheduler.kt
index 9952eb91..0262fc12 100644
--- a/reactive/kotlinx-coroutines-rx2/src/RxScheduler.kt
+++ b/reactive/kotlinx-coroutines-rx2/src/RxScheduler.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.rx2
diff --git a/reactive/kotlinx-coroutines-rx2/src/RxSingle.kt b/reactive/kotlinx-coroutines-rx2/src/RxSingle.kt
index 23040601..27842a21 100644
--- a/reactive/kotlinx-coroutines-rx2/src/RxSingle.kt
+++ b/reactive/kotlinx-coroutines-rx2/src/RxSingle.kt
@@ -1,15 +1,12 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
-@file:Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER")
-
package kotlinx.coroutines.rx2
import io.reactivex.*
import kotlinx.coroutines.*
import kotlin.coroutines.*
-import kotlin.internal.*
/**
* Creates cold [single][Single] that will run a given [block] in a coroutine and emits its result.
@@ -28,17 +25,6 @@ public fun <T : Any> rxSingle(
return rxSingleInternal(GlobalScope, context, block)
}
-@Deprecated(
- message = "CoroutineScope.rxSingle is deprecated in favour of top-level rxSingle",
- level = DeprecationLevel.ERROR,
- replaceWith = ReplaceWith("rxSingle(context, block)")
-) // Since 1.3.0, will be error in 1.3.1 and hidden in 1.4.0
-@LowPriorityInOverloadResolution
-public fun <T : Any> CoroutineScope.rxSingle(
- context: CoroutineContext = EmptyCoroutineContext,
- block: suspend CoroutineScope.() -> T
-): Single<T> = rxSingleInternal(this, context, block)
-
private fun <T : Any> rxSingleInternal(
scope: CoroutineScope, // support for legacy rxSingle in scope
context: CoroutineContext,
@@ -53,7 +39,7 @@ private fun <T : Any> rxSingleInternal(
private class RxSingleCoroutine<T: Any>(
parentContext: CoroutineContext,
private val subscriber: SingleEmitter<T>
-) : AbstractCoroutine<T>(parentContext, true) {
+) : AbstractCoroutine<T>(parentContext, false, true) {
override fun onCompleted(value: T) {
try {
subscriber.onSuccess(value)
@@ -64,11 +50,23 @@ private class RxSingleCoroutine<T: Any>(
override fun onCancelled(cause: Throwable, handled: Boolean) {
try {
- if (!subscriber.tryOnError(cause)) {
- handleUndeliverableException(cause, context)
+ if (subscriber.tryOnError(cause)) {
+ return
}
} catch (e: Throwable) {
- handleUndeliverableException(e, context)
+ cause.addSuppressed(e)
}
+ handleUndeliverableException(cause, context)
}
}
+
+/** @suppress */
+@Deprecated(
+ message = "CoroutineScope.rxSingle is deprecated in favour of top-level rxSingle",
+ level = DeprecationLevel.HIDDEN,
+ replaceWith = ReplaceWith("rxSingle(context, block)")
+) // Since 1.3.0, will be error in 1.3.1 and hidden in 1.4.0
+public fun <T : Any> CoroutineScope.rxSingle(
+ context: CoroutineContext = EmptyCoroutineContext,
+ block: suspend CoroutineScope.() -> T
+): Single<T> = rxSingleInternal(this, context, block)
diff --git a/reactive/kotlinx-coroutines-rx2/test/CompletableTest.kt b/reactive/kotlinx-coroutines-rx2/test/CompletableTest.kt
index 298b32bf..16f0005b 100644
--- a/reactive/kotlinx-coroutines-rx2/test/CompletableTest.kt
+++ b/reactive/kotlinx-coroutines-rx2/test/CompletableTest.kt
@@ -98,6 +98,31 @@ class CompletableTest : TestBase() {
}
}
+ /** Tests that calls to [await] throw [CancellationException] and dispose of the subscription when their [Job] is
+ * cancelled. */
+ @Test
+ fun testAwaitCancellation() = runTest {
+ expect(1)
+ val completable = CompletableSource { s ->
+ s.onSubscribe(object: Disposable {
+ override fun dispose() { expect(4) }
+ override fun isDisposed(): Boolean { expectUnreached(); return false }
+ })
+ }
+ val job = launch(start = CoroutineStart.UNDISPATCHED) {
+ try {
+ expect(2)
+ completable.await()
+ } catch (e: CancellationException) {
+ expect(5)
+ throw e
+ }
+ }
+ expect(3)
+ job.cancelAndJoin()
+ finish(6)
+ }
+
@Test
fun testSuppressedException() = runTest {
val completable = rxCompletable(currentDispatcher()) {
@@ -119,7 +144,7 @@ class CompletableTest : TestBase() {
}
@Test
- fun testUnhandledException() = runTest() {
+ fun testUnhandledException() = runTest {
expect(1)
var disposable: Disposable? = null
val handler = { e: Throwable ->
@@ -165,8 +190,7 @@ class CompletableTest : TestBase() {
withExceptionHandler(handler) {
rxCompletable(Dispatchers.Unconfined) {
expect(1)
- 42
- }.subscribe({ throw LinkageError() })
+ }.subscribe { throw LinkageError() }
finish(3)
}
}
diff --git a/reactive/kotlinx-coroutines-rx2/test/FlowableExceptionHandlingTest.kt b/reactive/kotlinx-coroutines-rx2/test/FlowableExceptionHandlingTest.kt
index 05b7ee92..31643929 100644
--- a/reactive/kotlinx-coroutines-rx2/test/FlowableExceptionHandlingTest.kt
+++ b/reactive/kotlinx-coroutines-rx2/test/FlowableExceptionHandlingTest.kt
@@ -38,16 +38,16 @@ class FlowableExceptionHandlingTest : TestBase() {
}
@Test
- fun testFatalException() = withExceptionHandler(handler<LinkageError>(3)) {
+ fun testFatalException() = withExceptionHandler({ expectUnreached() }) {
rxFlowable<Int>(Dispatchers.Unconfined) {
expect(1)
throw LinkageError()
}.subscribe({
expectUnreached()
}, {
- expect(2) // Fatal exception is reported to both onError and CEH
+ expect(2) // Fatal exceptions are not treated as special
})
- finish(4)
+ finish(3)
}
@Test
@@ -66,7 +66,7 @@ class FlowableExceptionHandlingTest : TestBase() {
}
@Test
- fun testFatalExceptionAsynchronous() = withExceptionHandler(handler<LinkageError>(3)) {
+ fun testFatalExceptionAsynchronous() = withExceptionHandler({ expectUnreached() }) {
rxFlowable<Int>(Dispatchers.Unconfined) {
expect(1)
throw LinkageError()
@@ -77,19 +77,19 @@ class FlowableExceptionHandlingTest : TestBase() {
}, {
expect(2)
})
- finish(4)
+ finish(3)
}
@Test
- fun testFatalExceptionFromSubscribe() = withExceptionHandler(handler<LinkageError>(4)) {
+ fun testFatalExceptionFromSubscribe() = withExceptionHandler(handler<LinkageError>(3)) {
rxFlowable(Dispatchers.Unconfined) {
expect(1)
send(Unit)
}.subscribe({
expect(2)
throw LinkageError()
- }, { expect(3) }) // Fatal exception is reported to both onError and CEH
- finish(5)
+ }, { expectUnreached() }) // Fatal exception is rethrown from `onNext` => the subscription is thought to be cancelled
+ finish(4)
}
@Test
diff --git a/reactive/kotlinx-coroutines-rx2/test/IntegrationTest.kt b/reactive/kotlinx-coroutines-rx2/test/IntegrationTest.kt
index 540fa76b..8a6362ad 100644
--- a/reactive/kotlinx-coroutines-rx2/test/IntegrationTest.kt
+++ b/reactive/kotlinx-coroutines-rx2/test/IntegrationTest.kt
@@ -7,6 +7,7 @@ package kotlinx.coroutines.rx2
import io.reactivex.*
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
+import kotlinx.coroutines.reactive.*
import org.junit.Test
import org.junit.runner.*
import org.junit.runners.*
@@ -92,7 +93,7 @@ class IntegrationTest(
assertEquals(n, observable.awaitLast())
assertFailsWith<IllegalArgumentException> { observable.awaitSingle() }
checkNumbers(n, observable)
- val channel = observable.openSubscription()
+ val channel = observable.toChannel()
checkNumbers(n, channel.consumeAsFlow().asObservable(ctx(coroutineContext)))
channel.cancel()
}
@@ -124,6 +125,21 @@ class IntegrationTest(
finish(3)
}
+ @Test
+ fun testObservableWithTimeout() = runTest {
+ val observable = rxObservable<Int> {
+ expect(2)
+ withTimeout(1) { delay(100) }
+ }
+ try {
+ expect(1)
+ observable.awaitFirstOrNull()
+ } catch (e: CancellationException) {
+ expect(3)
+ }
+ finish(4)
+ }
+
private suspend fun checkNumbers(n: Int, observable: Observable<Int>) {
var last = 0
observable.collect {
diff --git a/reactive/kotlinx-coroutines-rx2/test/MaybeTest.kt b/reactive/kotlinx-coroutines-rx2/test/MaybeTest.kt
index 08427dcf..f5d128d3 100644
--- a/reactive/kotlinx-coroutines-rx2/test/MaybeTest.kt
+++ b/reactive/kotlinx-coroutines-rx2/test/MaybeTest.kt
@@ -7,13 +7,12 @@ package kotlinx.coroutines.rx2
import io.reactivex.*
import io.reactivex.disposables.*
import io.reactivex.exceptions.*
-import io.reactivex.functions.*
import io.reactivex.internal.functions.Functions.*
import kotlinx.coroutines.*
+import kotlinx.coroutines.CancellationException
import org.junit.*
import org.junit.Test
import java.util.concurrent.*
-import java.util.concurrent.CancellationException
import kotlin.test.*
class MaybeTest : TestBase() {
@@ -47,7 +46,7 @@ class MaybeTest : TestBase() {
null
}
expect(2)
- maybe.subscribe (emptyConsumer(), ON_ERROR_MISSING, Action {
+ maybe.subscribe (emptyConsumer(), ON_ERROR_MISSING, {
expect(5)
})
expect(3)
@@ -112,18 +111,45 @@ class MaybeTest : TestBase() {
@Test
fun testMaybeAwait() = runBlocking {
- assertEquals("OK", Maybe.just("O").await() + "K")
+ assertEquals("OK", Maybe.just("O").awaitSingleOrNull() + "K")
+ assertEquals("OK", Maybe.just("O").awaitSingle() + "K")
}
@Test
- fun testMaybeAwaitForNull() = runBlocking {
- assertNull(Maybe.empty<String>().await())
+ fun testMaybeAwaitForNull(): Unit = runBlocking {
+ assertNull(Maybe.empty<String>().awaitSingleOrNull())
+ assertFailsWith<NoSuchElementException> { Maybe.empty<String>().awaitSingle() }
+ }
+
+ /** Tests that calls to [awaitSingleOrNull] throw [CancellationException] and dispose of the subscription when their
+ * [Job] is cancelled. */
+ @Test
+ fun testMaybeAwaitCancellation() = runTest {
+ expect(1)
+ val maybe = MaybeSource<Int> { s ->
+ s.onSubscribe(object: Disposable {
+ override fun dispose() { expect(4) }
+ override fun isDisposed(): Boolean { expectUnreached(); return false }
+ })
+ }
+ val job = launch(start = CoroutineStart.UNDISPATCHED) {
+ try {
+ expect(2)
+ maybe.awaitSingleOrNull()
+ } catch (e: CancellationException) {
+ expect(5)
+ throw e
+ }
+ }
+ expect(3)
+ job.cancelAndJoin()
+ finish(6)
}
@Test
fun testMaybeEmitAndAwait() {
val maybe = rxMaybe {
- Maybe.just("O").await() + "K"
+ Maybe.just("O").awaitSingleOrNull() + "K"
}
checkMaybeValue(maybe) {
@@ -205,7 +231,7 @@ class MaybeTest : TestBase() {
@Test
fun testCancelledConsumer() = runTest {
expect(1)
- val maybe = rxMaybe<Int>(currentDispatcher()) {
+ val maybe = rxMaybe(currentDispatcher()) {
expect(4)
try {
delay(Long.MAX_VALUE)
@@ -228,6 +254,56 @@ class MaybeTest : TestBase() {
finish(7)
}
+ /** Tests the simple scenario where the Maybe doesn't output a value. */
+ @Test
+ fun testMaybeCollectEmpty() = runTest {
+ expect(1)
+ Maybe.empty<Int>().collect {
+ expectUnreached()
+ }
+ finish(2)
+ }
+
+ /** Tests the simple scenario where the Maybe doesn't output a value. */
+ @Test
+ fun testMaybeCollectSingle() = runTest {
+ expect(1)
+ Maybe.just("OK").collect {
+ assertEquals("OK", it)
+ expect(2)
+ }
+ finish(3)
+ }
+
+ /** Tests the behavior of [collect] when the Maybe raises an error. */
+ @Test
+ fun testMaybeCollectThrowingMaybe() = runTest {
+ expect(1)
+ try {
+ Maybe.error<Int>(TestException()).collect {
+ expectUnreached()
+ }
+ } catch (e: TestException) {
+ expect(2)
+ }
+ finish(3)
+ }
+
+ /** Tests the behavior of [collect] when the action throws. */
+ @Test
+ fun testMaybeCollectThrowingAction() = runTest {
+ expect(1)
+ try {
+ Maybe.just("OK").collect {
+ expect(2)
+ throw TestException()
+ }
+ } catch (e: TestException) {
+ expect(3)
+ }
+ finish(4)
+ }
+
@Test
fun testSuppressedException() = runTest {
val maybe = rxMaybe(currentDispatcher()) {
@@ -241,7 +317,7 @@ class MaybeTest : TestBase() {
}
}
try {
- maybe.await()
+ maybe.awaitSingleOrNull()
expectUnreached()
} catch (e: TestException) {
assertTrue(e.suppressed[0] is TestException2)
@@ -301,7 +377,7 @@ class MaybeTest : TestBase() {
rxMaybe(Dispatchers.Unconfined) {
expect(1)
42
- }.subscribe({ throw LinkageError() })
+ }.subscribe { throw LinkageError() }
finish(3)
}
}
diff --git a/reactive/kotlinx-coroutines-rx2/test/ObservableCollectTest.kt b/reactive/kotlinx-coroutines-rx2/test/ObservableCollectTest.kt
new file mode 100644
index 00000000..508f594a
--- /dev/null
+++ b/reactive/kotlinx-coroutines-rx2/test/ObservableCollectTest.kt
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.rx2
+
+import io.reactivex.*
+import io.reactivex.disposables.*
+import kotlinx.coroutines.*
+import org.junit.Test
+import kotlin.test.*
+
+class ObservableCollectTest: TestBase() {
+
+ /** Tests the behavior of [collect] when the publisher raises an error. */
+ @Test
+ fun testObservableCollectThrowingObservable() = runTest {
+ expect(1)
+ var sum = 0
+ try {
+ rxObservable {
+ for (i in 0..100) {
+ send(i)
+ }
+ throw TestException()
+ }.collect {
+ sum += it
+ }
+ } catch (e: TestException) {
+ assertTrue(sum > 0)
+ finish(2)
+ }
+ }
+
+ /** Tests the behavior of [collect] when the action throws. */
+ @Test
+ fun testObservableCollectThrowingAction() = runTest {
+ expect(1)
+ var sum = 0
+ val expectedSum = 5
+ try {
+ var disposed = false
+ ObservableSource<Int> { observer ->
+ launch(Dispatchers.Default) {
+ observer.onSubscribe(object : Disposable {
+ override fun dispose() {
+ disposed = true
+ expect(expectedSum + 2)
+ }
+
+ override fun isDisposed(): Boolean = disposed
+ })
+ while (!disposed) {
+ observer.onNext(1)
+ }
+ }
+ }.collect {
+ expect(sum + 2)
+ sum += it
+ if (sum == expectedSum) {
+ throw TestException()
+ }
+ }
+ } catch (e: TestException) {
+ assertEquals(expectedSum, sum)
+ finish(expectedSum + 3)
+ }
+ }
+} \ No newline at end of file
diff --git a/reactive/kotlinx-coroutines-rx2/test/ObservableExceptionHandlingTest.kt b/reactive/kotlinx-coroutines-rx2/test/ObservableExceptionHandlingTest.kt
index d6cdd3ca..fb3d0f69 100644
--- a/reactive/kotlinx-coroutines-rx2/test/ObservableExceptionHandlingTest.kt
+++ b/reactive/kotlinx-coroutines-rx2/test/ObservableExceptionHandlingTest.kt
@@ -8,6 +8,7 @@ import io.reactivex.exceptions.*
import kotlinx.coroutines.*
import org.junit.*
import org.junit.Test
+import java.util.concurrent.*
import kotlin.test.*
class ObservableExceptionHandlingTest : TestBase() {
@@ -18,7 +19,7 @@ class ObservableExceptionHandlingTest : TestBase() {
}
private inline fun <reified T : Throwable> handler(expect: Int) = { t: Throwable ->
- assertTrue(t is UndeliverableException && t.cause is T)
+ assertTrue(t is UndeliverableException && t.cause is T, "$t")
expect(expect)
}
@@ -38,8 +39,8 @@ class ObservableExceptionHandlingTest : TestBase() {
}
@Test
- fun testFatalException() = withExceptionHandler(handler<LinkageError>(3)) {
- rxObservable<Int>(Dispatchers.Unconfined) {
+ fun testFatalException() = withExceptionHandler({ expectUnreached() }) {
+ rxObservable<Int>(Dispatchers.Unconfined + cehUnreached()) {
expect(1)
throw LinkageError()
}.subscribe({
@@ -47,7 +48,7 @@ class ObservableExceptionHandlingTest : TestBase() {
}, {
expect(2)
})
- finish(4)
+ finish(3)
}
@Test
@@ -66,7 +67,7 @@ class ObservableExceptionHandlingTest : TestBase() {
}
@Test
- fun testFatalExceptionAsynchronous() = withExceptionHandler(handler<LinkageError>(3)) {
+ fun testFatalExceptionAsynchronous() = withExceptionHandler({ expectUnreached() }) {
rxObservable<Int>(Dispatchers.Unconfined) {
expect(1)
throw LinkageError()
@@ -75,20 +76,28 @@ class ObservableExceptionHandlingTest : TestBase() {
.subscribe({
expectUnreached()
}, {
- expect(2) // Fatal exception is not reported in onError
+ expect(2) // Fatal exceptions are not treated in a special manner
})
- finish(4)
+ finish(3)
}
@Test
- fun testFatalExceptionFromSubscribe() = withExceptionHandler(handler<LinkageError>(4)) {
+ fun testFatalExceptionFromSubscribe() = withExceptionHandler(handler<LinkageError>(3)) {
+ val latch = CountDownLatch(1)
rxObservable(Dispatchers.Unconfined) {
expect(1)
- send(Unit)
+ val result = trySend(Unit)
+ val exception = result.exceptionOrNull()
+ assertTrue(exception is UndeliverableException)
+ assertTrue(exception.cause is LinkageError)
+ assertTrue(isClosedForSend)
+ expect(4)
+ latch.countDown()
}.subscribe({
expect(2)
throw LinkageError()
- }, { expect(3) }) // Unreached because fatal errors are rethrown
+ }, { expectUnreached() }) // Unreached because RxJava bubbles up fatal exceptions, causing `onNext` to throw.
+ latch.await()
finish(5)
}
@@ -100,7 +109,7 @@ class ObservableExceptionHandlingTest : TestBase() {
}.subscribe({
expect(2)
throw TestException()
- }, { expect(3) }) // not reported to onError because came from the subscribe itself
+ }, { expect(3) })
finish(4)
}
@@ -119,7 +128,7 @@ class ObservableExceptionHandlingTest : TestBase() {
}
@Test
- fun testAsynchronousFatalExceptionFromSubscribe() = withExceptionHandler(handler<LinkageError>(4)) {
+ fun testAsynchronousFatalExceptionFromSubscribe() = withExceptionHandler(handler<LinkageError>(3)) {
rxObservable(Dispatchers.Unconfined) {
expect(1)
send(Unit)
@@ -128,7 +137,7 @@ class ObservableExceptionHandlingTest : TestBase() {
.subscribe({
expect(2)
throw LinkageError()
- }, { expect(3) })
- finish(5)
+ }, { expectUnreached() }) // Unreached because RxJava bubbles up fatal exceptions, causing `onNext` to throw.
+ finish(4)
}
}
diff --git a/reactive/kotlinx-coroutines-rx2/test/ObservableSingleTest.kt b/reactive/kotlinx-coroutines-rx2/test/ObservableSingleTest.kt
index 4454190f..e246407a 100644
--- a/reactive/kotlinx-coroutines-rx2/test/ObservableSingleTest.kt
+++ b/reactive/kotlinx-coroutines-rx2/test/ObservableSingleTest.kt
@@ -5,7 +5,9 @@
package kotlinx.coroutines.rx2
import io.reactivex.*
+import io.reactivex.disposables.*
import kotlinx.coroutines.*
+import kotlinx.coroutines.CancellationException
import org.junit.*
import org.junit.Test
import java.util.concurrent.*
@@ -101,7 +103,7 @@ class ObservableSingleTest : TestBase() {
@Test
fun testAwaitFirstOrNull() {
- val observable = rxObservable<String> {
+ val observable = rxObservable {
send(Observable.empty<String>().awaitFirstOrNull() ?: "OK")
}
@@ -154,6 +156,32 @@ class ObservableSingleTest : TestBase() {
}
}
+ /** Tests that calls to [awaitFirst] (and, thus, the other methods) throw [CancellationException] and dispose of
+ * the subscription when their [Job] is cancelled. */
+ @Test
+ fun testAwaitCancellation() = runTest {
+ expect(1)
+ val observable = ObservableSource<Int> { s ->
+ s.onSubscribe(object: Disposable {
+ override fun dispose() { expect(4) }
+ override fun isDisposed(): Boolean { expectUnreached(); return false }
+ })
+ }
+ val job = launch(start = CoroutineStart.UNDISPATCHED) {
+ try {
+ expect(2)
+ observable.awaitFirst()
+ } catch (e: CancellationException) {
+ expect(5)
+ throw e
+ }
+ }
+ expect(3)
+ job.cancelAndJoin()
+ finish(6)
+ }
+
+
@Test
fun testExceptionFromObservable() {
val observable = rxObservable {
diff --git a/reactive/kotlinx-coroutines-rx2/test/ObservableSourceAsFlowStressTest.kt b/reactive/kotlinx-coroutines-rx2/test/ObservableSourceAsFlowStressTest.kt
index 159f3729..0253fced 100644
--- a/reactive/kotlinx-coroutines-rx2/test/ObservableSourceAsFlowStressTest.kt
+++ b/reactive/kotlinx-coroutines-rx2/test/ObservableSourceAsFlowStressTest.kt
@@ -26,7 +26,7 @@ class ObservableSourceAsFlowStressTest : TestBase() {
val latch = Channel<Unit>(1)
var i = 0
val observable = Observable.interval(100L, TimeUnit.MICROSECONDS)
- .doOnNext { if (++i > 100) latch.offer(Unit) }
+ .doOnNext { if (++i > 100) latch.trySend(Unit) }
val job = observable.asFlow().launchIn(CoroutineScope(Dispatchers.Default))
latch.receive()
job.cancelAndJoin()
diff --git a/reactive/kotlinx-coroutines-rx2/test/ObservableSubscriptionSelectTest.kt b/reactive/kotlinx-coroutines-rx2/test/ObservableSubscriptionSelectTest.kt
index 3cd3bbff..2c22cbf0 100644
--- a/reactive/kotlinx-coroutines-rx2/test/ObservableSubscriptionSelectTest.kt
+++ b/reactive/kotlinx-coroutines-rx2/test/ObservableSubscriptionSelectTest.kt
@@ -1,12 +1,14 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.rx2
import kotlinx.coroutines.*
+import kotlinx.coroutines.channels.*
import kotlinx.coroutines.selects.*
import org.junit.Test
+import kotlin.onSuccess
import kotlin.test.*
class ObservableSubscriptionSelectTest : TestBase() {
@@ -18,27 +20,27 @@ class ObservableSubscriptionSelectTest : TestBase() {
var a = 0
var b = 0
// open two subs
- val channelA = source.openSubscription()
- val channelB = source.openSubscription()
+ val channelA = source.toChannel()
+ val channelB = source.toChannel()
loop@ while (true) {
val done: Int = select {
- channelA.onReceiveOrNull {
- if (it != null) assertEquals(a++, it)
- if (it == null) 0 else 1
+ channelA.onReceiveCatching { result ->
+ result.onSuccess { assertEquals(a++, it) }
+ if (result.isSuccess) 1 else 0
}
- channelB.onReceiveOrNull {
- if (it != null) assertEquals(b++, it)
- if (it == null) 0 else 2
+ channelB.onReceiveCatching { result ->
+ result.onSuccess { assertEquals(b++, it) }
+ if (result.isSuccess) 2 else 0
}
}
when (done) {
0 -> break@loop
1 -> {
- val r = channelB.receiveOrNull()
+ val r = channelB.receiveCatching().getOrNull()
if (r != null) assertEquals(b++, r)
}
2 -> {
- val r = channelA.receiveOrNull()
+ val r = channelA.receiveCatching().getOrNull()
if (r != null) assertEquals(a++, r)
}
}
@@ -48,4 +50,4 @@ class ObservableSubscriptionSelectTest : TestBase() {
// should receive one of them fully
assertTrue(a == n || b == n)
}
-} \ No newline at end of file
+}
diff --git a/reactive/kotlinx-coroutines-rx2/test/SingleTest.kt b/reactive/kotlinx-coroutines-rx2/test/SingleTest.kt
index c66188a1..b359d963 100644
--- a/reactive/kotlinx-coroutines-rx2/test/SingleTest.kt
+++ b/reactive/kotlinx-coroutines-rx2/test/SingleTest.kt
@@ -9,6 +9,7 @@ import io.reactivex.disposables.*
import io.reactivex.exceptions.*
import io.reactivex.functions.*
import kotlinx.coroutines.*
+import kotlinx.coroutines.CancellationException
import org.junit.*
import org.junit.Test
import java.util.concurrent.*
@@ -98,6 +99,31 @@ class SingleTest : TestBase() {
assertEquals("OK", Single.just("O").await() + "K")
}
+ /** Tests that calls to [await] throw [CancellationException] and dispose of the subscription when their
+ * [Job] is cancelled. */
+ @Test
+ fun testSingleAwaitCancellation() = runTest {
+ expect(1)
+ val single = SingleSource<Int> { s ->
+ s.onSubscribe(object: Disposable {
+ override fun dispose() { expect(4) }
+ override fun isDisposed(): Boolean { expectUnreached(); return false }
+ })
+ }
+ val job = launch(start = CoroutineStart.UNDISPATCHED) {
+ try {
+ expect(2)
+ single.await()
+ } catch (e: CancellationException) {
+ expect(5)
+ throw e
+ }
+ }
+ expect(3)
+ job.cancelAndJoin()
+ finish(6)
+ }
+
@Test
fun testSingleEmitAndAwait() {
val single = rxSingle {
@@ -221,7 +247,7 @@ class SingleTest : TestBase() {
fun testFatalExceptionInSingle() = runTest {
rxSingle(Dispatchers.Unconfined) {
throw LinkageError()
- }.subscribe({ _, e -> assertTrue(e is LinkageError); expect(1) })
+ }.subscribe { _, e -> assertTrue(e is LinkageError); expect(1) }
finish(2)
}
diff --git a/reactive/kotlinx-coroutines-rx3/README.md b/reactive/kotlinx-coroutines-rx3/README.md
index 3aa73eb9..1530558c 100644
--- a/reactive/kotlinx-coroutines-rx3/README.md
+++ b/reactive/kotlinx-coroutines-rx3/README.md
@@ -25,8 +25,8 @@ Suspending extension functions and suspending iteration:
| **Name** | **Description**
| -------- | ---------------
| [CompletableSource.await][io.reactivex.rxjava3.core.CompletableSource.await] | Awaits for completion of the completable value
-| [MaybeSource.await][io.reactivex.rxjava3.core.MaybeSource.await] | Awaits for the value of the maybe and returns it or null
-| [MaybeSource.awaitOrDefault][io.reactivex.rxjava3.core.MaybeSource.awaitOrDefault] | Awaits for the value of the maybe and returns it or default
+| [MaybeSource.awaitSingle][io.reactivex.rxjava3.core.MaybeSource.awaitSingle] | Awaits for the value of the maybe and returns it or throws an exception
+| [MaybeSource.awaitSingleOrNull][io.reactivex.rxjava3.core.MaybeSource.awaitSingleOrNull] | Awaits for the value of the maybe and returns it or null
| [SingleSource.await][io.reactivex.rxjava3.core.SingleSource.await] | Awaits for completion of the single value and returns it
| [ObservableSource.awaitFirst][io.reactivex.rxjava3.core.ObservableSource.awaitFirst] | Awaits for the first value from the given observable
| [ObservableSource.awaitFirstOrDefault][io.reactivex.rxjava3.core.ObservableSource.awaitFirstOrDefault] | Awaits for the first value from the given observable or default
@@ -49,34 +49,42 @@ Conversion functions:
<!--- MODULE kotlinx-coroutines-core -->
<!--- INDEX kotlinx.coroutines -->
+
[CoroutineScope]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-scope/index.html
[CoroutineDispatcher]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-dispatcher/index.html
+
<!--- INDEX kotlinx.coroutines.channels -->
+
[ProducerScope]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-producer-scope/index.html
+
<!--- INDEX kotlinx.coroutines.flow -->
+
[Flow]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/-flow/index.html
+
<!--- MODULE kotlinx-coroutines-rx3 -->
<!--- INDEX kotlinx.coroutines.rx3 -->
+
[rxCompletable]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-rx3/kotlinx.coroutines.rx3/rx-completable.html
[rxMaybe]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-rx3/kotlinx.coroutines.rx3/rx-maybe.html
[rxSingle]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-rx3/kotlinx.coroutines.rx3/rx-single.html
[rxObservable]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-rx3/kotlinx.coroutines.rx3/rx-observable.html
[rxFlowable]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-rx3/kotlinx.coroutines.rx3/rx-flowable.html
-[Flow.asFlowable]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-rx3/kotlinx.coroutines.rx3/kotlinx.coroutines.flow.-flow/as-flowable.html
-[Flow.asObservable]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-rx3/kotlinx.coroutines.rx3/kotlinx.coroutines.flow.-flow/as-observable.html
-[ObservableSource.asFlow]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-rx3/kotlinx.coroutines.rx3/io.reactivex.rxjava3.core.-observable-source/as-flow.html
-[io.reactivex.rxjava3.core.CompletableSource.await]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-rx3/kotlinx.coroutines.rx3/io.reactivex.rxjava3.core.-completable-source/await.html
-[io.reactivex.rxjava3.core.MaybeSource.await]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-rx3/kotlinx.coroutines.rx3/io.reactivex.rxjava3.core.-maybe-source/await.html
-[io.reactivex.rxjava3.core.MaybeSource.awaitOrDefault]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-rx3/kotlinx.coroutines.rx3/io.reactivex.rxjava3.core.-maybe-source/await-or-default.html
-[io.reactivex.rxjava3.core.SingleSource.await]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-rx3/kotlinx.coroutines.rx3/io.reactivex.rxjava3.core.-single-source/await.html
-[io.reactivex.rxjava3.core.ObservableSource.awaitFirst]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-rx3/kotlinx.coroutines.rx3/io.reactivex.rxjava3.core.-observable-source/await-first.html
-[io.reactivex.rxjava3.core.ObservableSource.awaitFirstOrDefault]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-rx3/kotlinx.coroutines.rx3/io.reactivex.rxjava3.core.-observable-source/await-first-or-default.html
-[io.reactivex.rxjava3.core.ObservableSource.awaitFirstOrElse]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-rx3/kotlinx.coroutines.rx3/io.reactivex.rxjava3.core.-observable-source/await-first-or-else.html
-[io.reactivex.rxjava3.core.ObservableSource.awaitFirstOrNull]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-rx3/kotlinx.coroutines.rx3/io.reactivex.rxjava3.core.-observable-source/await-first-or-null.html
-[io.reactivex.rxjava3.core.ObservableSource.awaitSingle]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-rx3/kotlinx.coroutines.rx3/io.reactivex.rxjava3.core.-observable-source/await-single.html
-[kotlinx.coroutines.Job.asCompletable]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-rx3/kotlinx.coroutines.rx3/kotlinx.coroutines.-job/as-completable.html
-[kotlinx.coroutines.Deferred.asSingle]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-rx3/kotlinx.coroutines.rx3/kotlinx.coroutines.-deferred/as-single.html
-[io.reactivex.rxjava3.core.Scheduler.asCoroutineDispatcher]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-rx3/kotlinx.coroutines.rx3/io.reactivex.rxjava3.core.-scheduler/as-coroutine-dispatcher.html
+[Flow.asFlowable]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-rx3/kotlinx.coroutines.rx3/as-flowable.html
+[Flow.asObservable]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-rx3/kotlinx.coroutines.rx3/as-observable.html
+[ObservableSource.asFlow]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-rx3/kotlinx.coroutines.rx3/as-flow.html
+[io.reactivex.rxjava3.core.CompletableSource.await]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-rx3/kotlinx.coroutines.rx3/await.html
+[io.reactivex.rxjava3.core.MaybeSource.awaitSingle]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-rx3/kotlinx.coroutines.rx3/await-single.html
+[io.reactivex.rxjava3.core.MaybeSource.awaitSingleOrNull]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-rx3/kotlinx.coroutines.rx3/await-single-or-null.html
+[io.reactivex.rxjava3.core.SingleSource.await]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-rx3/kotlinx.coroutines.rx3/await.html
+[io.reactivex.rxjava3.core.ObservableSource.awaitFirst]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-rx3/kotlinx.coroutines.rx3/await-first.html
+[io.reactivex.rxjava3.core.ObservableSource.awaitFirstOrDefault]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-rx3/kotlinx.coroutines.rx3/await-first-or-default.html
+[io.reactivex.rxjava3.core.ObservableSource.awaitFirstOrElse]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-rx3/kotlinx.coroutines.rx3/await-first-or-else.html
+[io.reactivex.rxjava3.core.ObservableSource.awaitFirstOrNull]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-rx3/kotlinx.coroutines.rx3/await-first-or-null.html
+[io.reactivex.rxjava3.core.ObservableSource.awaitSingle]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-rx3/kotlinx.coroutines.rx3/await-single.html
+[kotlinx.coroutines.Job.asCompletable]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-rx3/kotlinx.coroutines.rx3/as-completable.html
+[kotlinx.coroutines.Deferred.asSingle]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-rx3/kotlinx.coroutines.rx3/as-single.html
+[io.reactivex.rxjava3.core.Scheduler.asCoroutineDispatcher]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-rx3/kotlinx.coroutines.rx3/as-coroutine-dispatcher.html
+
<!--- END -->
# Package kotlinx.coroutines.rx3
diff --git a/reactive/kotlinx-coroutines-rx3/api/kotlinx-coroutines-rx3.api b/reactive/kotlinx-coroutines-rx3/api/kotlinx-coroutines-rx3.api
index 6d2dd63d..f6f3f1d0 100644
--- a/reactive/kotlinx-coroutines-rx3/api/kotlinx-coroutines-rx3.api
+++ b/reactive/kotlinx-coroutines-rx3/api/kotlinx-coroutines-rx3.api
@@ -8,7 +8,9 @@ public final class kotlinx/coroutines/rx3/RxAwaitKt {
public static final fun awaitFirstOrNull (Lio/reactivex/rxjava3/core/ObservableSource;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static final fun awaitLast (Lio/reactivex/rxjava3/core/ObservableSource;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static final fun awaitOrDefault (Lio/reactivex/rxjava3/core/MaybeSource;Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+ public static final fun awaitSingle (Lio/reactivex/rxjava3/core/MaybeSource;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static final fun awaitSingle (Lio/reactivex/rxjava3/core/ObservableSource;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+ public static final fun awaitSingleOrNull (Lio/reactivex/rxjava3/core/MaybeSource;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
}
public final class kotlinx/coroutines/rx3/RxChannelKt {
diff --git a/reactive/kotlinx-coroutines-rx3/build.gradle b/reactive/kotlinx-coroutines-rx3/build.gradle
index ced694ab..15ef66da 100644
--- a/reactive/kotlinx-coroutines-rx3/build.gradle
+++ b/reactive/kotlinx-coroutines-rx3/build.gradle
@@ -1,13 +1,15 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
+import org.jetbrains.dokka.gradle.DokkaTaskPartial
+
targetCompatibility = JavaVersion.VERSION_1_8
dependencies {
- compile project(':kotlinx-coroutines-reactive')
- testCompile project(':kotlinx-coroutines-reactive').sourceSets.test.output
- testCompile "org.reactivestreams:reactive-streams-tck:$reactive_streams_version"
- compile "io.reactivex.rxjava3:rxjava:$rxjava3_version"
+ api project(':kotlinx-coroutines-reactive')
+ testImplementation project(':kotlinx-coroutines-reactive').sourceSets.test.output
+ testImplementation "org.reactivestreams:reactive-streams-tck:$reactive_streams_version"
+ api "io.reactivex.rxjava3:rxjava:$rxjava3_version"
}
compileTestKotlin {
@@ -18,10 +20,12 @@ compileKotlin {
kotlinOptions.jvmTarget = "1.8"
}
-tasks.withType(dokka.getClass()) {
- externalDocumentationLink {
- url = new URL('http://reactivex.io/RxJava/3.x/javadoc/')
- packageListUrl = projectDir.toPath().resolve("package.list").toUri().toURL()
+tasks.withType(DokkaTaskPartial.class) {
+ dokkaSourceSets.configureEach {
+ externalDocumentationLink {
+ url.set(new URL('http://reactivex.io/RxJava/3.x/javadoc/'))
+ packageListUrl.set(projectDir.toPath().resolve("package.list").toUri().toURL())
+ }
}
}
diff --git a/reactive/kotlinx-coroutines-rx3/src/RxAwait.kt b/reactive/kotlinx-coroutines-rx3/src/RxAwait.kt
index e52556e4..2a14cf7c 100644
--- a/reactive/kotlinx-coroutines-rx3/src/RxAwait.kt
+++ b/reactive/kotlinx-coroutines-rx3/src/RxAwait.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.rx3
@@ -15,11 +15,12 @@ import kotlin.coroutines.*
// ------------------------ CompletableSource ------------------------
/**
- * Awaits for completion of this completable without blocking a thread.
- * Returns `Unit` or throws the corresponding exception if this completable had produced error.
+ * Awaits for completion of this completable without blocking the thread.
+ * Returns `Unit`, or throws the corresponding exception if this completable produces an error.
*
* This suspending function is cancellable. If the [Job] of the invoking coroutine is cancelled or completed while this
- * suspending function is suspended, this function immediately resumes with [CancellationException].
+ * suspending function is suspended, this function immediately resumes with [CancellationException] and disposes of its
+ * subscription.
*/
public suspend fun CompletableSource.await(): Unit = suspendCancellableCoroutine { cont ->
subscribe(object : CompletableObserver {
@@ -32,6 +33,37 @@ public suspend fun CompletableSource.await(): Unit = suspendCancellableCoroutine
// ------------------------ MaybeSource ------------------------
/**
+ * Awaits for completion of the [MaybeSource] without blocking the thread.
+ * Returns the resulting value, or `null` if no value is produced, or throws the corresponding exception if this
+ * [MaybeSource] produces an error.
+ *
+ * This suspending function is cancellable.
+ * If the [Job] of the current coroutine is cancelled or completed while this suspending function is waiting, this
+ * function immediately resumes with [CancellationException] and disposes of its subscription.
+ */
+@Suppress("UNCHECKED_CAST")
+public suspend fun <T> MaybeSource<T>.awaitSingleOrNull(): T? = suspendCancellableCoroutine { cont ->
+ subscribe(object : MaybeObserver<T> {
+ override fun onSubscribe(d: Disposable) { cont.disposeOnCancellation(d) }
+ override fun onComplete() { cont.resume(null) }
+ override fun onSuccess(t: T) { cont.resume(t) }
+ override fun onError(error: Throwable) { cont.resumeWithException(error) }
+ })
+}
+
+/**
+ * Awaits for completion of the [MaybeSource] without blocking the thread.
+ * Returns the resulting value, or throws if either no value is produced or this [MaybeSource] produces an error.
+ *
+ * This suspending function is cancellable.
+ * If the [Job] of the current coroutine is cancelled or completed while this suspending function is waiting, this
+ * function immediately resumes with [CancellationException] and disposes of its subscription.
+ *
+ * @throws NoSuchElementException if no elements were produced by this [MaybeSource].
+ */
+public suspend fun <T> MaybeSource<T>.awaitSingle(): T = awaitSingleOrNull() ?: throw NoSuchElementException()
+
+/**
* Awaits for completion of the maybe without blocking a thread.
* Returns the resulting value, null if no value was produced or throws the corresponding exception if this
* maybe had produced error.
@@ -39,9 +71,20 @@ public suspend fun CompletableSource.await(): Unit = suspendCancellableCoroutine
* This suspending function is cancellable.
* If the [Job] of the current coroutine is cancelled or completed while this suspending function is waiting, this function
* immediately resumes with [CancellationException].
+ *
+ * ### Deprecation
+ *
+ * Deprecated in favor of [awaitSingleOrNull] in order to reflect that `null` can be returned to denote the absence of
+ * a value, as opposed to throwing in such case.
+ *
+ * @suppress
*/
-@Suppress("UNCHECKED_CAST")
-public suspend fun <T> MaybeSource<T>.await(): T? = (this as MaybeSource<T?>).awaitOrDefault(null)
+@Deprecated(
+ message = "Deprecated in favor of awaitSingleOrNull()",
+ level = DeprecationLevel.WARNING,
+ replaceWith = ReplaceWith("this.awaitSingleOrNull()")
+) // Warning since 1.5, error in 1.6, hidden in 1.7
+public suspend fun <T> MaybeSource<T>.await(): T? = awaitSingleOrNull()
/**
* Awaits for completion of the maybe without blocking a thread.
@@ -51,25 +94,30 @@ public suspend fun <T> MaybeSource<T>.await(): T? = (this as MaybeSource<T?>).aw
* This suspending function is cancellable.
* If the [Job] of the current coroutine is cancelled or completed while this suspending function is waiting, this function
* immediately resumes with [CancellationException].
+ *
+ * ### Deprecation
+ *
+ * Deprecated in favor of [awaitSingleOrNull] for naming consistency (see the deprecation of [MaybeSource.await] for
+ * details).
+ *
+ * @suppress
*/
-public suspend fun <T> MaybeSource<T>.awaitOrDefault(default: T): T = suspendCancellableCoroutine { cont ->
- subscribe(object : MaybeObserver<T> {
- override fun onSubscribe(d: Disposable) { cont.disposeOnCancellation(d) }
- override fun onComplete() { cont.resume(default) }
- override fun onSuccess(t: T) { cont.resume(t) }
- override fun onError(error: Throwable) { cont.resumeWithException(error) }
- })
-}
+@Deprecated(
+ message = "Deprecated in favor of awaitSingleOrNull()",
+ level = DeprecationLevel.WARNING,
+ replaceWith = ReplaceWith("this.awaitSingleOrNull() ?: default")
+) // Warning since 1.5, error in 1.6, hidden in 1.7
+public suspend fun <T> MaybeSource<T>.awaitOrDefault(default: T): T = awaitSingleOrNull() ?: default
// ------------------------ SingleSource ------------------------
/**
- * Awaits for completion of the single value without blocking a thread.
- * Returns the resulting value or throws the corresponding exception if this single had produced error.
+ * Awaits for completion of the single value response without blocking the thread.
+ * Returns the resulting value, or throws the corresponding exception if this response produces an error.
*
* This suspending function is cancellable.
- * If the [Job] of the current coroutine is cancelled or completed while this suspending function is waiting, this function
- * immediately resumes with [CancellationException].
+ * If the [Job] of the current coroutine is cancelled or completed while the suspending function is waiting, this
+ * function immediately disposes of its subscription and resumes with [CancellationException].
*/
public suspend fun <T> SingleSource<T>.await(): T = suspendCancellableCoroutine { cont ->
subscribe(object : SingleObserver<T> {
@@ -82,69 +130,73 @@ public suspend fun <T> SingleSource<T>.await(): T = suspendCancellableCoroutine
// ------------------------ ObservableSource ------------------------
/**
- * Awaits for the first value from the given observable without blocking a thread.
- * Returns the resulting value or throws the corresponding exception if this observable had produced error.
+ * Awaits the first value from the given [Observable] without blocking the thread and returns the resulting value, or,
+ * if the observable has produced an error, throws the corresponding exception.
*
* This suspending function is cancellable.
- * If the [Job] of the current coroutine is cancelled or completed while this suspending function is waiting, this function
- * immediately resumes with [CancellationException].
+ * If the [Job] of the current coroutine is cancelled or completed while the suspending function is waiting, this
+ * function immediately disposes of its subscription and resumes with [CancellationException].
*
- * @throws NoSuchElementException if observable does not emit any value
+ * @throws NoSuchElementException if the observable does not emit any value
*/
public suspend fun <T> ObservableSource<T>.awaitFirst(): T = awaitOne(Mode.FIRST)
/**
- * Awaits for the first value from the given observable or the [default] value if none is emitted without blocking a
- * thread and returns the resulting value or throws the corresponding exception if this observable had produced error.
+ * Awaits the first value from the given [Observable], or returns the [default] value if none is emitted, without
+ * blocking the thread, and returns the resulting value, or, if this observable has produced an error, throws the
+ * corresponding exception.
*
* This suspending function is cancellable.
- * If the [Job] of the current coroutine is cancelled or completed while this suspending function is waiting, this function
- * immediately resumes with [CancellationException].
+ * If the [Job] of the current coroutine is cancelled or completed while the suspending function is waiting, this
+ * function immediately disposes of its subscription and resumes with [CancellationException].
*/
public suspend fun <T> ObservableSource<T>.awaitFirstOrDefault(default: T): T = awaitOne(Mode.FIRST_OR_DEFAULT, default)
/**
- * Awaits for the first value from the given observable or `null` value if none is emitted without blocking a
- * thread and returns the resulting value or throws the corresponding exception if this observable had produced error.
+ * Awaits the first value from the given [Observable], or returns `null` if none is emitted, without blocking the
+ * thread, and returns the resulting value, or, if this observable has produced an error, throws the corresponding
+ * exception.
*
* This suspending function is cancellable.
- * If the [Job] of the current coroutine is cancelled or completed while this suspending function is waiting, this function
- * immediately resumes with [CancellationException].
+ * If the [Job] of the current coroutine is cancelled or completed while the suspending function is waiting, this
+ * function immediately disposes of its subscription and resumes with [CancellationException].
*/
public suspend fun <T> ObservableSource<T>.awaitFirstOrNull(): T? = awaitOne(Mode.FIRST_OR_DEFAULT)
/**
- * Awaits for the first value from the given observable or call [defaultValue] to get a value if none is emitted without blocking a
- * thread and returns the resulting value or throws the corresponding exception if this observable had produced error.
+ * Awaits the first value from the given [Observable], or calls [defaultValue] to get a value if none is emitted,
+ * without blocking the thread, and returns the resulting value, or, if this observable has produced an error, throws
+ * the corresponding exception.
*
* This suspending function is cancellable.
- * If the [Job] of the current coroutine is cancelled or completed while this suspending function is waiting, this function
- * immediately resumes with [CancellationException].
+ * If the [Job] of the current coroutine is cancelled or completed while the suspending function is waiting, this
+ * function immediately disposes of its subscription and resumes with [CancellationException].
*/
-public suspend fun <T> ObservableSource<T>.awaitFirstOrElse(defaultValue: () -> T): T = awaitOne(Mode.FIRST_OR_DEFAULT) ?: defaultValue()
+public suspend fun <T> ObservableSource<T>.awaitFirstOrElse(defaultValue: () -> T): T =
+ awaitOne(Mode.FIRST_OR_DEFAULT) ?: defaultValue()
/**
- * Awaits for the last value from the given observable without blocking a thread.
- * Returns the resulting value or throws the corresponding exception if this observable had produced error.
+ * Awaits the last value from the given [Observable] without blocking the thread and
+ * returns the resulting value, or, if this observable has produced an error, throws the corresponding exception.
*
* This suspending function is cancellable.
- * If the [Job] of the current coroutine is cancelled or completed while this suspending function is waiting, this function
- * immediately resumes with [CancellationException].
+ * If the [Job] of the current coroutine is cancelled or completed while the suspending function is waiting, this
+ * function immediately disposes of its subscription and resumes with [CancellationException].
*
- * @throws NoSuchElementException if observable does not emit any value
+ * @throws NoSuchElementException if the observable does not emit any value
*/
public suspend fun <T> ObservableSource<T>.awaitLast(): T = awaitOne(Mode.LAST)
/**
- * Awaits for the single value from the given observable without blocking a thread.
- * Returns the resulting value or throws the corresponding exception if this observable had produced error.
+ * Awaits the single value from the given observable without blocking the thread and returns the resulting value, or,
+ * if this observable has produced an error, throws the corresponding exception.
*
* This suspending function is cancellable.
- * If the [Job] of the current coroutine is cancelled or completed while this suspending function is waiting, this function
- * immediately resumes with [CancellationException].
+ * If the [Job] of the current coroutine is cancelled or completed while the suspending function is waiting, this
+ * function immediately disposes of its subscription and resumes with [CancellationException].
*
- * @throws NoSuchElementException if observable does not emit any value
- * @throws IllegalArgumentException if observable emits more than one value
+ * @throws NoSuchElementException if the observable does not emit any value
+ * @throws IllegalArgumentException if the observable emits more than one value
*/
public suspend fun <T> ObservableSource<T>.awaitSingle(): T = awaitOne(Mode.SINGLE)
@@ -218,3 +270,4 @@ private suspend fun <T> ObservableSource<T>.awaitOne(
}
})
}
+
diff --git a/reactive/kotlinx-coroutines-rx3/src/RxCancellable.kt b/reactive/kotlinx-coroutines-rx3/src/RxCancellable.kt
index 0b57b8bb..1017b112 100644
--- a/reactive/kotlinx-coroutines-rx3/src/RxCancellable.kt
+++ b/reactive/kotlinx-coroutines-rx3/src/RxCancellable.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.rx3
@@ -20,6 +20,7 @@ internal fun handleUndeliverableException(cause: Throwable, context: CoroutineCo
try {
RxJavaPlugins.onError(cause)
} catch (e: Throwable) {
+ cause.addSuppressed(e)
handleCoroutineException(context, cause)
}
}
diff --git a/reactive/kotlinx-coroutines-rx3/src/RxChannel.kt b/reactive/kotlinx-coroutines-rx3/src/RxChannel.kt
index 737cf671..21238d24 100644
--- a/reactive/kotlinx-coroutines-rx3/src/RxChannel.kt
+++ b/reactive/kotlinx-coroutines-rx3/src/RxChannel.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.rx3
@@ -9,6 +9,7 @@ import io.reactivex.rxjava3.disposables.*
import kotlinx.atomicfu.*
import kotlinx.coroutines.channels.*
import kotlinx.coroutines.internal.*
+import kotlinx.coroutines.flow.*
/**
* Subscribes to this [MaybeSource] and returns a channel to receive elements emitted by it.
@@ -40,14 +41,18 @@ internal fun <T> ObservableSource<T>.openSubscription(): ReceiveChannel<T> {
/**
* Subscribes to this [MaybeSource] and performs the specified action for each received element.
- * Cancels subscription if any exception happens during collect.
+ *
+ * If [action] throws an exception at some point or if the [MaybeSource] raises an error, the exception is rethrown from
+ * [collect].
*/
public suspend inline fun <T> MaybeSource<T>.collect(action: (T) -> Unit): Unit =
openSubscription().consumeEach(action)
/**
* Subscribes to this [ObservableSource] and performs the specified action for each received element.
- * Cancels subscription if any exception happens during collect.
+ *
+ * If [action] throws an exception at some point, the subscription is cancelled, and the exception is rethrown from
+ * [collect]. Also, if the [ObservableSource] signals an error, that error is rethrown from [collect].
*/
public suspend inline fun <T> ObservableSource<T>.collect(action: (T) -> Unit): Unit =
openSubscription().consumeEach(action)
@@ -69,11 +74,12 @@ private class SubscriptionChannel<T> :
}
override fun onSuccess(t: T) {
- offer(t)
+ trySend(t)
+ close(cause = null)
}
override fun onNext(t: T) {
- offer(t)
+ trySend(t) // Safe to ignore return value here, expectedly racing with cancellation
}
override fun onComplete() {
diff --git a/reactive/kotlinx-coroutines-rx3/src/RxCompletable.kt b/reactive/kotlinx-coroutines-rx3/src/RxCompletable.kt
index 54b412f1..47cc6ad3 100644
--- a/reactive/kotlinx-coroutines-rx3/src/RxCompletable.kt
+++ b/reactive/kotlinx-coroutines-rx3/src/RxCompletable.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.rx3
@@ -39,7 +39,7 @@ private fun rxCompletableInternal(
private class RxCompletableCoroutine(
parentContext: CoroutineContext,
private val subscriber: CompletableEmitter
-) : AbstractCoroutine<Unit>(parentContext, true) {
+) : AbstractCoroutine<Unit>(parentContext, false, true) {
override fun onCompleted(value: Unit) {
try {
subscriber.onComplete()
@@ -50,11 +50,12 @@ private class RxCompletableCoroutine(
override fun onCancelled(cause: Throwable, handled: Boolean) {
try {
- if (!subscriber.tryOnError(cause)) {
- handleUndeliverableException(cause, context)
+ if (subscriber.tryOnError(cause)) {
+ return
}
} catch (e: Throwable) {
- handleUndeliverableException(e, context)
+ cause.addSuppressed(e)
}
+ handleUndeliverableException(cause, context)
}
}
diff --git a/reactive/kotlinx-coroutines-rx3/src/RxConvert.kt b/reactive/kotlinx-coroutines-rx3/src/RxConvert.kt
index 0978423a..b4693a55 100644
--- a/reactive/kotlinx-coroutines-rx3/src/RxConvert.kt
+++ b/reactive/kotlinx-coroutines-rx3/src/RxConvert.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.rx3
@@ -26,7 +26,6 @@ import kotlin.coroutines.*
*
* @param context -- the coroutine context from which the resulting completable is going to be signalled
*/
-@ExperimentalCoroutinesApi
public fun Job.asCompletable(context: CoroutineContext): Completable = rxCompletable(context) {
this@asCompletable.join()
}
@@ -43,7 +42,6 @@ public fun Job.asCompletable(context: CoroutineContext): Completable = rxComplet
*
* @param context -- the coroutine context from which the resulting maybe is going to be signalled
*/
-@ExperimentalCoroutinesApi
public fun <T> Deferred<T?>.asMaybe(context: CoroutineContext): Maybe<T> = rxMaybe(context) {
this@asMaybe.await()
}
@@ -60,7 +58,6 @@ public fun <T> Deferred<T?>.asMaybe(context: CoroutineContext): Maybe<T> = rxMay
*
* @param context -- the coroutine context from which the resulting single is going to be signalled
*/
-@ExperimentalCoroutinesApi
public fun <T : Any> Deferred<T>.asSingle(context: CoroutineContext): Single<T> = rxSingle(context) {
this@asSingle.await()
}
@@ -75,17 +72,20 @@ public fun <T : Any> Deferred<T>.asSingle(context: CoroutineContext): Single<T>
* resulting flow to specify a user-defined value and to control what happens when data is produced faster
* than consumed, i.e. to control the back-pressure behavior. Check [callbackFlow] for more details.
*/
-@ExperimentalCoroutinesApi
public fun <T: Any> ObservableSource<T>.asFlow(): Flow<T> = callbackFlow {
val disposableRef = AtomicReference<Disposable>()
val observer = object : Observer<T> {
override fun onComplete() { close() }
override fun onSubscribe(d: Disposable) { if (!disposableRef.compareAndSet(null, d)) d.dispose() }
override fun onNext(t: T) {
+ /*
+ * Channel was closed by the downstream, so the exception (if any)
+ * also was handled by the same downstream
+ */
try {
- sendBlocking(t)
- } catch (ignored: Throwable) { // TODO: Replace when this issue is fixed: https://github.com/Kotlin/kotlinx.coroutines/issues/974
- // Is handled by the downstream flow
+ trySendBlocking(t)
+ } catch (e: InterruptedException) {
+ // RxJava interrupts the source
}
}
override fun onError(e: Throwable) { close(e) }
@@ -104,7 +104,6 @@ public fun <T: Any> ObservableSource<T>.asFlow(): Flow<T> = callbackFlow {
* inject additional context into the caller thread. By default, the [Unconfined][Dispatchers.Unconfined] dispatcher
* is used, so calls are performed from an arbitrary thread.
*/
-@ExperimentalCoroutinesApi
public fun <T: Any> Flow<T>.asObservable(context: CoroutineContext = EmptyCoroutineContext) : Observable<T> = Observable.create { emitter ->
/*
* ATOMIC is used here to provide stable behaviour of subscribe+dispose pair even if
@@ -137,10 +136,10 @@ public fun <T: Any> Flow<T>.asObservable(context: CoroutineContext = EmptyCorout
* inject additional context into the caller thread. By default, the [Unconfined][Dispatchers.Unconfined] dispatcher
* is used, so calls are performed from an arbitrary thread.
*/
-@ExperimentalCoroutinesApi
public fun <T: Any> Flow<T>.asFlowable(context: CoroutineContext = EmptyCoroutineContext): Flowable<T> =
Flowable.fromPublisher(asPublisher(context))
+/** @suppress */
@Suppress("UNUSED") // KT-42513
@JvmOverloads // binary compatibility
@JvmName("from")
@@ -148,6 +147,7 @@ public fun <T: Any> Flow<T>.asFlowable(context: CoroutineContext = EmptyCoroutin
public fun <T: Any> Flow<T>._asFlowable(context: CoroutineContext = EmptyCoroutineContext): Flowable<T> =
asFlowable(context)
+/** @suppress */
@Suppress("UNUSED") // KT-42513
@JvmOverloads // binary compatibility
@JvmName("from")
diff --git a/reactive/kotlinx-coroutines-rx3/src/RxFlowable.kt b/reactive/kotlinx-coroutines-rx3/src/RxFlowable.kt
index 2de46a6a..9357f283 100644
--- a/reactive/kotlinx-coroutines-rx3/src/RxFlowable.kt
+++ b/reactive/kotlinx-coroutines-rx3/src/RxFlowable.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.rx3
@@ -28,7 +28,6 @@ import kotlin.coroutines.*
*
* **Note: This is an experimental api.** Behaviour of publishers that work as children in a parent scope with respect
*/
-@ExperimentalCoroutinesApi
public fun <T: Any> rxFlowable(
context: CoroutineContext = EmptyCoroutineContext,
@BuilderInference block: suspend ProducerScope<T>.() -> Unit
diff --git a/reactive/kotlinx-coroutines-rx3/src/RxMaybe.kt b/reactive/kotlinx-coroutines-rx3/src/RxMaybe.kt
index 4d55ef5f..12d0197b 100644
--- a/reactive/kotlinx-coroutines-rx3/src/RxMaybe.kt
+++ b/reactive/kotlinx-coroutines-rx3/src/RxMaybe.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.rx3
@@ -40,7 +40,7 @@ private fun <T> rxMaybeInternal(
private class RxMaybeCoroutine<T>(
parentContext: CoroutineContext,
private val subscriber: MaybeEmitter<T>
-) : AbstractCoroutine<T>(parentContext, true) {
+) : AbstractCoroutine<T>(parentContext, false, true) {
override fun onCompleted(value: T) {
try {
if (value == null) subscriber.onComplete() else subscriber.onSuccess(value)
@@ -51,11 +51,12 @@ private class RxMaybeCoroutine<T>(
override fun onCancelled(cause: Throwable, handled: Boolean) {
try {
- if (!subscriber.tryOnError(cause)) {
- handleUndeliverableException(cause, context)
+ if (subscriber.tryOnError(cause)) {
+ return
}
} catch (e: Throwable) {
- handleUndeliverableException(e, context)
+ cause.addSuppressed(e)
}
+ handleUndeliverableException(cause, context)
}
}
diff --git a/reactive/kotlinx-coroutines-rx3/src/RxObservable.kt b/reactive/kotlinx-coroutines-rx3/src/RxObservable.kt
index 102d06ea..57007bbd 100644
--- a/reactive/kotlinx-coroutines-rx3/src/RxObservable.kt
+++ b/reactive/kotlinx-coroutines-rx3/src/RxObservable.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.rx3
@@ -12,6 +12,7 @@ import kotlinx.coroutines.channels.*
import kotlinx.coroutines.selects.*
import kotlinx.coroutines.sync.*
import kotlin.coroutines.*
+import kotlinx.coroutines.internal.*
/**
* Creates cold [observable][Observable] that will run a given [block] in a coroutine.
@@ -29,7 +30,6 @@ import kotlin.coroutines.*
* If the context does not have any dispatcher nor any other [ContinuationInterceptor], then [Dispatchers.Default] is used.
* Method throws [IllegalArgumentException] if provided [context] contains a [Job] instance.
*/
-@ExperimentalCoroutinesApi
public fun <T : Any> rxObservable(
context: CoroutineContext = EmptyCoroutineContext,
@BuilderInference block: suspend ProducerScope<T>.() -> Unit
@@ -54,39 +54,35 @@ private const val OPEN = 0 // open channel, still working
private const val CLOSED = -1 // closed, but have not signalled onCompleted/onError yet
private const val SIGNALLED = -2 // already signalled subscriber onCompleted/onError
-private class RxObservableCoroutine<T: Any>(
+private class RxObservableCoroutine<T : Any>(
parentContext: CoroutineContext,
private val subscriber: ObservableEmitter<T>
-) : AbstractCoroutine<Unit>(parentContext, true), ProducerScope<T>, SelectClause2<T, SendChannel<T>> {
+) : AbstractCoroutine<Unit>(parentContext, false, true), ProducerScope<T>, SelectClause2<T, SendChannel<T>> {
override val channel: SendChannel<T> get() = this
- // Mutex is locked when while subscriber.onXXX is being invoked
+ // Mutex is locked while subscriber.onXXX is being invoked
private val mutex = Mutex()
private val _signal = atomic(OPEN)
- override val isClosedForSend: Boolean get() = isCompleted
- override val isFull: Boolean = mutex.isLocked
+ override val isClosedForSend: Boolean get() = !isActive
override fun close(cause: Throwable?): Boolean = cancelCoroutine(cause)
override fun invokeOnClose(handler: (Throwable?) -> Unit) =
throw UnsupportedOperationException("RxObservableCoroutine doesn't support invokeOnClose")
- override fun offer(element: T): Boolean {
- if (!mutex.tryLock()) return false
- doLockedNext(element)
- return true
- }
+ override fun trySend(element: T): ChannelResult<Unit> =
+ if (!mutex.tryLock()) {
+ ChannelResult.failure()
+ } else {
+ when (val throwable = doLockedNext(element)) {
+ null -> ChannelResult.success(Unit)
+ else -> ChannelResult.closed(throwable)
+ }
+ }
public override suspend fun send(element: T) {
- // fast-path -- try send without suspension
- if (offer(element)) return
- // slow-path does suspend
- return sendSuspend(element)
- }
-
- private suspend fun sendSuspend(element: T) {
mutex.lock()
- doLockedNext(element)
+ doLockedNext(element)?.let { throw it }
}
override val onSend: SelectClause2<T, SendChannel<T>>
@@ -94,30 +90,39 @@ private class RxObservableCoroutine<T: Any>(
// registerSelectSend
@Suppress("PARAMETER_NAME_CHANGED_ON_OVERRIDE")
- override fun <R> registerSelectClause2(select: SelectInstance<R>, element: T, block: suspend (SendChannel<T>) -> R) {
+ override fun <R> registerSelectClause2(
+ select: SelectInstance<R>,
+ element: T,
+ block: suspend (SendChannel<T>) -> R
+ ) {
mutex.onLock.registerSelectClause2(select, null) {
- doLockedNext(element)
+ doLockedNext(element)?.let { throw it }
block(this)
}
}
// assert: mutex.isLocked()
- private fun doLockedNext(elem: T) {
+ private fun doLockedNext(elem: T): Throwable? {
// check if already closed for send
if (!isActive) {
doLockedSignalCompleted(completionCause, completionCauseHandled)
- throw getCancellationException()
+ return getCancellationException()
}
// notify subscriber
try {
subscriber.onNext(elem)
} catch (e: Throwable) {
- // If onNext fails with exception, then we cancel coroutine (with this exception) and then rethrow it
- // to abort the corresponding send/offer invocation. From the standpoint of coroutines machinery,
- // this failure is essentially equivalent to a failure of a child coroutine.
- cancelCoroutine(e)
- mutex.unlock()
- throw e
+ val cause = UndeliverableException(e)
+ val causeDelivered = close(cause)
+ unlockAndCheckCompleted()
+ return if (causeDelivered) {
+ // `cause` is the reason this channel is closed
+ cause
+ } else {
+ // Someone else closed the channel during `onNext`. We report `cause` as an undeliverable exception.
+ handleUndeliverableException(cause, context)
+ getCancellationException()
+ }
}
/*
* There is no sense to check for `isActive` before doing `unlock`, because cancellation/completion might
@@ -126,6 +131,7 @@ private class RxObservableCoroutine<T: Any>(
* We have to recheck `isCompleted` after `unlock` anyway.
*/
unlockAndCheckCompleted()
+ return null
}
private fun unlockAndCheckCompleted() {
@@ -139,33 +145,31 @@ private class RxObservableCoroutine<T: Any>(
private fun doLockedSignalCompleted(cause: Throwable?, handled: Boolean) {
// cancellation failures
try {
- if (_signal.value >= CLOSED) {
- _signal.value = SIGNALLED // we'll signal onError/onCompleted (that the final state -- no CAS needed)
+ if (_signal.value == SIGNALLED)
+ return
+ _signal.value = SIGNALLED // we'll signal onError/onCompleted (that the final state -- no CAS needed)
+ @Suppress("INVISIBLE_MEMBER")
+ val unwrappedCause = cause?.let { unwrap(it) }
+ if (unwrappedCause == null) {
try {
- if (cause != null && cause !is CancellationException) {
- /*
- * Reactive frameworks have two types of exceptions: regular and fatal.
- * Regular are passed to onError.
- * Fatal can be passed to onError, but even the standard implementations **can just swallow it** (e.g. see #1297).
- * Such behaviour is inconsistent, leads to silent failures and we can't possibly know whether
- * the cause will be handled by onError (and moreover, it depends on whether a fatal exception was
- * thrown by subscriber or upstream).
- * To make behaviour consistent and least surprising, we always handle fatal exceptions
- * by coroutines machinery, anyway, they should not be present in regular program flow,
- * thus our goal here is just to expose it as soon as possible.
- */
- subscriber.tryOnError(cause)
- if (!handled && cause.isFatal()) {
- handleUndeliverableException(cause, context)
- }
- }
- else {
- subscriber.onComplete()
- }
- } catch (e: Throwable) {
- // Unhandled exception (cannot handle in other way, since we are already complete)
+ subscriber.onComplete()
+ } catch (e: Exception) {
handleUndeliverableException(e, context)
}
+ } else if (unwrappedCause is UndeliverableException && !handled) {
+ /** Such exceptions are not reported to `onError`, as, according to the reactive specifications,
+ * exceptions thrown from the Subscriber methods must be treated as if the Subscriber was already
+ * cancelled. */
+ handleUndeliverableException(cause, context)
+ } else if (unwrappedCause !== getCancellationException() || !subscriber.isDisposed) {
+ try {
+ /** If the subscriber is already in a terminal state, the error will be signalled to
+ * `RxJavaPlugins.onError`. */
+ subscriber.onError(cause)
+ } catch (e: Exception) {
+ cause.addSuppressed(e)
+ handleUndeliverableException(cause, context)
+ }
}
} finally {
mutex.unlock()
@@ -187,9 +191,3 @@ private class RxObservableCoroutine<T: Any>(
}
}
-internal fun Throwable.isFatal() = try {
- Exceptions.throwIfFatal(this) // Rx-consistent behaviour without hardcode
- false
-} catch (e: Throwable) {
- true
-}
diff --git a/reactive/kotlinx-coroutines-rx3/src/RxScheduler.kt b/reactive/kotlinx-coroutines-rx3/src/RxScheduler.kt
index a426aea6..24c3f118 100644
--- a/reactive/kotlinx-coroutines-rx3/src/RxScheduler.kt
+++ b/reactive/kotlinx-coroutines-rx3/src/RxScheduler.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.rx3
diff --git a/reactive/kotlinx-coroutines-rx3/src/RxSingle.kt b/reactive/kotlinx-coroutines-rx3/src/RxSingle.kt
index 225df93a..e7678f0d 100644
--- a/reactive/kotlinx-coroutines-rx3/src/RxSingle.kt
+++ b/reactive/kotlinx-coroutines-rx3/src/RxSingle.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.rx3
@@ -39,7 +39,7 @@ private fun <T : Any> rxSingleInternal(
private class RxSingleCoroutine<T: Any>(
parentContext: CoroutineContext,
private val subscriber: SingleEmitter<T>
-) : AbstractCoroutine<T>(parentContext, true) {
+) : AbstractCoroutine<T>(parentContext, false, true) {
override fun onCompleted(value: T) {
try {
subscriber.onSuccess(value)
@@ -50,11 +50,12 @@ private class RxSingleCoroutine<T: Any>(
override fun onCancelled(cause: Throwable, handled: Boolean) {
try {
- if (!subscriber.tryOnError(cause)) {
- handleUndeliverableException(cause, context)
+ if (subscriber.tryOnError(cause)) {
+ return
}
} catch (e: Throwable) {
- handleUndeliverableException(e, context)
+ cause.addSuppressed(e)
}
+ handleUndeliverableException(cause, context)
}
}
diff --git a/reactive/kotlinx-coroutines-rx3/test/CompletableTest.kt b/reactive/kotlinx-coroutines-rx3/test/CompletableTest.kt
index e5399d16..cfdb6d41 100644
--- a/reactive/kotlinx-coroutines-rx3/test/CompletableTest.kt
+++ b/reactive/kotlinx-coroutines-rx3/test/CompletableTest.kt
@@ -98,6 +98,31 @@ class CompletableTest : TestBase() {
}
}
+ /** Tests that calls to [await] throw [CancellationException] and dispose of the subscription when their [Job] is
+ * cancelled. */
+ @Test
+ fun testAwaitCancellation() = runTest {
+ expect(1)
+ val completable = CompletableSource { s ->
+ s.onSubscribe(object: Disposable {
+ override fun dispose() { expect(4) }
+ override fun isDisposed(): Boolean { expectUnreached(); return false }
+ })
+ }
+ val job = launch(start = CoroutineStart.UNDISPATCHED) {
+ try {
+ expect(2)
+ completable.await()
+ } catch (e: CancellationException) {
+ expect(5)
+ throw e
+ }
+ }
+ expect(3)
+ job.cancelAndJoin()
+ finish(6)
+ }
+
@Test
fun testSuppressedException() = runTest {
val completable = rxCompletable(currentDispatcher()) {
@@ -119,7 +144,7 @@ class CompletableTest : TestBase() {
}
@Test
- fun testUnhandledException() = runTest() {
+ fun testUnhandledException() = runTest {
expect(1)
var disposable: Disposable? = null
val handler = { e: Throwable ->
@@ -165,8 +190,7 @@ class CompletableTest : TestBase() {
withExceptionHandler(handler) {
rxCompletable(Dispatchers.Unconfined) {
expect(1)
- 42
- }.subscribe({ throw LinkageError() })
+ }.subscribe { throw LinkageError() }
finish(3)
}
}
diff --git a/reactive/kotlinx-coroutines-rx3/test/FlowableExceptionHandlingTest.kt b/reactive/kotlinx-coroutines-rx3/test/FlowableExceptionHandlingTest.kt
index 8cbd7ee8..126cb818 100644
--- a/reactive/kotlinx-coroutines-rx3/test/FlowableExceptionHandlingTest.kt
+++ b/reactive/kotlinx-coroutines-rx3/test/FlowableExceptionHandlingTest.kt
@@ -38,16 +38,16 @@ class FlowableExceptionHandlingTest : TestBase() {
}
@Test
- fun testFatalException() = withExceptionHandler(handler<LinkageError>(3)) {
+ fun testFatalException() = withExceptionHandler({ expectUnreached() }) {
rxFlowable<Int>(Dispatchers.Unconfined) {
expect(1)
throw LinkageError()
}.subscribe({
expectUnreached()
}, {
- expect(2) // Fatal exception is reported to both onError and CEH
+ expect(2) // Fatal exceptions are not treated as special
})
- finish(4)
+ finish(3)
}
@Test
@@ -66,7 +66,7 @@ class FlowableExceptionHandlingTest : TestBase() {
}
@Test
- fun testFatalExceptionAsynchronous() = withExceptionHandler(handler<LinkageError>(3)) {
+ fun testFatalExceptionAsynchronous() = withExceptionHandler({ expectUnreached() }) {
rxFlowable<Int>(Dispatchers.Unconfined) {
expect(1)
throw LinkageError()
@@ -77,19 +77,19 @@ class FlowableExceptionHandlingTest : TestBase() {
}, {
expect(2)
})
- finish(4)
+ finish(3)
}
@Test
- fun testFatalExceptionFromSubscribe() = withExceptionHandler(handler<LinkageError>(4)) {
+ fun testFatalExceptionFromSubscribe() = withExceptionHandler(handler<LinkageError>(3)) {
rxFlowable(Dispatchers.Unconfined) {
expect(1)
send(Unit)
}.subscribe({
expect(2)
throw LinkageError()
- }, { expect(3) }) // Fatal exception is reported to both onError and CEH
- finish(5)
+ }, { expectUnreached() }) // Fatal exception is rethrown from `onNext` => the subscription is thought to be cancelled
+ finish(4)
}
@Test
diff --git a/reactive/kotlinx-coroutines-rx3/test/IntegrationTest.kt b/reactive/kotlinx-coroutines-rx3/test/IntegrationTest.kt
index 395672ce..1302124f 100644
--- a/reactive/kotlinx-coroutines-rx3/test/IntegrationTest.kt
+++ b/reactive/kotlinx-coroutines-rx3/test/IntegrationTest.kt
@@ -125,6 +125,21 @@ class IntegrationTest(
finish(3)
}
+ @Test
+ fun testObservableWithTimeout() = runTest {
+ val observable = rxObservable<Int> {
+ expect(2)
+ withTimeout(1) { delay(100) }
+ }
+ try {
+ expect(1)
+ observable.awaitFirstOrNull()
+ } catch (e: CancellationException) {
+ expect(3)
+ }
+ finish(4)
+ }
+
private suspend fun checkNumbers(n: Int, observable: Observable<Int>) {
var last = 0
observable.collect {
diff --git a/reactive/kotlinx-coroutines-rx3/test/MaybeTest.kt b/reactive/kotlinx-coroutines-rx3/test/MaybeTest.kt
index e0cec748..bea939ef 100644
--- a/reactive/kotlinx-coroutines-rx3/test/MaybeTest.kt
+++ b/reactive/kotlinx-coroutines-rx3/test/MaybeTest.kt
@@ -7,13 +7,12 @@ package kotlinx.coroutines.rx3
import io.reactivex.rxjava3.core.*
import io.reactivex.rxjava3.disposables.*
import io.reactivex.rxjava3.exceptions.*
-import io.reactivex.rxjava3.functions.*
import io.reactivex.rxjava3.internal.functions.Functions.*
import kotlinx.coroutines.*
+import kotlinx.coroutines.CancellationException
import org.junit.*
import org.junit.Test
import java.util.concurrent.*
-import java.util.concurrent.CancellationException
import kotlin.test.*
class MaybeTest : TestBase() {
@@ -47,7 +46,7 @@ class MaybeTest : TestBase() {
null
}
expect(2)
- maybe.subscribe (emptyConsumer(), ON_ERROR_MISSING, Action {
+ maybe.subscribe (emptyConsumer(), ON_ERROR_MISSING, {
expect(5)
})
expect(3)
@@ -85,7 +84,7 @@ class MaybeTest : TestBase() {
expectUnreached()
}
expect(2)
- // nothing is called on a disposed rx3 maybe
+ // nothing is called on a disposed rx2 maybe
val sub = maybe.subscribe({
expectUnreached()
}, {
@@ -112,18 +111,45 @@ class MaybeTest : TestBase() {
@Test
fun testMaybeAwait() = runBlocking {
- assertEquals("OK", Maybe.just("O").await() + "K")
+ assertEquals("OK", Maybe.just("O").awaitSingleOrNull() + "K")
+ assertEquals("OK", Maybe.just("O").awaitSingle() + "K")
}
@Test
- fun testMaybeAwaitForNull() = runBlocking {
- assertNull(Maybe.empty<String>().await())
+ fun testMaybeAwaitForNull(): Unit = runBlocking {
+ assertNull(Maybe.empty<String>().awaitSingleOrNull())
+ assertFailsWith<NoSuchElementException> { Maybe.empty<String>().awaitSingle() }
+ }
+
+ /** Tests that calls to [awaitSingleOrNull] throw [CancellationException] and dispose of the subscription when their
+ * [Job] is cancelled. */
+ @Test
+ fun testMaybeAwaitCancellation() = runTest {
+ expect(1)
+ val maybe = MaybeSource<Int> { s ->
+ s.onSubscribe(object: Disposable {
+ override fun dispose() { expect(4) }
+ override fun isDisposed(): Boolean { expectUnreached(); return false }
+ })
+ }
+ val job = launch(start = CoroutineStart.UNDISPATCHED) {
+ try {
+ expect(2)
+ maybe.awaitSingleOrNull()
+ } catch (e: CancellationException) {
+ expect(5)
+ throw e
+ }
+ }
+ expect(3)
+ job.cancelAndJoin()
+ finish(6)
}
@Test
fun testMaybeEmitAndAwait() {
val maybe = rxMaybe {
- Maybe.just("O").await() + "K"
+ Maybe.just("O").awaitSingleOrNull() + "K"
}
checkMaybeValue(maybe) {
@@ -205,7 +231,7 @@ class MaybeTest : TestBase() {
@Test
fun testCancelledConsumer() = runTest {
expect(1)
- val maybe = rxMaybe<Int>(currentDispatcher()) {
+ val maybe = rxMaybe(currentDispatcher()) {
expect(4)
try {
delay(Long.MAX_VALUE)
@@ -228,6 +254,56 @@ class MaybeTest : TestBase() {
finish(7)
}
+ /** Tests the simple scenario where the Maybe doesn't output a value. */
+ @Test
+ fun testMaybeCollectEmpty() = runTest {
+ expect(1)
+ Maybe.empty<Int>().collect {
+ expectUnreached()
+ }
+ finish(2)
+ }
+
+ /** Tests the simple scenario where the Maybe doesn't output a value. */
+ @Test
+ fun testMaybeCollectSingle() = runTest {
+ expect(1)
+ Maybe.just("OK").collect {
+ assertEquals("OK", it)
+ expect(2)
+ }
+ finish(3)
+ }
+
+ /** Tests the behavior of [collect] when the Maybe raises an error. */
+ @Test
+ fun testMaybeCollectThrowingMaybe() = runTest {
+ expect(1)
+ try {
+ Maybe.error<Int>(TestException()).collect {
+ expectUnreached()
+ }
+ } catch (e: TestException) {
+ expect(2)
+ }
+ finish(3)
+ }
+
+ /** Tests the behavior of [collect] when the action throws. */
+ @Test
+ fun testMaybeCollectThrowingAction() = runTest {
+ expect(1)
+ try {
+ Maybe.just("OK").collect {
+ expect(2)
+ throw TestException()
+ }
+ } catch (e: TestException) {
+ expect(3)
+ }
+ finish(4)
+ }
+
@Test
fun testSuppressedException() = runTest {
val maybe = rxMaybe(currentDispatcher()) {
@@ -241,7 +317,7 @@ class MaybeTest : TestBase() {
}
}
try {
- maybe.await()
+ maybe.awaitSingleOrNull()
expectUnreached()
} catch (e: TestException) {
assertTrue(e.suppressed[0] is TestException2)
@@ -301,7 +377,7 @@ class MaybeTest : TestBase() {
rxMaybe(Dispatchers.Unconfined) {
expect(1)
42
- }.subscribe({ throw LinkageError() })
+ }.subscribe { throw LinkageError() }
finish(3)
}
}
diff --git a/reactive/kotlinx-coroutines-rx3/test/ObservableCollectTest.kt b/reactive/kotlinx-coroutines-rx3/test/ObservableCollectTest.kt
new file mode 100644
index 00000000..680786f9
--- /dev/null
+++ b/reactive/kotlinx-coroutines-rx3/test/ObservableCollectTest.kt
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.rx3
+
+import io.reactivex.rxjava3.core.ObservableSource
+import io.reactivex.rxjava3.disposables.*
+import kotlinx.coroutines.*
+import org.junit.Test
+import kotlin.test.*
+
+class ObservableCollectTest: TestBase() {
+
+ /** Tests the behavior of [collect] when the publisher raises an error. */
+ @Test
+ fun testObservableCollectThrowingObservable() = runTest {
+ expect(1)
+ var sum = 0
+ try {
+ rxObservable {
+ for (i in 0..100) {
+ send(i)
+ }
+ throw TestException()
+ }.collect {
+ sum += it
+ }
+ } catch (e: TestException) {
+ assertTrue(sum > 0)
+ finish(2)
+ }
+ }
+
+ @Test
+ fun testObservableCollectThrowingAction() = runTest {
+ expect(1)
+ var sum = 0
+ val expectedSum = 5
+ try {
+ var disposed = false
+ ObservableSource<Int> { observer ->
+ launch(Dispatchers.Default) {
+ observer.onSubscribe(object : Disposable {
+ override fun dispose() {
+ disposed = true
+ expect(expectedSum + 2)
+ }
+
+ override fun isDisposed(): Boolean = disposed
+ })
+ while (!disposed) {
+ observer.onNext(1)
+ }
+ }
+ }.collect {
+ expect(sum + 2)
+ sum += it
+ if (sum == expectedSum) {
+ throw TestException()
+ }
+ }
+ } catch (e: TestException) {
+ assertEquals(expectedSum, sum)
+ finish(expectedSum + 3)
+ }
+ }
+} \ No newline at end of file
diff --git a/reactive/kotlinx-coroutines-rx3/test/ObservableExceptionHandlingTest.kt b/reactive/kotlinx-coroutines-rx3/test/ObservableExceptionHandlingTest.kt
index 1183b2ae..5ddb36ed 100644
--- a/reactive/kotlinx-coroutines-rx3/test/ObservableExceptionHandlingTest.kt
+++ b/reactive/kotlinx-coroutines-rx3/test/ObservableExceptionHandlingTest.kt
@@ -8,6 +8,7 @@ import io.reactivex.rxjava3.exceptions.*
import kotlinx.coroutines.*
import org.junit.*
import org.junit.Test
+import java.util.concurrent.*
import kotlin.test.*
class ObservableExceptionHandlingTest : TestBase() {
@@ -18,7 +19,7 @@ class ObservableExceptionHandlingTest : TestBase() {
}
private inline fun <reified T : Throwable> handler(expect: Int) = { t: Throwable ->
- assertTrue(t is UndeliverableException && t.cause is T)
+ assertTrue(t is UndeliverableException && t.cause is T, "$t")
expect(expect)
}
@@ -38,8 +39,8 @@ class ObservableExceptionHandlingTest : TestBase() {
}
@Test
- fun testFatalException() = withExceptionHandler(handler<LinkageError>(3)) {
- rxObservable<Int>(Dispatchers.Unconfined) {
+ fun testFatalException() = withExceptionHandler({ expectUnreached() }) {
+ rxObservable<Int>(Dispatchers.Unconfined + cehUnreached()) {
expect(1)
throw LinkageError()
}.subscribe({
@@ -47,7 +48,7 @@ class ObservableExceptionHandlingTest : TestBase() {
}, {
expect(2)
})
- finish(4)
+ finish(3)
}
@Test
@@ -66,7 +67,7 @@ class ObservableExceptionHandlingTest : TestBase() {
}
@Test
- fun testFatalExceptionAsynchronous() = withExceptionHandler(handler<LinkageError>(3)) {
+ fun testFatalExceptionAsynchronous() = withExceptionHandler({ expectUnreached() }) {
rxObservable<Int>(Dispatchers.Unconfined) {
expect(1)
throw LinkageError()
@@ -75,20 +76,28 @@ class ObservableExceptionHandlingTest : TestBase() {
.subscribe({
expectUnreached()
}, {
- expect(2) // Fatal exception is not reported in onError
+ expect(2) // Fatal exceptions are not treated in a special manner
})
- finish(4)
+ finish(3)
}
@Test
- fun testFatalExceptionFromSubscribe() = withExceptionHandler(handler<LinkageError>(4)) {
+ fun testFatalExceptionFromSubscribe() = withExceptionHandler(handler<LinkageError>(3)) {
+ val latch = CountDownLatch(1)
rxObservable(Dispatchers.Unconfined) {
expect(1)
- send(Unit)
+ val result = trySend(Unit)
+ val exception = result.exceptionOrNull()
+ assertTrue(exception is UndeliverableException)
+ assertTrue(exception.cause is LinkageError)
+ assertTrue(isClosedForSend)
+ expect(4)
+ latch.countDown()
}.subscribe({
expect(2)
throw LinkageError()
- }, { expect(3) }) // Unreached because fatal errors are rethrown
+ }, { expectUnreached() }) // Unreached because RxJava bubbles up fatal exceptions, causing `onNext` to throw.
+ latch.await()
finish(5)
}
@@ -100,7 +109,7 @@ class ObservableExceptionHandlingTest : TestBase() {
}.subscribe({
expect(2)
throw TestException()
- }, { expect(3) }) // not reported to onError because came from the subscribe itself
+ }, { expect(3) })
finish(4)
}
@@ -119,7 +128,7 @@ class ObservableExceptionHandlingTest : TestBase() {
}
@Test
- fun testAsynchronousFatalExceptionFromSubscribe() = withExceptionHandler(handler<LinkageError>(4)) {
+ fun testAsynchronousFatalExceptionFromSubscribe() = withExceptionHandler(handler<LinkageError>(3)) {
rxObservable(Dispatchers.Unconfined) {
expect(1)
send(Unit)
@@ -128,7 +137,7 @@ class ObservableExceptionHandlingTest : TestBase() {
.subscribe({
expect(2)
throw LinkageError()
- }, { expect(3) })
- finish(5)
+ }, { expectUnreached() }) // Unreached because RxJava bubbles up fatal exceptions, causing `onNext` to throw.
+ finish(4)
}
}
diff --git a/reactive/kotlinx-coroutines-rx3/test/ObservableSingleTest.kt b/reactive/kotlinx-coroutines-rx3/test/ObservableSingleTest.kt
index 2a3ce046..692f0144 100644
--- a/reactive/kotlinx-coroutines-rx3/test/ObservableSingleTest.kt
+++ b/reactive/kotlinx-coroutines-rx3/test/ObservableSingleTest.kt
@@ -5,7 +5,9 @@
package kotlinx.coroutines.rx3
import io.reactivex.rxjava3.core.*
+import io.reactivex.rxjava3.disposables.*
import kotlinx.coroutines.*
+import kotlinx.coroutines.CancellationException
import org.junit.*
import org.junit.Test
import java.util.concurrent.*
@@ -101,7 +103,7 @@ class ObservableSingleTest : TestBase() {
@Test
fun testAwaitFirstOrNull() {
- val observable = rxObservable<String> {
+ val observable = rxObservable {
send(Observable.empty<String>().awaitFirstOrNull() ?: "OK")
}
@@ -154,6 +156,32 @@ class ObservableSingleTest : TestBase() {
}
}
+ /** Tests that calls to [awaitFirst] (and, thus, the other methods) throw [CancellationException] and dispose of
+ * the subscription when their [Job] is cancelled. */
+ @Test
+ fun testAwaitCancellation() = runTest {
+ expect(1)
+ val observable = ObservableSource<Int> { s ->
+ s.onSubscribe(object: Disposable {
+ override fun dispose() { expect(4) }
+ override fun isDisposed(): Boolean { expectUnreached(); return false }
+ })
+ }
+ val job = launch(start = CoroutineStart.UNDISPATCHED) {
+ try {
+ expect(2)
+ observable.awaitFirst()
+ } catch (e: CancellationException) {
+ expect(5)
+ throw e
+ }
+ }
+ expect(3)
+ job.cancelAndJoin()
+ finish(6)
+ }
+
+
@Test
fun testExceptionFromObservable() {
val observable = rxObservable {
diff --git a/reactive/kotlinx-coroutines-rx3/test/ObservableSourceAsFlowStressTest.kt b/reactive/kotlinx-coroutines-rx3/test/ObservableSourceAsFlowStressTest.kt
index 431a7a78..01d6a20e 100644
--- a/reactive/kotlinx-coroutines-rx3/test/ObservableSourceAsFlowStressTest.kt
+++ b/reactive/kotlinx-coroutines-rx3/test/ObservableSourceAsFlowStressTest.kt
@@ -27,7 +27,7 @@ class ObservableSourceAsFlowStressTest : TestBase() {
val latch = Channel<Unit>(1)
var i = 0
val observable = Observable.interval(100L, TimeUnit.MICROSECONDS)
- .doOnNext { if (++i > 100) latch.offer(Unit) }
+ .doOnNext { if (++i > 100) latch.trySend(Unit) }
val job = observable.asFlow().launchIn(CoroutineScope(Dispatchers.Default))
latch.receive()
job.cancelAndJoin()
diff --git a/reactive/kotlinx-coroutines-rx3/test/ObservableSubscriptionSelectTest.kt b/reactive/kotlinx-coroutines-rx3/test/ObservableSubscriptionSelectTest.kt
index 2f043161..58a54616 100644
--- a/reactive/kotlinx-coroutines-rx3/test/ObservableSubscriptionSelectTest.kt
+++ b/reactive/kotlinx-coroutines-rx3/test/ObservableSubscriptionSelectTest.kt
@@ -1,12 +1,14 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.rx3
import kotlinx.coroutines.*
+import kotlinx.coroutines.channels.*
import kotlinx.coroutines.selects.*
import org.junit.Test
+import kotlin.onSuccess
import kotlin.test.*
class ObservableSubscriptionSelectTest : TestBase() {
@@ -22,23 +24,23 @@ class ObservableSubscriptionSelectTest : TestBase() {
val channelB = source.openSubscription()
loop@ while (true) {
val done: Int = select {
- channelA.onReceiveOrNull {
- if (it != null) assertEquals(a++, it)
- if (it == null) 0 else 1
+ channelA.onReceiveCatching { result ->
+ result.onSuccess { assertEquals(a++, it) }
+ if (result.isSuccess) 1 else 0
}
- channelB.onReceiveOrNull {
- if (it != null) assertEquals(b++, it)
- if (it == null) 0 else 2
+ channelB.onReceiveCatching { result ->
+ result.onSuccess { assertEquals(b++, it) }
+ if (result.isSuccess) 2 else 0
}
}
when (done) {
0 -> break@loop
1 -> {
- val r = channelB.receiveOrNull()
+ val r = channelB.receiveCatching().getOrNull()
if (r != null) assertEquals(b++, r)
}
2 -> {
- val r = channelA.receiveOrNull()
+ val r = channelA.receiveCatching().getOrNull()
if (r != null) assertEquals(a++, r)
}
}
diff --git a/reactive/kotlinx-coroutines-rx3/test/SingleTest.kt b/reactive/kotlinx-coroutines-rx3/test/SingleTest.kt
index 46bcaf84..3e763aa5 100644
--- a/reactive/kotlinx-coroutines-rx3/test/SingleTest.kt
+++ b/reactive/kotlinx-coroutines-rx3/test/SingleTest.kt
@@ -9,6 +9,7 @@ import io.reactivex.rxjava3.disposables.*
import io.reactivex.rxjava3.exceptions.*
import io.reactivex.rxjava3.functions.*
import kotlinx.coroutines.*
+import kotlinx.coroutines.CancellationException
import org.junit.*
import org.junit.Test
import java.util.concurrent.*
@@ -98,6 +99,31 @@ class SingleTest : TestBase() {
assertEquals("OK", Single.just("O").await() + "K")
}
+ /** Tests that calls to [await] throw [CancellationException] and dispose of the subscription when their
+ * [Job] is cancelled. */
+ @Test
+ fun testSingleAwaitCancellation() = runTest {
+ expect(1)
+ val single = SingleSource<Int> { s ->
+ s.onSubscribe(object: Disposable {
+ override fun dispose() { expect(4) }
+ override fun isDisposed(): Boolean { expectUnreached(); return false }
+ })
+ }
+ val job = launch(start = CoroutineStart.UNDISPATCHED) {
+ try {
+ expect(2)
+ single.await()
+ } catch (e: CancellationException) {
+ expect(5)
+ throw e
+ }
+ }
+ expect(3)
+ job.cancelAndJoin()
+ finish(6)
+ }
+
@Test
fun testSingleEmitAndAwait() {
val single = rxSingle {
@@ -221,7 +247,7 @@ class SingleTest : TestBase() {
fun testFatalExceptionInSingle() = runTest {
rxSingle(Dispatchers.Unconfined) {
throw LinkageError()
- }.subscribe({ _, e -> assertTrue(e is LinkageError); expect(1) })
+ }.subscribe { _, e -> assertTrue(e is LinkageError); expect(1) }
finish(2)
}
diff --git a/settings.gradle b/settings.gradle
index 3a078917..44effa7c 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
pluginManagement {
@@ -10,6 +10,11 @@ pluginManagement {
id "net.ltgt.apt" version "0.21"
id "me.champeau.gradle.jmh" version "0.5.2"
}
+
+ repositories {
+ maven { url "https://maven.pkg.jetbrains.space/kotlin/p/dokka/dev/" }
+ gradlePluginPortal()
+ }
}
rootProject.name = 'kotlinx.coroutines'
@@ -31,7 +36,6 @@ include "kotlinx-coroutines-core"
module('kotlinx-coroutines-test')
module('kotlinx-coroutines-debug')
-module('stdlib-stubs')
module('kotlinx-coroutines-bom')
@@ -52,10 +56,8 @@ if (JavaVersion.current().isJava11Compatible()) {
}
module('ui/kotlinx-coroutines-swing')
-module('js/js-stub')
if (!build_snapshot_train) {
module('js/example-frontend-js')
- include('site')
}
module('integration-testing')
diff --git a/site/README.md b/site/README.md
deleted file mode 100644
index 7ffb4103..00000000
--- a/site/README.md
+++ /dev/null
@@ -1,15 +0,0 @@
-# Reference documentation site
-
-This module builds references documentation.
-
-## Building docs
-
-* Install [Docker](https://www.docker.com/)
-* In the project root directory run `./gradlew site`
-* The resulting HTML pages are generated in `site/build/dist`
-* For continuous testing of the documentation run `./gradlew serve` and navigate
- to the URL that is printed on the screen
- * Update the docs via `./gradlew copyDocs` while `serve` is running
-
-For release use [`deploy.sh`](deploy.sh) that performs clean build of the site and pushes the results
-into `gh-pages` branch of the project. \ No newline at end of file
diff --git a/site/build.gradle.kts b/site/build.gradle.kts
deleted file mode 100644
index eba7b1a1..00000000
--- a/site/build.gradle.kts
+++ /dev/null
@@ -1,65 +0,0 @@
-import groovy.lang.*
-
-/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
- */
-
-val buildDocsDir = "$buildDir/docs"
-val jekyllDockerImage = "jekyll/jekyll:${version("jekyll")}"
-
-val copyDocs by tasks.registering(Copy::class) {
- val dokkaTasks = rootProject.getTasksByName("dokka", true)
-
- from(dokkaTasks.map { "${it.project.buildDir}/dokka" }) {
- include("**/*.md")
- include("**/package-list")
- }
- from("docs")
- into(buildDocsDir)
- filter { it.replace("/index.md\"", "/index.html\"") }
-
- dependsOn(dokkaTasks)
-}
-
-val copyExampleFrontendJs by tasks.registering(Copy::class) {
- val srcBuildDir = project(":example-frontend-js").buildDir
- from("$srcBuildDir/dist")
- into("$buildDocsDir/example-frontend-js")
-
- dependsOn(":example-frontend-js:browserDistribution")
-}
-
-tasks.register<Exec>("site") {
- description = "Generate github pages"
-
- inputs.files(fileTree(buildDocsDir))
- outputs.dir("$buildDir/dist")
- workingDir = file(buildDocsDir)
-
- commandLine(
- "docker", "run", "--rm", "--volume=$buildDir:/srv/jekyll",
- "-t", jekyllDockerImage,
- "/bin/bash", "-c", "cd docs; jekyll build"
- )
-
- dependsOn(copyDocs)
- dependsOn(copyExampleFrontendJs)
-}
-
-// A useful task for local debugging -- serves a site locally
-tasks.register<Exec>("serve") {
- commandLine(
- "docker", "run", "--rm", "--volume=$buildDir:/srv/jekyll",
- "-p", "8080:8080",
- "-t", jekyllDockerImage,
- "/bin/bash", "-c", "cd docs; jekyll serve --host 0.0.0.0 --port 8080"
- )
-
- dependsOn(copyDocs)
- dependsOn(copyExampleFrontendJs)
-}
-
-tasks.register<Delete>("clean") {
- delete(buildDir)
-}
-
diff --git a/site/deploy.sh b/site/deploy.sh
index de7cdef2..a04e4925 100755
--- a/site/deploy.sh
+++ b/site/deploy.sh
@@ -1,13 +1,17 @@
#!/usr/bin/env bash
+#
+# Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+#
+
# Abort on first error
set -e
# Directories
SITE_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
ROOT_DIR="$SITE_DIR/.."
-BUILD_DIR="$SITE_DIR/build"
-DIST_DIR="$BUILD_DIR/dist"
+BUILD_DIR="$ROOT_DIR/build"
+DIST_DIR="$BUILD_DIR/dokka/htmlMultiModule"
PAGES_DIR="$BUILD_DIR/pages"
# Init options
@@ -24,7 +28,7 @@ else
fi
# Makes sure that site is built
-"$ROOT_DIR/gradlew" $GRADLE_OPT site
+"$ROOT_DIR/gradlew" $GRADLE_OPT dokkaHtmlMultiModule
# Cleanup dist directory (and ignore errors)
rm -rf "$PAGES_DIR" || true
@@ -42,27 +46,8 @@ cd "$PAGES_DIR"
# Remove non-.html files
git rm `find . -type f -not -name '*.html' -not -name '.git'` > /dev/null
-# Replace "experimental" .html files with links to the corresponding non-experimental version
-# or remove them if there is no corresponding non-experimental file
-echo "Redirecting experimental pages"
-git_add=()
-git_rm=()
-for file in `find . -type f -name '*.html'` ; do
- match=nothing_is_found
- if [[ $file =~ \.experimental ]] ; then
- match="${file//\.experimental/}"
- fi
- if [[ -f "$DIST_DIR/$match" ]] ; then
- # redirect to non-experimental version
- echo "<html><script>window.onload = function() { window.location.href = \"/kotlinx.coroutines${match#.}\" }</script></html>" > "$file"
- git_add+=("$file")
- else
- # redirect not found -- remove the file
- git_rm+=("$file")
- fi
-done
-git add "${git_add[@]}"
-git rm "${git_rm[@]}" > /dev/null
+# Remove all the old documentation
+git rm -r * > /dev/null
# Copy new documentation from dist
cp -r "$DIST_DIR"/* "$PAGES_DIR"
diff --git a/site/docs/_config.yml b/site/docs/_config.yml
deleted file mode 100644
index d7617e1b..00000000
--- a/site/docs/_config.yml
+++ /dev/null
@@ -1,14 +0,0 @@
-# Jekyll configuration file
-title: kotlinx.coroutines
-description: Library support for kotlin coroutines
-baseurl: "/kotlinx.coroutines"
-url: "https://kotlin.github.io"
-
-# Dirs
-source: .
-destination: ../dist
-
-# Build settings
-markdown: kramdown
-include:
- - package-list
diff --git a/site/docs/_includes/footer.html b/site/docs/_includes/footer.html
deleted file mode 100644
index b115703c..00000000
--- a/site/docs/_includes/footer.html
+++ /dev/null
@@ -1,3 +0,0 @@
-<footer class="site-footer">
- <!-- empty -->
-</footer>
diff --git a/site/docs/_includes/head.html b/site/docs/_includes/head.html
deleted file mode 100644
index ca3227d6..00000000
--- a/site/docs/_includes/head.html
+++ /dev/null
@@ -1,19 +0,0 @@
-<head>
- <meta charset="utf-8">
- <meta http-equiv="X-UA-Compatible" content="IE=edge">
- <meta name="viewport" content="width=device-width, initial-scale=1">
- <title>{% if page.title %}{{ page.title | escape }}{% else %}{{ site.title | escape }}{% endif %}</title>
- <meta name="description" content="{{ page.excerpt | default: site.description | strip_html | normalize_whitespace | truncate: 160 | escape }}">
- <link rel="stylesheet" href="{{ "/assets/main.css" | relative_url }}">
- <link rel="canonical" href="{{ page.url | replace:'index.html','' | absolute_url }}">
- <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
- <script src="{{ "/assets/js/api.js" | relative_url }}"></script>
- <script>
- (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
- (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
- m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
- })(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
- ga('create', 'UA-47631155-3', 'auto');
- ga('send', 'pageview');
- </script>
-</head>
diff --git a/site/docs/_includes/header.html b/site/docs/_includes/header.html
deleted file mode 100644
index b250a177..00000000
--- a/site/docs/_includes/header.html
+++ /dev/null
@@ -1,5 +0,0 @@
-<header class="site-header" role="banner">
- <div class="wrapper">
- <a class="site-title" href="{{ "/" | relative_url}}">{{ site.title | escape }}</a>
- </div>
-</header>
diff --git a/site/docs/_layouts/api.html b/site/docs/_layouts/api.html
deleted file mode 100644
index 0f93a6b2..00000000
--- a/site/docs/_layouts/api.html
+++ /dev/null
@@ -1,14 +0,0 @@
-<!DOCTYPE html>
-<html>
- {% include head.html %}
- <body>
- {% include header.html %}
- <main class="page-content" aria-label="Content">
- <div class="wrapper">
- {{ content }}
- </div>
- </main>
- {% include footer.html %}
- </body>
-</html>
-
diff --git a/site/docs/_sass/_api.scss b/site/docs/_sass/_api.scss
deleted file mode 100644
index ae411495..00000000
--- a/site/docs/_sass/_api.scss
+++ /dev/null
@@ -1,225 +0,0 @@
-
-// ----------------- Bits and pieces from kotlinlang.org reference -----------------
-
-body {
- -webkit-font-smoothing: antialiased;
- font-smoothing: antialiased;
- text-rendering: optimizeLegibility;
-}
-
-a {
- text-decoration: underline;
-}
-
-$vertical-rhythm-unit: 15px !global;
-
-h1 {
- margin-top: $vertical-rhythm-unit * 2;
- margin-bottom: $vertical-rhythm-unit;
- font-size: 30px;
- line-height: 33px;
-
- &:first-of-type {
- margin-top: 0;
- margin-bottom: $vertical-rhythm-unit * 2;
- }
-
- @media print {
- page-break-before: always;
- page-break-after: avoid;
- }
-
- &%_section-title {
- padding-top: 140px;
- margin-bottom: 45px;
- font-size: 55px;
- line-height: 65px;
- font-weight: bold;
- }
-}
-
-h2 {
- margin-top: $vertical-rhythm-unit * 2;
- margin-bottom: $vertical-rhythm-unit;
- font-size: 24px;
- line-height: 27px;
-
- &:first-of-type {
- margin-top: 0;
- }
-
- @media print {
- page-break-after: avoid;
- }
-}
-
-h3 {
- margin-top: $vertical-rhythm-unit * 2;
- margin-bottom: $vertical-rhythm-unit;
- font-size: 19px;
- line-height: 22px;
-
- @media print {
- page-break-after: avoid;
- }
-}
-
-h4 {
- margin-top: $vertical-rhythm-unit * 2;
- margin-bottom: $vertical-rhythm-unit;
- font-size: 16px;
- line-height: 20px;
- font-weight: bold;
-
- @media print {
- page-break-after: avoid;
- }
-}
-
-h5 {
- margin-top: $vertical-rhythm-unit * 2;
- margin-bottom: $vertical-rhythm-unit;
- font-size: 16px;
- line-height: 20px;
- font-weight: normal;
-
- @media print {
- page-break-after: avoid;
- }
-}
-
-caption {margin: 0;}
-
-$vertical-rhythm-unit: 15px !global;
-
-// tables
-
-table {
- margin-bottom: $vertical-rhythm-unit*2;
- line-height: inherit;
- font-size: inherit;
- border: 1px solid #dcdcdc;
-
- // Remove most spacing between table cells
- border-collapse: collapse;
- border-spacing: 0;
-
- &.zebra {
- tbody tr:nth-child(odd) {
- background-color: #f5f5f5;
- }
- }
-
- &.wide {
- min-width: 100%;
- }
-
- // Table header
- thead {
- background-color: #F7F7F7;
- border-bottom-width: 2px;
- }
-
- // Table footer
- tfoot {
- color: #ccc;
-
- tr {border-bottom: none;}
- }
-
- // Row
- tr {
- border-bottom: 1px solid #dcdcdc;
- }
-
- // Header cell
- th {
- padding-top: 10px;
- padding-bottom: 6px;
- text-align: left;
- font-weight: bold;
- }
-
- // Cell
- th,
- td {
- padding: 6px 10px;
- vertical-align: top;
-
- &:first-child {
- padding-left: 12px;
- }
-
- &:last-child {
- padding-right: 12px;
- }
- }
-
- // ???
- p:last-child,
- pre:last-child {
- margin-bottom: 0;
- }
-}
-
-.api-docs-breadcrumbs {
- margin-bottom: 25px;
-}
-
-// code
-
-$font-family-mono: 'Liberation Mono', Consolas, Menlo, Courier, monospace !global;
-$code-background: #efefef;
-
-pre {
- background-color: $code-background;
- overflow: auto;
-}
-
-code {
- font-family: $font-family-mono;
- font-style: normal;
- background-color: $code-background;
-}
-
-code :target {
- background-color: #FFFFCC;
-}
-
-// kotlin syntax highlight
-
-.signature {
- background-color: $code-background;
- padding: 4px;
-}
-
-.keyword {
- color: #0000C0;
-}
-
-.summarizedTypeName {
- background-color: lightcyan;
- font-style: italic;
-}
-
-.parameterName {
- font-weight: bold;
-}
-
-// MPP projects
-
-.tags {
- display: flex;
-}
-
-.tags__tag {
- color: white;
- font-weight: bold;
- text-transform: uppercase;
- background: #a7a7a7;
- padding: 0 7px;
- font-size: 10px;
- border-radius: 9px;
- line-height: 18px;
- margin-right: 5px;
-} \ No newline at end of file
diff --git a/site/docs/_sass/_base.scss b/site/docs/_sass/_base.scss
deleted file mode 100644
index b8d70d52..00000000
--- a/site/docs/_sass/_base.scss
+++ /dev/null
@@ -1,97 +0,0 @@
-// Bits and pieces from Minima Jekyll Layout
-// The MIT License (MIT) Copyright (c) 2016 Parker Moor
-
-// Reset some basic elements
-body, h1, h2, h3, h4, h5, h6,
-p, blockquote, pre, hr,
-dl, dd, ol, ul, figure {
- margin: 0;
- padding: 0;
-}
-
-// Basic styling
-body {
- font: $base-font-weight #{$base-font-size}/#{$base-line-height} $base-font-family;
- color: $text-color;
- background-color: $background-color;
- -webkit-text-size-adjust: 100%;
- -webkit-font-feature-settings: "kern" 1;
- -moz-font-feature-settings: "kern" 1;
- -o-font-feature-settings: "kern" 1;
- font-feature-settings: "kern" 1;
- font-kerning: normal;
-}
-
-// Set `margin-bottom` to maintain vertical rhythm
-h1, h2, h3, h4, h5, h6,
-p, blockquote, pre,
-ul, ol, dl, figure,
-%vertical-rhythm {
- margin-bottom: $spacing-unit / 2;
-}
-
-// Lists
-ul, ol {
- margin-left: $spacing-unit;
-}
-
-li {
- > ul,
- > ol {
- margin-bottom: 0;
- }
-}
-
-// Links
-a {
- color: $brand-color;
- text-decoration: none;
-
- &:visited {
- color: darken($brand-color, 15%);
- }
-
- &:hover {
- color: $text-color;
- text-decoration: underline;
- }
-}
-
-// Blockquotes
-blockquote {
- color: $grey-color;
- border-left: 4px solid $grey-color-light;
- padding-left: $spacing-unit / 2;
- font-size: 18px;
- letter-spacing: -1px;
- font-style: italic;
-
- > :last-child {
- margin-bottom: 0;
- }
-}
-
-// Wrapper
-.wrapper {
- max-width: -webkit-calc(#{$content-width} - (#{$spacing-unit} * 2));
- max-width: calc(#{$content-width} - (#{$spacing-unit} * 2));
- margin-right: auto;
- margin-left: auto;
- padding-right: $spacing-unit;
- padding-left: $spacing-unit;
- @extend %clearfix;
-
- @include media-query($on-laptop) {
- max-width: -webkit-calc(#{$content-width} - (#{$spacing-unit}));
- max-width: calc(#{$content-width} - (#{$spacing-unit}));
- padding-right: $spacing-unit / 2;
- padding-left: $spacing-unit / 2;
- }
-}
-
-// Clearfix
-%clearfix:after {
- content: "";
- display: table;
- clear: both;
-}
diff --git a/site/docs/_sass/_layout.scss b/site/docs/_sass/_layout.scss
deleted file mode 100644
index d85d0592..00000000
--- a/site/docs/_sass/_layout.scss
+++ /dev/null
@@ -1,37 +0,0 @@
-// Bits and pieces from Minima Jekyll Layout
-// The MIT License (MIT) Copyright (c) 2016 Parker Moor
-
-// Site header
-.site-header {
- border-top: 5px solid $grey-color-dark;
- border-bottom: 1px solid $grey-color-light;
- min-height: 56px;
-
- // Positioning context for the mobile navigation icon
- position: relative;
-}
-
-.site-title {
- font-size: 26px;
- font-weight: 300;
- line-height: 56px;
- letter-spacing: -1px;
- margin-bottom: 0;
- float: left;
-
- &,
- &:visited {
- color: $grey-color-dark;
- }
-}
-// Site footer
-.site-footer {
- border-top: 1px solid $grey-color-light;
- padding: $spacing-unit 0;
-}
-
-// Page content
-.page-content {
- padding: $spacing-unit 0;
-}
-
diff --git a/site/docs/_sass/_minima.scss b/site/docs/_sass/_minima.scss
deleted file mode 100644
index 86079f85..00000000
--- a/site/docs/_sass/_minima.scss
+++ /dev/null
@@ -1,35 +0,0 @@
-// Bits and pieces from Minima Jekyll Layout
-// The MIT License (MIT) Copyright (c) 2016 Parker Moor
-
-// Define defaults for each variable.
-
-$base-font-family: "Helvetica Neue", Helvetica, Arial, sans-serif !default;
-$base-font-size: 16px !default;
-$base-font-weight: 400 !default;
-$small-font-size: $base-font-size * 0.875 !default;
-$base-line-height: 1.5 !default;
-
-$spacing-unit: 30px !default;
-
-$text-color: #111 !default;
-$background-color: #fdfdfd !default;
-$brand-color: #2a7ae2 !default;
-
-$grey-color: #828282 !default;
-$grey-color-light: lighten($grey-color, 40%) !default;
-$grey-color-dark: darken($grey-color, 25%) !default;
-
-// Width of the content area
-$content-width: 800px !default;
-
-$on-palm: 600px !default;
-$on-laptop: 800px !default;
-
-@mixin media-query($device) {
- @media screen and (max-width: $device) {
- @content;
- }
-}
-
-// Import partials.
-@import "base", "layout";
diff --git a/site/docs/assets/js/api.js b/site/docs/assets/js/api.js
deleted file mode 100644
index 08f41fa0..00000000
--- a/site/docs/assets/js/api.js
+++ /dev/null
@@ -1,20 +0,0 @@
-/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
- */
-
-
-function addTag(rowElement, tag) {
- var tags = $(rowElement).find('.tags');
- if (tags.length == 0) {
- tags = $('<div class="tags"></div>');
- $(rowElement).find('td:first').append(tags);
- }
- tags.append('<div class="tags__tag">' + tag + '</div>')
-}
-
-$(document).ready(function () {
- $('[data-platform]').each(function (ind, element) {
- var platform = element.getAttribute('data-platform');
- addTag(element, platform)
- });
-});
diff --git a/site/docs/assets/main.scss b/site/docs/assets/main.scss
deleted file mode 100644
index 1be8487f..00000000
--- a/site/docs/assets/main.scss
+++ /dev/null
@@ -1,36 +0,0 @@
----
-# Only the main Sass file needs front matter (the dashes are enough)
----
-@charset "utf-8";
-
-// Sans Serif
-@import url('//fonts.googleapis.com/css?family=Open+Sans:300,300italic,400italic,700italic,400,700');
-
-// Our variables
-$base-font-family: "Open Sans", Helvetica, Arial, sans-serif;
-$base-font-size: 14px;
-$base-font-weight: 400;
-$small-font-size: $base-font-size * 0.875;
-$base-line-height: 20px;
-
-$spacing-unit: 30px;
-
-$text-color: #111;
-$background-color: #fdfdfd;
-$brand-color: #2a7ae2;
-
-$grey-color: #828282;
-$grey-color-light: lighten($grey-color, 40%);
-$grey-color-dark: darken($grey-color, 25%);
-
-// Width of the content area
-$content-width: 800px;
-
-$on-palm: 600px;
-$on-laptop: 800px;
-
-// Import partials from the `minima` theme.
-@import "minima";
-
-// Import api reference styles
-@import "api";
diff --git a/site/docs/index.md b/site/docs/index.md
deleted file mode 100644
index 3e6bb934..00000000
--- a/site/docs/index.md
+++ /dev/null
@@ -1,32 +0,0 @@
----
-title: kotlinx-coroutines
-layout: api
----
-
-# kotlinx.coroutines reference documentation
-
-Library support for Kotlin coroutines. This reference is a companion to
-[Guide to kotlinx.coroutines by example](https://github.com/Kotlin/kotlinx.coroutines/blob/master/coroutines-guide.md).
-
-## Modules
-
-| Name | Description |
-| ---------------------------------------------------------- | ------------------------------------------------ |
-| [kotlinx-coroutines-core](kotlinx-coroutines-core) | Core primitives to work with coroutines |
-| [kotlinx-coroutines-debug](kotlinx-coroutines-debug) | Debugging utilities for coroutines |
-| [kotlinx-coroutines-test](kotlinx-coroutines-test) | Test primitives for coroutines, `Main` dispatcher injection |
-| [kotlinx-coroutines-reactive](kotlinx-coroutines-reactive) | Utilities for [Reactive Streams](https://www.reactive-streams.org) |
-| [kotlinx-coroutines-reactor](kotlinx-coroutines-reactor) | Utilities for [Reactor](https://projectreactor.io) |
-| [kotlinx-coroutines-rx2](kotlinx-coroutines-rx2) | Utilities for [RxJava 2.x](https://github.com/ReactiveX/RxJava) |
-| [kotlinx-coroutines-android](kotlinx-coroutines-android) | `Main` dispatcher for Android applications |
-| [kotlinx-coroutines-javafx](kotlinx-coroutines-javafx) | `JavaFx` dispatcher for JavaFX UI applications |
-| [kotlinx-coroutines-swing](kotlinx-coroutines-swing) | `Swing` dispatcher for Swing UI applications |
-| [kotlinx-coroutines-jdk8](kotlinx-coroutines-jdk8) | Integration with JDK8 `CompletableFuture` (Android API level 24) |
-| [kotlinx-coroutines-guava](kotlinx-coroutines-guava) | Integration with Guava [ListenableFuture](https://github.com/google/guava/wiki/ListenableFutureExplained) |
-| [kotlinx-coroutines-slf4j](kotlinx-coroutines-slf4j) | Integration with SLF4J [MDC](https://logback.qos.ch/manual/mdc.html) |
-| [kotlinx-coroutines-play-services](kotlinx-coroutines-play-services) | Integration with Google Play Services [Tasks API](https://developers.google.com/android/guides/tasks) |
-
-## Examples
-
-* [example-frontend-js](example-frontend-js/index.html) -- frontend application written in Kotlin/JS
-that uses coroutines to implement animations in imperative style.
diff --git a/stdlib-stubs/README.md b/stdlib-stubs/README.md
deleted file mode 100644
index f47bccc8..00000000
--- a/stdlib-stubs/README.md
+++ /dev/null
@@ -1 +0,0 @@
-This is a workaround for Dokka to generate proper references for Kotlin 1.3 API. \ No newline at end of file
diff --git a/stdlib-stubs/build.gradle.kts b/stdlib-stubs/build.gradle.kts
deleted file mode 100644
index 6b9d6555..00000000
--- a/stdlib-stubs/build.gradle.kts
+++ /dev/null
@@ -1,11 +0,0 @@
-/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
- */
-
-import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
-
-tasks.named<KotlinCompile>("compileKotlin") {
- kotlinOptions {
- freeCompilerArgs += "-Xallow-kotlin-package"
- }
-}
diff --git a/stdlib-stubs/src/Continuation.kt b/stdlib-stubs/src/Continuation.kt
deleted file mode 100644
index 662f9dae..00000000
--- a/stdlib-stubs/src/Continuation.kt
+++ /dev/null
@@ -1,10 +0,0 @@
-/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
- */
-package kotlin.coroutines
-
-// DOKKA STUB
-public interface Continuation<in T> {
- public val context: CoroutineContext
- public fun resumeWith(result: Result<T>)
-}
diff --git a/stdlib-stubs/src/ContinuationInterceptor.kt b/stdlib-stubs/src/ContinuationInterceptor.kt
deleted file mode 100644
index ebf0a335..00000000
--- a/stdlib-stubs/src/ContinuationInterceptor.kt
+++ /dev/null
@@ -1,15 +0,0 @@
-/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
- */
-package kotlin.coroutines
-
-// DOKKA STUB
-public interface ContinuationInterceptor : CoroutineContext.Element {
- public companion object Key : CoroutineContext.Key<ContinuationInterceptor>
- public fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T>
- public fun releaseInterceptedContinuation(continuation: Continuation<*>): Continuation<*> {
- return continuation
- }
- public override operator fun <E : CoroutineContext.Element> get(key: CoroutineContext.Key<E>): E? = TODO()
- public override fun minusKey(key: CoroutineContext.Key<*>): CoroutineContext = TODO()
-}
diff --git a/stdlib-stubs/src/CoroutineContext.kt b/stdlib-stubs/src/CoroutineContext.kt
deleted file mode 100644
index ac216a0b..00000000
--- a/stdlib-stubs/src/CoroutineContext.kt
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
- */
-package kotlin.coroutines
-
-// DOKKA STUB
-public interface CoroutineContext {
- public operator fun <E : Element> get(key: Key<E>): E?
- public fun <R> fold(initial: R, operation: (R, Element) -> R): R
- public operator fun plus(context: CoroutineContext): CoroutineContext = TODO()
- public fun minusKey(key: Key<*>): CoroutineContext
- public interface Key<E : Element>
- public interface Element : CoroutineContext {
- public val key: Key<*>
-
- public override operator fun <E : Element> get(key: Key<E>): E? =
- @Suppress("UNCHECKED_CAST")
- if (this.key == key) this as E else null
-
- public override fun <R> fold(initial: R, operation: (R, Element) -> R): R =
- operation(initial, this)
-
- public override fun minusKey(key: Key<*>): CoroutineContext =
- if (this.key == key) EmptyCoroutineContext else this
- }
-}
-
-public object EmptyCoroutineContext : CoroutineContext {
- private const val serialVersionUID: Long = 0
- private fun readResolve(): Any = EmptyCoroutineContext
-
- public override fun <E : CoroutineContext.Element> get(key: CoroutineContext.Key<E>): E? = null
- public override fun <R> fold(initial: R, operation: (R, CoroutineContext.Element) -> R): R = initial
- public override fun plus(context: CoroutineContext): CoroutineContext = context
- public override fun minusKey(key: CoroutineContext.Key<*>): CoroutineContext = this
- public override fun hashCode(): Int = 0
- public override fun toString(): String = "EmptyCoroutineContext"
-}
diff --git a/stdlib-stubs/src/Result.kt b/stdlib-stubs/src/Result.kt
deleted file mode 100644
index 611074a7..00000000
--- a/stdlib-stubs/src/Result.kt
+++ /dev/null
@@ -1,14 +0,0 @@
-/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
- */
-
-package kotlin
-
-public interface Result<out T> {
- public val value: T
- public val isSuccess: Boolean
- public val isFailure: Boolean
- public fun exceptionOrNull(): Throwable?
- public fun getOrNull(): T?
- public fun getOrThrow(): T
-}
diff --git a/ui/coroutines-guide-ui.md b/ui/coroutines-guide-ui.md
index 9c1251fe..71b2d69c 100644
--- a/ui/coroutines-guide-ui.md
+++ b/ui/coroutines-guide-ui.md
@@ -1,7 +1,7 @@
# Guide to UI programming with coroutines
This guide assumes familiarity with basic coroutine concepts that are
-covered in [Guide to kotlinx.coroutines](../docs/coroutines-guide.md) and gives specific
+covered in [Guide to kotlinx.coroutines](../docs/topics/coroutines-guide.md) and gives specific
examples on how to use coroutines in UI applications.
All UI application libraries have one thing in common. They have the single main thread where all state of the UI
@@ -110,7 +110,7 @@ Add dependencies on `kotlinx-coroutines-android` module to the `dependencies { .
`app/build.gradle` file:
```groovy
-implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.1"
+implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.2"
```
You can clone [kotlinx.coroutines](https://github.com/Kotlin/kotlinx.coroutines) project from GitHub onto your
@@ -268,7 +268,7 @@ fun Node.onClick(action: suspend (MouseEvent) -> Unit) {
}
// install a listener to offer events to this actor
onMouseClicked = EventHandler { event ->
- eventActor.offer(event)
+ eventActor.trySend(event)
}
}
```
@@ -276,12 +276,12 @@ fun Node.onClick(action: suspend (MouseEvent) -> Unit) {
> You can get the full code [here](kotlinx-coroutines-javafx/test/guide/example-ui-actor-02.kt).
The key idea that underlies an integration of an actor coroutine and a regular event handler is that
-there is an [offer][SendChannel.offer] function on [SendChannel] that does not wait. It sends an element to the actor immediately,
-if it is possible, or discards an element otherwise. An `offer` actually returns a `Boolean` result which we ignore here.
+there is an [trySend][SendChannel.trySend] function on [SendChannel] that does not wait. It sends an element to the actor immediately,
+if it is possible, or discards an element otherwise. A `trySend` actually returns a `ChanneResult` instance which we ignore here.
Try clicking repeatedly on a circle in this version of the code. The clicks are just ignored while the countdown
animation is running. This happens because the actor is busy with an animation and does not receive from its channel.
-By default, an actor's mailbox is backed by `RendezvousChannel`, whose `offer` operation succeeds only when
+By default, an actor's mailbox is backed by `RendezvousChannel`, whose `trySend` operation succeeds only when
the `receive` is active.
> On Android, there is `View` sent in OnClickListener, so we send the `View` to the actor as a signal.
@@ -295,7 +295,7 @@ fun View.onClick(action: suspend (View) -> Unit) {
}
// install a listener to activate this actor
setOnClickListener {
- eventActor.offer(it)
+ eventActor.trySend(it)
}
}
```
@@ -310,7 +310,7 @@ processing the previous one. The [actor] coroutine builder accepts an optional
controls the implementation of the channel that this actor is using for its mailbox. The description of all
the available choices is given in documentation of the [`Channel()`][Channel] factory function.
-Let us change the code to use a conflated channel by passing [Channel.CONFLATED] capacity value. The
+Let us change the code to use a conflated channel by passing [Channel.CONFLATED][Channel.Factory.CONFLATED] capacity value. The
change is only to the line that creates an actor:
```kotlin
@@ -319,9 +319,9 @@ fun Node.onClick(action: suspend (MouseEvent) -> Unit) {
val eventActor = GlobalScope.actor<MouseEvent>(Dispatchers.Main, capacity = Channel.CONFLATED) { // <--- Changed here
for (event in channel) action(event) // pass event to action
}
- // install a listener to offer events to this actor
+ // install a listener to send events to this actor
onMouseClicked = EventHandler { event ->
- eventActor.offer(event)
+ eventActor.trySend(event)
}
}
```
@@ -359,7 +359,7 @@ fun Node.onClick(action: suspend (MouseEvent) -> Unit) {
for (event in channel) action(event) // pass event to action
}
onMouseClicked = EventHandler { event ->
- eventActor.offer(event)
+ eventActor.trySend(event)
}
}
-->
@@ -456,7 +456,7 @@ See [the corresponding documentation](https://developer.android.com/topic/librar
Parent-child relation between jobs forms a hierarchy. A coroutine that performs some background job on behalf of
the activity can create further children coroutines. The whole tree of coroutines gets cancelled
when the parent job is cancelled. An example of that is shown in the
-["Children of a coroutine"](../docs/coroutine-context-and-dispatchers.md#children-of-a-coroutine) section of the guide to coroutines.
+["Children of a coroutine"](../docs/topics/coroutine-context-and-dispatchers.md#children-of-a-coroutine) section of the guide to coroutines.
<!--- CLEAR -->
@@ -607,26 +607,32 @@ After delay
<!--- MODULE kotlinx-coroutines-core -->
<!--- INDEX kotlinx.coroutines -->
+
[launch]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/launch.html
[delay]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/delay.html
[Job]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job/index.html
-[Job.cancel]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job/cancel.html
+[Job.cancel]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/cancel.html
[CoroutineScope]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-scope/index.html
[MainScope()]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-main-scope.html
[withContext]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/with-context.html
[Dispatchers.Default]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-dispatchers/-default.html
[CoroutineStart]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-start/index.html
[async]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/async.html
-[CoroutineStart.UNDISPATCHED]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-start/-u-n-d-i-s-p-a-t-c-h-e-d.html
+[CoroutineStart.UNDISPATCHED]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-start/-u-n-d-i-s-p-a-t-c-h-e-d/index.html
+
<!--- INDEX kotlinx.coroutines.channels -->
+
[actor]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/actor.html
-[SendChannel.offer]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-send-channel/offer.html
+[SendChannel.trySend]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-send-channel/try-send.html
[SendChannel]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-send-channel/index.html
[Channel]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-channel/index.html
-[Channel.CONFLATED]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-channel/-c-o-n-f-l-a-t-e-d.html
+[Channel.Factory.CONFLATED]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-channel/-factory/-c-o-n-f-l-a-t-e-d.html
+
<!--- MODULE kotlinx-coroutines-javafx -->
<!--- INDEX kotlinx.coroutines.javafx -->
-[kotlinx.coroutines.Dispatchers.JavaFx]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-javafx/kotlinx.coroutines.javafx/kotlinx.coroutines.-dispatchers/-java-fx.html
+
+[kotlinx.coroutines.Dispatchers.JavaFx]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-javafx/kotlinx.coroutines.javafx/-java-fx.html
+
<!--- MODULE kotlinx-coroutines-android -->
<!--- INDEX kotlinx.coroutines.android -->
<!--- END -->
diff --git a/ui/knit.properties b/ui/knit.properties
index 3ad209a4..76a1d77a 100644
--- a/ui/knit.properties
+++ b/ui/knit.properties
@@ -1,7 +1,7 @@
#
-# Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+# Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
#
knit.dir=kotlinx-coroutines-javafx/test/guide/
knit.package=kotlinx.coroutines.javafx.guide
-knit.include=knit.code.include \ No newline at end of file
+knit.include=knit.code.include
diff --git a/ui/kotlinx-coroutines-android/android-unit-tests/build.gradle.kts b/ui/kotlinx-coroutines-android/android-unit-tests/build.gradle.kts
index 2acc058d..2fd0b814 100644
--- a/ui/kotlinx-coroutines-android/android-unit-tests/build.gradle.kts
+++ b/ui/kotlinx-coroutines-android/android-unit-tests/build.gradle.kts
@@ -1,8 +1,9 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
dependencies {
+ kotlinCompilerPluginClasspathMain(project(":kotlinx-coroutines-core"))
testImplementation("com.google.android:android:${version("android")}")
testImplementation("org.robolectric:robolectric:${version("robolectric")}")
testImplementation(project(":kotlinx-coroutines-test"))
diff --git a/ui/kotlinx-coroutines-android/android-unit-tests/src/EmptyCoroutineScopeImpl.kt b/ui/kotlinx-coroutines-android/android-unit-tests/src/EmptyCoroutineScopeImpl.kt
index a08f44ad..1c5c6abd 100644
--- a/ui/kotlinx-coroutines-android/android-unit-tests/src/EmptyCoroutineScopeImpl.kt
+++ b/ui/kotlinx-coroutines-android/android-unit-tests/src/EmptyCoroutineScopeImpl.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.android
diff --git a/ui/kotlinx-coroutines-android/animation-app/app/build.gradle.kts b/ui/kotlinx-coroutines-android/animation-app/app/build.gradle.kts
deleted file mode 100644
index 517f1f63..00000000
--- a/ui/kotlinx-coroutines-android/animation-app/app/build.gradle.kts
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
- */
-
-plugins {
- id("com.android.application")
- kotlin("android")
- kotlin("android.extensions")
-}
-
-android {
- compileSdkVersion = "29"
- defaultConfig {
- applicationId = "org.jetbrains.kotlinx.animation"
- minSdkVersion(14)
- targetSdkVersion(29)
- versionCode = 1
- versionName = "1.0"
- testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
- }
-}
-
-dependencies {
- implementation("androidx.appcompat:appcompat:1.0.2")
- implementation("androidx.constraintlayout:constraintlayout:1.1.3")
- implementation("com.google.android.material:material:1.0.0")
- implementation("androidx.lifecycle:lifecycle-extensions:2.0.0")
-
- implementation(kotlin("stdlib-jdk7"))
- implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:${property("coroutines_version")}")
-
- testImplementation("junit:junit:4.12")
- androidTestImplementation("androidx.test:runner:1.2.0")
- androidTestImplementation("androidx.test.espresso:espresso-core:3.2.0")
-}
diff --git a/ui/kotlinx-coroutines-android/animation-app/app/src/main/AndroidManifest.xml b/ui/kotlinx-coroutines-android/animation-app/app/src/main/AndroidManifest.xml
deleted file mode 100644
index 34d0dd14..00000000
--- a/ui/kotlinx-coroutines-android/animation-app/app/src/main/AndroidManifest.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
- -->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="org.jetbrains.kotlinx.animation">
-
- <application
- android:allowBackup="true"
- android:icon="@mipmap/ic_launcher"
- android:label="@string/app_name"
- android:roundIcon="@mipmap/ic_launcher_round"
- android:supportsRtl="true"
- android:theme="@style/AppTheme">
- <activity
- android:name="org.jetbrains.kotlinx.animation.MainActivity"
- android:label="@string/app_name"
- android:theme="@style/AppTheme.NoActionBar">
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
- </intent-filter>
- </activity>
- </application>
-
-</manifest>
diff --git a/ui/kotlinx-coroutines-android/animation-app/app/src/main/java/org/jetbrains/kotlinx/animation/Animation.kt b/ui/kotlinx-coroutines-android/animation-app/app/src/main/java/org/jetbrains/kotlinx/animation/Animation.kt
deleted file mode 100644
index 88e0baee..00000000
--- a/ui/kotlinx-coroutines-android/animation-app/app/src/main/java/org/jetbrains/kotlinx/animation/Animation.kt
+++ /dev/null
@@ -1,159 +0,0 @@
-/*
- * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
- */
-
-package org.jetbrains.kotlinx.animation
-
-import androidx.lifecycle.LifecycleOwner
-import androidx.lifecycle.MutableLiveData
-import androidx.lifecycle.Observer
-import androidx.lifecycle.ViewModel
-import android.content.Context
-import android.graphics.Canvas
-import android.graphics.Color
-import android.graphics.Paint
-import android.graphics.RectF
-import android.util.AttributeSet
-import android.view.View
-import kotlinx.coroutines.*
-import kotlinx.coroutines.android.*
-import java.util.*
-
-sealed class AnimatedShape {
- var x = 0.5f // 0 .. 1
- var y = 0.5f // 0 .. 1
- var color = Color.BLACK
- var r = 0.05f
-}
-
-class AnimatedCircle : AnimatedShape()
-class AnimatedSquare : AnimatedShape()
-
-private val NO_SHAPES = emptySet<AnimatedShape>()
-
-class AnimationView(
- context: Context, attributeSet: AttributeSet
-) : View(context, attributeSet), Observer<Set<AnimatedShape>> {
- private var shapes = NO_SHAPES
- private val paint = Paint()
- private val rect = RectF()
-
- override fun onChanged(shapes: Set<AnimatedShape>?) {
- this.shapes = shapes ?: NO_SHAPES
- invalidate()
- }
-
- override fun onDraw(canvas: Canvas) {
- val scale = minOf(width, height) / 2.0f
- shapes.forEach { shape ->
- val x = (shape.x - 0.5f) * scale + width / 2
- val y = (shape.y - 0.5f) * scale + height / 2
- val r = shape.r * scale
- rect.set(x - r, y - r, x + r, y + r)
- paint.color = shape.color
- when (shape) {
- is AnimatedCircle -> canvas.drawArc(rect, 0.0f, 360.0f, true, paint)
- is AnimatedSquare -> canvas.drawRect(rect, paint)
- }
- }
- }
-}
-
-private val rnd = Random()
-
-class AnimationModel : ViewModel(), CoroutineScope {
-
- override val coroutineContext = Job() + Dispatchers.Main
-
- private val shapes = MutableLiveData<Set<AnimatedShape>>()
-
- fun observe(owner: LifecycleOwner, observer: Observer<Set<AnimatedShape>>) =
- shapes.observe(owner, observer)
-
- fun update(shape: AnimatedShape) {
- val old = shapes.value ?: NO_SHAPES
- shapes.value = if (shape in old) old else old + shape
- }
-
- fun addAnimation() {
- launch {
- animateShape(if (rnd.nextBoolean()) AnimatedCircle() else AnimatedSquare())
- }
- }
-
- fun clearAnimations() {
- coroutineContext.cancelChildren()
- shapes.value = NO_SHAPES
- }
-}
-
-private fun norm(x: Float, y: Float) = Math.hypot(x.toDouble(), y.toDouble()).toFloat()
-
-private const val ACC = 1e-18f
-private const val MAX_SPEED = 2e-9f // in screen_fraction/nanos
-private const val INIT_POS = 0.8f
-
-private fun Random.nextColor() = Color.rgb(nextInt(256), nextInt(256), nextInt(256))
-private fun Random.nextPos() = nextFloat() * INIT_POS + (1 - INIT_POS) / 2
-private fun Random.nextSpeed() = nextFloat() * MAX_SPEED - MAX_SPEED / 2
-
-suspend fun AnimationModel.animateShape(shape: AnimatedShape) {
- shape.x = rnd.nextPos()
- shape.y = rnd.nextPos()
- shape.color = rnd.nextColor()
- var sx = rnd.nextSpeed()
- var sy = rnd.nextSpeed()
- var time = System.nanoTime() // nanos
- var checkTime = time
- while (true) {
- val dt = time.let { old -> awaitFrame().also { time = it } - old }
- if (dt > 0.5e9) continue // don't animate through over a half second lapses
- val dx = shape.x - 0.5f
- val dy = shape.y - 0.5f
- val dn = norm(dx, dy)
- sx -= dx / dn * ACC * dt
- sy -= dy / dn * ACC * dt
- val sn = norm(sx, sy)
- val trim = sn.coerceAtMost(MAX_SPEED)
- sx = sx / sn * trim
- sy = sy / sn * trim
- shape.x += sx * dt
- shape.y += sy * dt
- update(shape)
- // check once a second
- if (time > checkTime + 1e9) {
- checkTime = time
- when (rnd.nextInt(20)) { // roll d20
- 0 -> {
- animateColor(shape) // wait a second & animate color
- time = awaitFrame() // and sync with next frame
- }
- 1 -> { // random speed change
- sx = rnd.nextSpeed()
- sy = rnd.nextSpeed()
- }
- }
- }
- }
-}
-
-suspend fun AnimationModel.animateColor(shape: AnimatedShape) {
- val duration = 1e9f
- val startTime = System.nanoTime()
- val aColor = shape.color
- val bColor = rnd.nextColor()
- while (true) {
- val time = awaitFrame()
- val b = (time - startTime) / duration
- if (b >= 1.0f) break
- val a = 1 - b
- shape.color = Color.rgb(
- (Color.red(bColor) * b + Color.red(aColor) * a).toInt(),
- (Color.green(bColor) * b + Color.green(aColor) * a).toInt(),
- (Color.blue(bColor) * b + Color.blue(aColor) * a).toInt()
- )
- update(shape)
- }
- shape.color = bColor
- update(shape)
-}
diff --git a/ui/kotlinx-coroutines-android/animation-app/app/src/main/java/org/jetbrains/kotlinx/animation/MainActivity.kt b/ui/kotlinx-coroutines-android/animation-app/app/src/main/java/org/jetbrains/kotlinx/animation/MainActivity.kt
deleted file mode 100644
index 756db9bb..00000000
--- a/ui/kotlinx-coroutines-android/animation-app/app/src/main/java/org/jetbrains/kotlinx/animation/MainActivity.kt
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
- */
-
-package org.jetbrains.kotlinx.animation
-
-import androidx.lifecycle.ViewModelProviders
-import android.os.Bundle
-import androidx.appcompat.app.AppCompatActivity
-import kotlinx.android.synthetic.main.activity_main.*
-import kotlinx.android.synthetic.main.content_main.*
-
-class MainActivity : AppCompatActivity() {
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- setContentView(R.layout.activity_main)
- setSupportActionBar(toolbar)
-
- val animationModel = ViewModelProviders.of(this).get(AnimationModel::class.java)
- animationModel.observe(this, animationView)
-
- addButton.setOnClickListener {
- animationModel.addAnimation()
- }
-
- removeButton.setOnClickListener {
- animationModel.clearAnimations()
- }
- }
-}
diff --git a/ui/kotlinx-coroutines-android/animation-app/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/ui/kotlinx-coroutines-android/animation-app/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
deleted file mode 100644
index c7bd21db..00000000
--- a/ui/kotlinx-coroutines-android/animation-app/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
+++ /dev/null
@@ -1,34 +0,0 @@
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:aapt="http://schemas.android.com/aapt"
- android:width="108dp"
- android:height="108dp"
- android:viewportHeight="108"
- android:viewportWidth="108">
- <path
- android:fillType="evenOdd"
- android:pathData="M32,64C32,64 38.39,52.99 44.13,50.95C51.37,48.37 70.14,49.57 70.14,49.57L108.26,87.69L108,109.01L75.97,107.97L32,64Z"
- android:strokeColor="#00000000"
- android:strokeWidth="1">
- <aapt:attr name="android:fillColor">
- <gradient
- android:endX="78.5885"
- android:endY="90.9159"
- android:startX="48.7653"
- android:startY="61.0927"
- android:type="linear">
- <item
- android:color="#44000000"
- android:offset="0.0" />
- <item
- android:color="#00000000"
- android:offset="1.0" />
- </gradient>
- </aapt:attr>
- </path>
- <path
- android:fillColor="#FFFFFF"
- android:fillType="nonZero"
- android:pathData="M66.94,46.02L66.94,46.02C72.44,50.07 76,56.61 76,64L32,64C32,56.61 35.56,50.11 40.98,46.06L36.18,41.19C35.45,40.45 35.45,39.3 36.18,38.56C36.91,37.81 38.05,37.81 38.78,38.56L44.25,44.05C47.18,42.57 50.48,41.71 54,41.71C57.48,41.71 60.78,42.57 63.68,44.05L69.11,38.56C69.84,37.81 70.98,37.81 71.71,38.56C72.44,39.3 72.44,40.45 71.71,41.19L66.94,46.02ZM62.94,56.92C64.08,56.92 65,56.01 65,54.88C65,53.76 64.08,52.85 62.94,52.85C61.8,52.85 60.88,53.76 60.88,54.88C60.88,56.01 61.8,56.92 62.94,56.92ZM45.06,56.92C46.2,56.92 47.13,56.01 47.13,54.88C47.13,53.76 46.2,52.85 45.06,52.85C43.92,52.85 43,53.76 43,54.88C43,56.01 43.92,56.92 45.06,56.92Z"
- android:strokeColor="#00000000"
- android:strokeWidth="1" />
-</vector>
diff --git a/ui/kotlinx-coroutines-android/animation-app/app/src/main/res/drawable/ic_launcher_background.xml b/ui/kotlinx-coroutines-android/animation-app/app/src/main/res/drawable/ic_launcher_background.xml
deleted file mode 100644
index d5fccc53..00000000
--- a/ui/kotlinx-coroutines-android/animation-app/app/src/main/res/drawable/ic_launcher_background.xml
+++ /dev/null
@@ -1,170 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="108dp"
- android:height="108dp"
- android:viewportHeight="108"
- android:viewportWidth="108">
- <path
- android:fillColor="#26A69A"
- android:pathData="M0,0h108v108h-108z" />
- <path
- android:fillColor="#00000000"
- android:pathData="M9,0L9,108"
- android:strokeColor="#33FFFFFF"
- android:strokeWidth="0.8" />
- <path
- android:fillColor="#00000000"
- android:pathData="M19,0L19,108"
- android:strokeColor="#33FFFFFF"
- android:strokeWidth="0.8" />
- <path
- android:fillColor="#00000000"
- android:pathData="M29,0L29,108"
- android:strokeColor="#33FFFFFF"
- android:strokeWidth="0.8" />
- <path
- android:fillColor="#00000000"
- android:pathData="M39,0L39,108"
- android:strokeColor="#33FFFFFF"
- android:strokeWidth="0.8" />
- <path
- android:fillColor="#00000000"
- android:pathData="M49,0L49,108"
- android:strokeColor="#33FFFFFF"
- android:strokeWidth="0.8" />
- <path
- android:fillColor="#00000000"
- android:pathData="M59,0L59,108"
- android:strokeColor="#33FFFFFF"
- android:strokeWidth="0.8" />
- <path
- android:fillColor="#00000000"
- android:pathData="M69,0L69,108"
- android:strokeColor="#33FFFFFF"
- android:strokeWidth="0.8" />
- <path
- android:fillColor="#00000000"
- android:pathData="M79,0L79,108"
- android:strokeColor="#33FFFFFF"
- android:strokeWidth="0.8" />
- <path
- android:fillColor="#00000000"
- android:pathData="M89,0L89,108"
- android:strokeColor="#33FFFFFF"
- android:strokeWidth="0.8" />
- <path
- android:fillColor="#00000000"
- android:pathData="M99,0L99,108"
- android:strokeColor="#33FFFFFF"
- android:strokeWidth="0.8" />
- <path
- android:fillColor="#00000000"
- android:pathData="M0,9L108,9"
- android:strokeColor="#33FFFFFF"
- android:strokeWidth="0.8" />
- <path
- android:fillColor="#00000000"
- android:pathData="M0,19L108,19"
- android:strokeColor="#33FFFFFF"
- android:strokeWidth="0.8" />
- <path
- android:fillColor="#00000000"
- android:pathData="M0,29L108,29"
- android:strokeColor="#33FFFFFF"
- android:strokeWidth="0.8" />
- <path
- android:fillColor="#00000000"
- android:pathData="M0,39L108,39"
- android:strokeColor="#33FFFFFF"
- android:strokeWidth="0.8" />
- <path
- android:fillColor="#00000000"
- android:pathData="M0,49L108,49"
- android:strokeColor="#33FFFFFF"
- android:strokeWidth="0.8" />
- <path
- android:fillColor="#00000000"
- android:pathData="M0,59L108,59"
- android:strokeColor="#33FFFFFF"
- android:strokeWidth="0.8" />
- <path
- android:fillColor="#00000000"
- android:pathData="M0,69L108,69"
- android:strokeColor="#33FFFFFF"
- android:strokeWidth="0.8" />
- <path
- android:fillColor="#00000000"
- android:pathData="M0,79L108,79"
- android:strokeColor="#33FFFFFF"
- android:strokeWidth="0.8" />
- <path
- android:fillColor="#00000000"
- android:pathData="M0,89L108,89"
- android:strokeColor="#33FFFFFF"
- android:strokeWidth="0.8" />
- <path
- android:fillColor="#00000000"
- android:pathData="M0,99L108,99"
- android:strokeColor="#33FFFFFF"
- android:strokeWidth="0.8" />
- <path
- android:fillColor="#00000000"
- android:pathData="M19,29L89,29"
- android:strokeColor="#33FFFFFF"
- android:strokeWidth="0.8" />
- <path
- android:fillColor="#00000000"
- android:pathData="M19,39L89,39"
- android:strokeColor="#33FFFFFF"
- android:strokeWidth="0.8" />
- <path
- android:fillColor="#00000000"
- android:pathData="M19,49L89,49"
- android:strokeColor="#33FFFFFF"
- android:strokeWidth="0.8" />
- <path
- android:fillColor="#00000000"
- android:pathData="M19,59L89,59"
- android:strokeColor="#33FFFFFF"
- android:strokeWidth="0.8" />
- <path
- android:fillColor="#00000000"
- android:pathData="M19,69L89,69"
- android:strokeColor="#33FFFFFF"
- android:strokeWidth="0.8" />
- <path
- android:fillColor="#00000000"
- android:pathData="M19,79L89,79"
- android:strokeColor="#33FFFFFF"
- android:strokeWidth="0.8" />
- <path
- android:fillColor="#00000000"
- android:pathData="M29,19L29,89"
- android:strokeColor="#33FFFFFF"
- android:strokeWidth="0.8" />
- <path
- android:fillColor="#00000000"
- android:pathData="M39,19L39,89"
- android:strokeColor="#33FFFFFF"
- android:strokeWidth="0.8" />
- <path
- android:fillColor="#00000000"
- android:pathData="M49,19L49,89"
- android:strokeColor="#33FFFFFF"
- android:strokeWidth="0.8" />
- <path
- android:fillColor="#00000000"
- android:pathData="M59,19L59,89"
- android:strokeColor="#33FFFFFF"
- android:strokeWidth="0.8" />
- <path
- android:fillColor="#00000000"
- android:pathData="M69,19L69,89"
- android:strokeColor="#33FFFFFF"
- android:strokeWidth="0.8" />
- <path
- android:fillColor="#00000000"
- android:pathData="M79,19L79,89"
- android:strokeColor="#33FFFFFF"
- android:strokeWidth="0.8" />
-</vector>
diff --git a/ui/kotlinx-coroutines-android/animation-app/app/src/main/res/layout/activity_main.xml b/ui/kotlinx-coroutines-android/animation-app/app/src/main/res/layout/activity_main.xml
deleted file mode 100644
index 8e06e901..00000000
--- a/ui/kotlinx-coroutines-android/animation-app/app/src/main/res/layout/activity_main.xml
+++ /dev/null
@@ -1,47 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
- -->
-
-<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:app="http://schemas.android.com/apk/res-auto"
- xmlns:tools="http://schemas.android.com/tools"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- tools:context="org.jetbrains.kotlinx.animation.MainActivity">
-
- <com.google.android.material.appbar.AppBarLayout
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:theme="@style/AppTheme.AppBarOverlay">
-
- <androidx.appcompat.widget.Toolbar
- android:id="@+id/toolbar"
- android:layout_width="match_parent"
- android:layout_height="?attr/actionBarSize"
- android:background="?attr/colorPrimary"
- app:popupTheme="@style/AppTheme.PopupOverlay" />
-
- </com.google.android.material.appbar.AppBarLayout>
-
- <include layout="@layout/content_main" />
-
- <com.google.android.material.floatingactionbutton.FloatingActionButton
- android:id="@+id/addButton"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="bottom|end"
- android:layout_margin="@dimen/fab_margin"
- app:backgroundTint="@color/colorPrimary"
- app:srcCompat="@android:drawable/ic_input_add" />
-
- <com.google.android.material.floatingactionbutton.FloatingActionButton
- android:id="@+id/removeButton"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="bottom"
- android:layout_margin="@dimen/fab_margin"
- app:backgroundTint="@color/colorPrimary"
- app:srcCompat="@android:drawable/ic_delete" />
-
-</androidx.coordinatorlayout.widget.CoordinatorLayout>
diff --git a/ui/kotlinx-coroutines-android/animation-app/app/src/main/res/layout/content_main.xml b/ui/kotlinx-coroutines-android/animation-app/app/src/main/res/layout/content_main.xml
deleted file mode 100644
index 2019bb5d..00000000
--- a/ui/kotlinx-coroutines-android/animation-app/app/src/main/res/layout/content_main.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
- -->
-
-<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:app="http://schemas.android.com/apk/res-auto"
- xmlns:tools="http://schemas.android.com/tools"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- app:layout_behavior="@string/appbar_scrolling_view_behavior"
- tools:context="org.jetbrains.kotlinx.animation.MainActivity"
- tools:showIn="@layout/activity_main">
-
- <org.jetbrains.kotlinx.animation.AnimationView
- android:id="@+id/animationView"
- android:layout_height="wrap_content"
- android:layout_width="wrap_content"
- app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintLeft_toLeftOf="parent"
- app:layout_constraintRight_toRightOf="parent"
- app:layout_constraintTop_toTopOf="parent" />
-
-</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/ui/kotlinx-coroutines-android/animation-app/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/ui/kotlinx-coroutines-android/animation-app/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
deleted file mode 100644
index 8bc717e4..00000000
--- a/ui/kotlinx-coroutines-android/animation-app/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
- -->
-
-<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
- <background android:drawable="@drawable/ic_launcher_background" />
- <foreground android:drawable="@drawable/ic_launcher_foreground" />
-</adaptive-icon>
diff --git a/ui/kotlinx-coroutines-android/animation-app/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/ui/kotlinx-coroutines-android/animation-app/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
deleted file mode 100644
index 8bc717e4..00000000
--- a/ui/kotlinx-coroutines-android/animation-app/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
- -->
-
-<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
- <background android:drawable="@drawable/ic_launcher_background" />
- <foreground android:drawable="@drawable/ic_launcher_foreground" />
-</adaptive-icon>
diff --git a/ui/kotlinx-coroutines-android/animation-app/app/src/main/res/mipmap-hdpi/ic_launcher.png b/ui/kotlinx-coroutines-android/animation-app/app/src/main/res/mipmap-hdpi/ic_launcher.png
deleted file mode 100644
index a2f59082..00000000
--- a/ui/kotlinx-coroutines-android/animation-app/app/src/main/res/mipmap-hdpi/ic_launcher.png
+++ /dev/null
Binary files differ
diff --git a/ui/kotlinx-coroutines-android/animation-app/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/ui/kotlinx-coroutines-android/animation-app/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
deleted file mode 100644
index 1b523998..00000000
--- a/ui/kotlinx-coroutines-android/animation-app/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
+++ /dev/null
Binary files differ
diff --git a/ui/kotlinx-coroutines-android/animation-app/app/src/main/res/mipmap-mdpi/ic_launcher.png b/ui/kotlinx-coroutines-android/animation-app/app/src/main/res/mipmap-mdpi/ic_launcher.png
deleted file mode 100644
index ff10afd6..00000000
--- a/ui/kotlinx-coroutines-android/animation-app/app/src/main/res/mipmap-mdpi/ic_launcher.png
+++ /dev/null
Binary files differ
diff --git a/ui/kotlinx-coroutines-android/animation-app/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/ui/kotlinx-coroutines-android/animation-app/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
deleted file mode 100644
index 115a4c76..00000000
--- a/ui/kotlinx-coroutines-android/animation-app/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
+++ /dev/null
Binary files differ
diff --git a/ui/kotlinx-coroutines-android/animation-app/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/ui/kotlinx-coroutines-android/animation-app/app/src/main/res/mipmap-xhdpi/ic_launcher.png
deleted file mode 100644
index dcd3cd80..00000000
--- a/ui/kotlinx-coroutines-android/animation-app/app/src/main/res/mipmap-xhdpi/ic_launcher.png
+++ /dev/null
Binary files differ
diff --git a/ui/kotlinx-coroutines-android/animation-app/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/ui/kotlinx-coroutines-android/animation-app/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
deleted file mode 100644
index 459ca609..00000000
--- a/ui/kotlinx-coroutines-android/animation-app/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
+++ /dev/null
Binary files differ
diff --git a/ui/kotlinx-coroutines-android/animation-app/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/ui/kotlinx-coroutines-android/animation-app/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
deleted file mode 100644
index 8ca12fe0..00000000
--- a/ui/kotlinx-coroutines-android/animation-app/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
+++ /dev/null
Binary files differ
diff --git a/ui/kotlinx-coroutines-android/animation-app/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/ui/kotlinx-coroutines-android/animation-app/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
deleted file mode 100644
index 8e19b410..00000000
--- a/ui/kotlinx-coroutines-android/animation-app/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
+++ /dev/null
Binary files differ
diff --git a/ui/kotlinx-coroutines-android/animation-app/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/ui/kotlinx-coroutines-android/animation-app/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
deleted file mode 100644
index b824ebdd..00000000
--- a/ui/kotlinx-coroutines-android/animation-app/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
+++ /dev/null
Binary files differ
diff --git a/ui/kotlinx-coroutines-android/animation-app/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/ui/kotlinx-coroutines-android/animation-app/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
deleted file mode 100644
index 4c19a13c..00000000
--- a/ui/kotlinx-coroutines-android/animation-app/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
+++ /dev/null
Binary files differ
diff --git a/ui/kotlinx-coroutines-android/animation-app/app/src/main/res/values/colors.xml b/ui/kotlinx-coroutines-android/animation-app/app/src/main/res/values/colors.xml
deleted file mode 100644
index 9ad7e369..00000000
--- a/ui/kotlinx-coroutines-android/animation-app/app/src/main/res/values/colors.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<resources>
- <color name="colorPrimary">#3f51b5</color>
- <color name="colorPrimaryDark">#303F9F</color>
- <color name="colorAccent">#FF4081</color>
-</resources>
diff --git a/ui/kotlinx-coroutines-android/animation-app/app/src/main/res/values/dimens.xml b/ui/kotlinx-coroutines-android/animation-app/app/src/main/res/values/dimens.xml
deleted file mode 100644
index 59a0b0c4..00000000
--- a/ui/kotlinx-coroutines-android/animation-app/app/src/main/res/values/dimens.xml
+++ /dev/null
@@ -1,3 +0,0 @@
-<resources>
- <dimen name="fab_margin">16dp</dimen>
-</resources>
diff --git a/ui/kotlinx-coroutines-android/animation-app/app/src/main/res/values/strings.xml b/ui/kotlinx-coroutines-android/animation-app/app/src/main/res/values/strings.xml
deleted file mode 100644
index cd3f467b..00000000
--- a/ui/kotlinx-coroutines-android/animation-app/app/src/main/res/values/strings.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<resources>
- <string name="app_name">Animation</string>
- <string name="action_settings">Settings</string>
-</resources>
diff --git a/ui/kotlinx-coroutines-android/animation-app/app/src/main/res/values/styles.xml b/ui/kotlinx-coroutines-android/animation-app/app/src/main/res/values/styles.xml
deleted file mode 100644
index 545b9c6d..00000000
--- a/ui/kotlinx-coroutines-android/animation-app/app/src/main/res/values/styles.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<resources>
-
- <!-- Base application theme. -->
- <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
- <!-- Customize your theme here. -->
- <item name="colorPrimary">@color/colorPrimary</item>
- <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
- <item name="colorAccent">@color/colorAccent</item>
- </style>
-
- <style name="AppTheme.NoActionBar">
- <item name="windowActionBar">false</item>
- <item name="windowNoTitle">true</item>
- </style>
-
- <style name="AppTheme.AppBarOverlay" parent="ThemeOverlay.AppCompat.Dark.ActionBar" />
-
- <style name="AppTheme.PopupOverlay" parent="ThemeOverlay.AppCompat.Light" />
-
-</resources>
diff --git a/ui/kotlinx-coroutines-android/animation-app/build.gradle.kts b/ui/kotlinx-coroutines-android/animation-app/build.gradle.kts
deleted file mode 100644
index 9cd0c592..00000000
--- a/ui/kotlinx-coroutines-android/animation-app/build.gradle.kts
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
- */
-
-// Top-level build file where you can add configuration options common to all sub-projects/modules.
-
-buildscript {
- repositories {
- google()
- jcenter()
- }
- dependencies {
- classpath("com.android.tools.build:gradle:3.5.0")
- classpath(kotlin("gradle-plugin", property("kotlin_version") as String))
-
- // NOTE: Do not place your application dependencies here; they belong
- // in the individual module build.gradle files
- }
-}
-
-allprojects {
- repositories {
- google()
- jcenter()
- }
-}
-
-task<Delete>("clean") {
- delete(rootProject.buildDir)
-}
diff --git a/ui/kotlinx-coroutines-android/animation-app/gradle.properties b/ui/kotlinx-coroutines-android/animation-app/gradle.properties
deleted file mode 100644
index c4aa6758..00000000
--- a/ui/kotlinx-coroutines-android/animation-app/gradle.properties
+++ /dev/null
@@ -1,28 +0,0 @@
-#
-# Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
-#
-
-# Project-wide Gradle settings.
-
-# IDE (e.g. Android Studio) users:
-# Gradle settings configured through the IDE *will override*
-# any settings specified in this file.
-
-# For more details on how to configure your build environment visit
-# http://www.gradle.org/docs/current/userguide/build_environment.html
-
-# Specifies the JVM arguments used for the daemon process.
-# The setting is particularly useful for tweaking memory settings.
-org.gradle.jvmargs=-Xmx1536m
-
-# When configured, Gradle will run in incubating parallel mode.
-# This option should only be used with decoupled projects. More details, visit
-# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
-# org.gradle.parallel=true
-
-kotlin_version=1.4.0
-coroutines_version=1.4.1
-
-android.useAndroidX=true
-android.enableJetifier=true
-
diff --git a/ui/kotlinx-coroutines-android/animation-app/gradle/wrapper/gradle-wrapper.jar b/ui/kotlinx-coroutines-android/animation-app/gradle/wrapper/gradle-wrapper.jar
deleted file mode 100644
index 490fda85..00000000
--- a/ui/kotlinx-coroutines-android/animation-app/gradle/wrapper/gradle-wrapper.jar
+++ /dev/null
Binary files differ
diff --git a/ui/kotlinx-coroutines-android/animation-app/gradle/wrapper/gradle-wrapper.properties b/ui/kotlinx-coroutines-android/animation-app/gradle/wrapper/gradle-wrapper.properties
deleted file mode 100644
index dbe85eef..00000000
--- a/ui/kotlinx-coroutines-android/animation-app/gradle/wrapper/gradle-wrapper.properties
+++ /dev/null
@@ -1,5 +0,0 @@
-distributionBase=GRADLE_USER_HOME
-distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.1-all.zip
-zipStoreBase=GRADLE_USER_HOME
-zipStorePath=wrapper/dists
diff --git a/ui/kotlinx-coroutines-android/animation-app/gradlew b/ui/kotlinx-coroutines-android/animation-app/gradlew
deleted file mode 100755
index 2fe81a7d..00000000
--- a/ui/kotlinx-coroutines-android/animation-app/gradlew
+++ /dev/null
@@ -1,183 +0,0 @@
-#!/usr/bin/env sh
-
-#
-# Copyright 2015 the original author or authors.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# https://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-##############################################################################
-##
-## Gradle start up script for UN*X
-##
-##############################################################################
-
-# Attempt to set APP_HOME
-# Resolve links: $0 may be a link
-PRG="$0"
-# Need this for relative symlinks.
-while [ -h "$PRG" ] ; do
- ls=`ls -ld "$PRG"`
- link=`expr "$ls" : '.*-> \(.*\)$'`
- if expr "$link" : '/.*' > /dev/null; then
- PRG="$link"
- else
- PRG=`dirname "$PRG"`"/$link"
- fi
-done
-SAVED="`pwd`"
-cd "`dirname \"$PRG\"`/" >/dev/null
-APP_HOME="`pwd -P`"
-cd "$SAVED" >/dev/null
-
-APP_NAME="Gradle"
-APP_BASE_NAME=`basename "$0"`
-
-# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
-DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
-
-# Use the maximum available, or set MAX_FD != -1 to use that value.
-MAX_FD="maximum"
-
-warn () {
- echo "$*"
-}
-
-die () {
- echo
- echo "$*"
- echo
- exit 1
-}
-
-# OS specific support (must be 'true' or 'false').
-cygwin=false
-msys=false
-darwin=false
-nonstop=false
-case "`uname`" in
- CYGWIN* )
- cygwin=true
- ;;
- Darwin* )
- darwin=true
- ;;
- MINGW* )
- msys=true
- ;;
- NONSTOP* )
- nonstop=true
- ;;
-esac
-
-CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
-
-# Determine the Java command to use to start the JVM.
-if [ -n "$JAVA_HOME" ] ; then
- if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
- # IBM's JDK on AIX uses strange locations for the executables
- JAVACMD="$JAVA_HOME/jre/sh/java"
- else
- JAVACMD="$JAVA_HOME/bin/java"
- fi
- if [ ! -x "$JAVACMD" ] ; then
- die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
-
-Please set the JAVA_HOME variable in your environment to match the
-location of your Java installation."
- fi
-else
- JAVACMD="java"
- which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
-
-Please set the JAVA_HOME variable in your environment to match the
-location of your Java installation."
-fi
-
-# Increase the maximum file descriptors if we can.
-if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
- MAX_FD_LIMIT=`ulimit -H -n`
- if [ $? -eq 0 ] ; then
- if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
- MAX_FD="$MAX_FD_LIMIT"
- fi
- ulimit -n $MAX_FD
- if [ $? -ne 0 ] ; then
- warn "Could not set maximum file descriptor limit: $MAX_FD"
- fi
- else
- warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
- fi
-fi
-
-# For Darwin, add options to specify how the application appears in the dock
-if $darwin; then
- GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
-fi
-
-# For Cygwin or MSYS, switch paths to Windows format before running java
-if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
- APP_HOME=`cygpath --path --mixed "$APP_HOME"`
- CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
- JAVACMD=`cygpath --unix "$JAVACMD"`
-
- # We build the pattern for arguments to be converted via cygpath
- ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
- SEP=""
- for dir in $ROOTDIRSRAW ; do
- ROOTDIRS="$ROOTDIRS$SEP$dir"
- SEP="|"
- done
- OURCYGPATTERN="(^($ROOTDIRS))"
- # Add a user-defined pattern to the cygpath arguments
- if [ "$GRADLE_CYGPATTERN" != "" ] ; then
- OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
- fi
- # Now convert the arguments - kludge to limit ourselves to /bin/sh
- i=0
- for arg in "$@" ; do
- CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
- CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
-
- if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
- eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
- else
- eval `echo args$i`="\"$arg\""
- fi
- i=`expr $i + 1`
- done
- case $i in
- 0) set -- ;;
- 1) set -- "$args0" ;;
- 2) set -- "$args0" "$args1" ;;
- 3) set -- "$args0" "$args1" "$args2" ;;
- 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
- 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
- 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
- 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
- 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
- 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
- esac
-fi
-
-# Escape application args
-save () {
- for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
- echo " "
-}
-APP_ARGS=`save "$@"`
-
-# Collect all arguments for the java command, following the shell quoting and substitution rules
-eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
-
-exec "$JAVACMD" "$@"
diff --git a/ui/kotlinx-coroutines-android/animation-app/gradlew.bat b/ui/kotlinx-coroutines-android/animation-app/gradlew.bat
deleted file mode 100644
index 62bd9b9c..00000000
--- a/ui/kotlinx-coroutines-android/animation-app/gradlew.bat
+++ /dev/null
@@ -1,103 +0,0 @@
-@rem
-@rem Copyright 2015 the original author or authors.
-@rem
-@rem Licensed under the Apache License, Version 2.0 (the "License");
-@rem you may not use this file except in compliance with the License.
-@rem You may obtain a copy of the License at
-@rem
-@rem https://www.apache.org/licenses/LICENSE-2.0
-@rem
-@rem Unless required by applicable law or agreed to in writing, software
-@rem distributed under the License is distributed on an "AS IS" BASIS,
-@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-@rem See the License for the specific language governing permissions and
-@rem limitations under the License.
-@rem
-
-@if "%DEBUG%" == "" @echo off
-@rem ##########################################################################
-@rem
-@rem Gradle startup script for Windows
-@rem
-@rem ##########################################################################
-
-@rem Set local scope for the variables with windows NT shell
-if "%OS%"=="Windows_NT" setlocal
-
-set DIRNAME=%~dp0
-if "%DIRNAME%" == "" set DIRNAME=.
-set APP_BASE_NAME=%~n0
-set APP_HOME=%DIRNAME%
-
-@rem Resolve any "." and ".." in APP_HOME to make it shorter.
-for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
-
-@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
-set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
-
-@rem Find java.exe
-if defined JAVA_HOME goto findJavaFromJavaHome
-
-set JAVA_EXE=java.exe
-%JAVA_EXE% -version >NUL 2>&1
-if "%ERRORLEVEL%" == "0" goto init
-
-echo.
-echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
-echo.
-echo Please set the JAVA_HOME variable in your environment to match the
-echo location of your Java installation.
-
-goto fail
-
-:findJavaFromJavaHome
-set JAVA_HOME=%JAVA_HOME:"=%
-set JAVA_EXE=%JAVA_HOME%/bin/java.exe
-
-if exist "%JAVA_EXE%" goto init
-
-echo.
-echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
-echo.
-echo Please set the JAVA_HOME variable in your environment to match the
-echo location of your Java installation.
-
-goto fail
-
-:init
-@rem Get command-line arguments, handling Windows variants
-
-if not "%OS%" == "Windows_NT" goto win9xME_args
-
-:win9xME_args
-@rem Slurp the command line arguments.
-set CMD_LINE_ARGS=
-set _SKIP=2
-
-:win9xME_args_slurp
-if "x%~1" == "x" goto execute
-
-set CMD_LINE_ARGS=%*
-
-:execute
-@rem Setup the command line
-
-set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
-
-@rem Execute Gradle
-"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
-
-:end
-@rem End local scope for the variables with windows NT shell
-if "%ERRORLEVEL%"=="0" goto mainEnd
-
-:fail
-rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
-rem the _cmd.exe /c_ return code!
-if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
-exit /b 1
-
-:mainEnd
-if "%OS%"=="Windows_NT" endlocal
-
-:omega
diff --git a/ui/kotlinx-coroutines-android/animation-app/settings.gradle.kts b/ui/kotlinx-coroutines-android/animation-app/settings.gradle.kts
deleted file mode 100644
index b05d810b..00000000
--- a/ui/kotlinx-coroutines-android/animation-app/settings.gradle.kts
+++ /dev/null
@@ -1,5 +0,0 @@
-/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
- */
-
-include(":app")
diff --git a/ui/kotlinx-coroutines-android/build.gradle.kts b/ui/kotlinx-coroutines-android/build.gradle.kts
index 4f247883..9cec1dc9 100644
--- a/ui/kotlinx-coroutines-android/build.gradle.kts
+++ b/ui/kotlinx-coroutines-android/build.gradle.kts
@@ -1,15 +1,15 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
-import org.jetbrains.dokka.DokkaConfiguration.ExternalDocumentationLink
-import org.jetbrains.dokka.gradle.DokkaTask
-import java.net.URL
-
configurations {
create("r8")
}
+repositories {
+ mavenCentral()
+ jcenter() // https://youtrack.jetbrains.com/issue/IDEA-261387
+}
dependencies {
compileOnly("com.google.android:android:${version("android")}")
compileOnly("androidx.annotation:annotation:${version("androidx_annotation")}")
@@ -18,7 +18,7 @@ dependencies {
testImplementation("org.robolectric:robolectric:${version("robolectric")}")
testImplementation("org.smali:baksmali:${version("baksmali")}")
- "r8"("com.android.tools.build:builder:4.0.0-alpha06") // Contains r8-2.0.4-dev
+ "r8"("com.android.tools.build:builder:7.1.0-alpha01")
}
val optimizedDexDir = File(buildDir, "dex-optim/")
@@ -60,3 +60,46 @@ tasks.test {
externalDocumentationLink(
url = "https://developer.android.com/reference/"
)
+/*
+ * Task used by our ui/android tests to test minification results and keep track of size of the binary.
+ */
+open class RunR8 : JavaExec() {
+
+ @OutputDirectory
+ lateinit var outputDex: File
+
+ @InputFile
+ lateinit var inputConfig: File
+
+ @InputFile
+ val inputConfigCommon: File = File("testdata/r8-test-common.pro")
+
+ @InputFiles
+ val jarFile: File = project.tasks.named<Zip>("jar").get().archivePath
+
+ init {
+ classpath = project.configurations["r8"]
+ main = "com.android.tools.r8.R8"
+ }
+
+ override fun exec() {
+ // Resolve classpath only during execution
+ val arguments = mutableListOf(
+ "--release",
+ "--no-desugaring",
+ "--min-api", "26",
+ "--output", outputDex.absolutePath,
+ "--pg-conf", inputConfig.absolutePath
+ )
+ arguments.addAll(project.configurations["runtimeClasspath"].files.map { it.absolutePath })
+ arguments.add(jarFile.absolutePath)
+
+ args = arguments
+
+ project.delete(outputDex)
+ outputDex.mkdirs()
+
+ super.exec()
+ }
+}
+
diff --git a/ui/kotlinx-coroutines-android/example-app/.gitignore b/ui/kotlinx-coroutines-android/example-app/.gitignore
deleted file mode 100644
index 03d649e1..00000000
--- a/ui/kotlinx-coroutines-android/example-app/.gitignore
+++ /dev/null
@@ -1,7 +0,0 @@
-local.properties
-.gradle
-.idea
-build
-example-app.iml
-app/build
-app/app.iml
diff --git a/ui/kotlinx-coroutines-android/example-app/app/build.gradle.kts b/ui/kotlinx-coroutines-android/example-app/app/build.gradle.kts
deleted file mode 100644
index 39bba5bf..00000000
--- a/ui/kotlinx-coroutines-android/example-app/app/build.gradle.kts
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
- */
-
-plugins {
- id("com.android.application")
- kotlin("android")
- kotlin("android.extensions")
-}
-
-android {
- compileSdkVersion = "29"
- defaultConfig {
- applicationId = "com.example.app"
- minSdkVersion(14)
- targetSdkVersion(29)
- versionCode = 1
- versionName = "1.0"
- testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
- }
-}
-
-dependencies {
- implementation("androidx.appcompat:appcompat:1.0.2")
- implementation("androidx.constraintlayout:constraintlayout:1.1.3")
- implementation("com.google.android.material:material:1.0.0")
-
- implementation(kotlin("stdlib-jdk7"))
- implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:${property("coroutines_version")}")
-
- testImplementation("junit:junit:4.12")
- androidTestImplementation("androidx.test:runner:1.2.0")
- androidTestImplementation("androidx.test.espresso:espresso-core:3.2.0")
-}
diff --git a/ui/kotlinx-coroutines-android/example-app/app/src/main/AndroidManifest.xml b/ui/kotlinx-coroutines-android/example-app/app/src/main/AndroidManifest.xml
deleted file mode 100644
index b8b2a76c..00000000
--- a/ui/kotlinx-coroutines-android/example-app/app/src/main/AndroidManifest.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
- -->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.example.app">
-
- <application
- android:allowBackup="true"
- android:icon="@mipmap/ic_launcher"
- android:label="@string/app_name"
- android:roundIcon="@mipmap/ic_launcher_round"
- android:supportsRtl="true"
- android:theme="@style/AppTheme">
- <activity
- android:name=".MainActivity"
- android:label="@string/app_name"
- android:theme="@style/AppTheme.NoActionBar">
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
-
- <category android:name="android.intent.category.LAUNCHER" />
- </intent-filter>
- </activity>
- </application>
-
-</manifest>
diff --git a/ui/kotlinx-coroutines-android/example-app/app/src/main/java/com/example/app/MainActivity.kt b/ui/kotlinx-coroutines-android/example-app/app/src/main/java/com/example/app/MainActivity.kt
deleted file mode 100644
index 47bd16cc..00000000
--- a/ui/kotlinx-coroutines-android/example-app/app/src/main/java/com/example/app/MainActivity.kt
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
- */
-
-package com.example.app
-
-import android.os.Bundle
-import com.google.android.material.floatingactionbutton.FloatingActionButton
-import androidx.appcompat.app.AppCompatActivity
-import android.view.Menu
-import android.view.MenuItem
-import android.widget.TextView
-import kotlinx.android.synthetic.main.activity_main.*
-import kotlinx.android.synthetic.main.content_main.*
-
-class MainActivity : AppCompatActivity() {
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- setContentView(R.layout.activity_main)
- setSupportActionBar(toolbar)
- setup(hello, fab)
- }
-
- override fun onCreateOptionsMenu(menu: Menu): Boolean {
- // Inflate the menu; this adds items to the action bar if it is present.
- menuInflater.inflate(R.menu.menu_main, menu)
- return true
- }
-
- override fun onOptionsItemSelected(item: MenuItem): Boolean {
- // Handle action bar item clicks here. The action bar will
- // automatically handle clicks on the Home/Up button, so long
- // as you specify a parent activity in AndroidManifest.xml.
- val id = item.itemId
- if (id == R.id.action_settings) return true
- return super.onOptionsItemSelected(item)
- }
-}
-
-fun setup(hello: TextView, fab: FloatingActionButton) {
- // placeholder
-}
diff --git a/ui/kotlinx-coroutines-android/example-app/app/src/main/res/layout/activity_main.xml b/ui/kotlinx-coroutines-android/example-app/app/src/main/res/layout/activity_main.xml
deleted file mode 100644
index 0b728022..00000000
--- a/ui/kotlinx-coroutines-android/example-app/app/src/main/res/layout/activity_main.xml
+++ /dev/null
@@ -1,37 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
- -->
-
-<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:app="http://schemas.android.com/apk/res-auto"
- xmlns:tools="http://schemas.android.com/tools"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- tools:context="com.example.app.MainActivity">
-
- <com.google.android.material.appbar.AppBarLayout
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:theme="@style/AppTheme.AppBarOverlay">
-
- <androidx.appcompat.widget.Toolbar
- android:id="@+id/toolbar"
- android:layout_width="match_parent"
- android:layout_height="?attr/actionBarSize"
- android:background="?attr/colorPrimary"
- app:popupTheme="@style/AppTheme.PopupOverlay" />
-
- </com.google.android.material.appbar.AppBarLayout>
-
- <include layout="@layout/content_main" />
-
- <com.google.android.material.floatingactionbutton.FloatingActionButton
- android:id="@+id/fab"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="bottom|end"
- android:layout_margin="@dimen/fab_margin"
- app:srcCompat="@android:drawable/ic_dialog_email" />
-
-</androidx.coordinatorlayout.widget.CoordinatorLayout>
diff --git a/ui/kotlinx-coroutines-android/example-app/app/src/main/res/layout/content_main.xml b/ui/kotlinx-coroutines-android/example-app/app/src/main/res/layout/content_main.xml
deleted file mode 100644
index 77525183..00000000
--- a/ui/kotlinx-coroutines-android/example-app/app/src/main/res/layout/content_main.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
- -->
-
-<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:app="http://schemas.android.com/apk/res-auto"
- xmlns:tools="http://schemas.android.com/tools"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- app:layout_behavior="@string/appbar_scrolling_view_behavior"
- tools:context="com.example.app.MainActivity"
- tools:showIn="@layout/activity_main">
-
- <TextView
- android:id="@+id/hello"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="Hello World!"
- app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintLeft_toLeftOf="parent"
- app:layout_constraintRight_toRightOf="parent"
- app:layout_constraintTop_toTopOf="parent" />
-
-</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/ui/kotlinx-coroutines-android/example-app/app/src/main/res/menu/menu_main.xml b/ui/kotlinx-coroutines-android/example-app/app/src/main/res/menu/menu_main.xml
deleted file mode 100644
index c4ad0980..00000000
--- a/ui/kotlinx-coroutines-android/example-app/app/src/main/res/menu/menu_main.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-<menu xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:app="http://schemas.android.com/apk/res-auto"
- xmlns:tools="http://schemas.android.com/tools"
- tools:context="com.example.app.MainActivity">
- <item
- android:id="@+id/action_settings"
- android:orderInCategory="100"
- android:title="@string/action_settings"
- app:showAsAction="never" />
-</menu>
diff --git a/ui/kotlinx-coroutines-android/example-app/app/src/main/res/mipmap-hdpi/ic_launcher.png b/ui/kotlinx-coroutines-android/example-app/app/src/main/res/mipmap-hdpi/ic_launcher.png
deleted file mode 100644
index cde69bcc..00000000
--- a/ui/kotlinx-coroutines-android/example-app/app/src/main/res/mipmap-hdpi/ic_launcher.png
+++ /dev/null
Binary files differ
diff --git a/ui/kotlinx-coroutines-android/example-app/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/ui/kotlinx-coroutines-android/example-app/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
deleted file mode 100644
index 9a078e3e..00000000
--- a/ui/kotlinx-coroutines-android/example-app/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
+++ /dev/null
Binary files differ
diff --git a/ui/kotlinx-coroutines-android/example-app/app/src/main/res/mipmap-mdpi/ic_launcher.png b/ui/kotlinx-coroutines-android/example-app/app/src/main/res/mipmap-mdpi/ic_launcher.png
deleted file mode 100644
index c133a0cb..00000000
--- a/ui/kotlinx-coroutines-android/example-app/app/src/main/res/mipmap-mdpi/ic_launcher.png
+++ /dev/null
Binary files differ
diff --git a/ui/kotlinx-coroutines-android/example-app/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/ui/kotlinx-coroutines-android/example-app/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
deleted file mode 100644
index efc028a6..00000000
--- a/ui/kotlinx-coroutines-android/example-app/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
+++ /dev/null
Binary files differ
diff --git a/ui/kotlinx-coroutines-android/example-app/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/ui/kotlinx-coroutines-android/example-app/app/src/main/res/mipmap-xhdpi/ic_launcher.png
deleted file mode 100644
index bfa42f0e..00000000
--- a/ui/kotlinx-coroutines-android/example-app/app/src/main/res/mipmap-xhdpi/ic_launcher.png
+++ /dev/null
Binary files differ
diff --git a/ui/kotlinx-coroutines-android/example-app/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/ui/kotlinx-coroutines-android/example-app/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
deleted file mode 100644
index 3af2608a..00000000
--- a/ui/kotlinx-coroutines-android/example-app/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
+++ /dev/null
Binary files differ
diff --git a/ui/kotlinx-coroutines-android/example-app/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/ui/kotlinx-coroutines-android/example-app/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
deleted file mode 100644
index 324e72cd..00000000
--- a/ui/kotlinx-coroutines-android/example-app/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
+++ /dev/null
Binary files differ
diff --git a/ui/kotlinx-coroutines-android/example-app/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/ui/kotlinx-coroutines-android/example-app/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
deleted file mode 100644
index 9bec2e62..00000000
--- a/ui/kotlinx-coroutines-android/example-app/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
+++ /dev/null
Binary files differ
diff --git a/ui/kotlinx-coroutines-android/example-app/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/ui/kotlinx-coroutines-android/example-app/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
deleted file mode 100644
index aee44e13..00000000
--- a/ui/kotlinx-coroutines-android/example-app/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
+++ /dev/null
Binary files differ
diff --git a/ui/kotlinx-coroutines-android/example-app/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/ui/kotlinx-coroutines-android/example-app/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
deleted file mode 100644
index 34947cd6..00000000
--- a/ui/kotlinx-coroutines-android/example-app/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
+++ /dev/null
Binary files differ
diff --git a/ui/kotlinx-coroutines-android/example-app/app/src/main/res/values/colors.xml b/ui/kotlinx-coroutines-android/example-app/app/src/main/res/values/colors.xml
deleted file mode 100644
index 3ab3e9cb..00000000
--- a/ui/kotlinx-coroutines-android/example-app/app/src/main/res/values/colors.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<resources>
- <color name="colorPrimary">#3F51B5</color>
- <color name="colorPrimaryDark">#303F9F</color>
- <color name="colorAccent">#FF4081</color>
-</resources>
diff --git a/ui/kotlinx-coroutines-android/example-app/app/src/main/res/values/dimens.xml b/ui/kotlinx-coroutines-android/example-app/app/src/main/res/values/dimens.xml
deleted file mode 100644
index 59a0b0c4..00000000
--- a/ui/kotlinx-coroutines-android/example-app/app/src/main/res/values/dimens.xml
+++ /dev/null
@@ -1,3 +0,0 @@
-<resources>
- <dimen name="fab_margin">16dp</dimen>
-</resources>
diff --git a/ui/kotlinx-coroutines-android/example-app/app/src/main/res/values/strings.xml b/ui/kotlinx-coroutines-android/example-app/app/src/main/res/values/strings.xml
deleted file mode 100644
index a94b2dfb..00000000
--- a/ui/kotlinx-coroutines-android/example-app/app/src/main/res/values/strings.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<resources>
- <string name="app_name">ExampleApp</string>
- <string name="action_settings">Settings</string>
-</resources>
diff --git a/ui/kotlinx-coroutines-android/example-app/app/src/main/res/values/styles.xml b/ui/kotlinx-coroutines-android/example-app/app/src/main/res/values/styles.xml
deleted file mode 100644
index d4ea9ae7..00000000
--- a/ui/kotlinx-coroutines-android/example-app/app/src/main/res/values/styles.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<resources>
-
- <!-- Base application theme. -->
- <style name="AppTheme" parent="Theme.AppCompat">
- <!-- Customize your theme here. -->
- <item name="colorPrimary">@color/colorPrimary</item>
- <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
- <item name="colorAccent">@color/colorAccent</item>
- </style>
-
- <style name="AppTheme.NoActionBar">
- <item name="windowActionBar">false</item>
- <item name="windowNoTitle">true</item>
- </style>
-
- <style name="AppTheme.AppBarOverlay" parent="ThemeOverlay.AppCompat.Dark.ActionBar" />
-
- <style name="AppTheme.PopupOverlay" parent="ThemeOverlay.AppCompat.Light" />
-
-</resources>
diff --git a/ui/kotlinx-coroutines-android/example-app/build.gradle.kts b/ui/kotlinx-coroutines-android/example-app/build.gradle.kts
deleted file mode 100644
index 9cd0c592..00000000
--- a/ui/kotlinx-coroutines-android/example-app/build.gradle.kts
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
- */
-
-// Top-level build file where you can add configuration options common to all sub-projects/modules.
-
-buildscript {
- repositories {
- google()
- jcenter()
- }
- dependencies {
- classpath("com.android.tools.build:gradle:3.5.0")
- classpath(kotlin("gradle-plugin", property("kotlin_version") as String))
-
- // NOTE: Do not place your application dependencies here; they belong
- // in the individual module build.gradle files
- }
-}
-
-allprojects {
- repositories {
- google()
- jcenter()
- }
-}
-
-task<Delete>("clean") {
- delete(rootProject.buildDir)
-}
diff --git a/ui/kotlinx-coroutines-android/example-app/gradle.properties b/ui/kotlinx-coroutines-android/example-app/gradle.properties
deleted file mode 100644
index c4aa6758..00000000
--- a/ui/kotlinx-coroutines-android/example-app/gradle.properties
+++ /dev/null
@@ -1,28 +0,0 @@
-#
-# Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
-#
-
-# Project-wide Gradle settings.
-
-# IDE (e.g. Android Studio) users:
-# Gradle settings configured through the IDE *will override*
-# any settings specified in this file.
-
-# For more details on how to configure your build environment visit
-# http://www.gradle.org/docs/current/userguide/build_environment.html
-
-# Specifies the JVM arguments used for the daemon process.
-# The setting is particularly useful for tweaking memory settings.
-org.gradle.jvmargs=-Xmx1536m
-
-# When configured, Gradle will run in incubating parallel mode.
-# This option should only be used with decoupled projects. More details, visit
-# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
-# org.gradle.parallel=true
-
-kotlin_version=1.4.0
-coroutines_version=1.4.1
-
-android.useAndroidX=true
-android.enableJetifier=true
-
diff --git a/ui/kotlinx-coroutines-android/example-app/gradle/wrapper/gradle-wrapper.jar b/ui/kotlinx-coroutines-android/example-app/gradle/wrapper/gradle-wrapper.jar
deleted file mode 100644
index 490fda85..00000000
--- a/ui/kotlinx-coroutines-android/example-app/gradle/wrapper/gradle-wrapper.jar
+++ /dev/null
Binary files differ
diff --git a/ui/kotlinx-coroutines-android/example-app/gradle/wrapper/gradle-wrapper.properties b/ui/kotlinx-coroutines-android/example-app/gradle/wrapper/gradle-wrapper.properties
deleted file mode 100644
index dbe85eef..00000000
--- a/ui/kotlinx-coroutines-android/example-app/gradle/wrapper/gradle-wrapper.properties
+++ /dev/null
@@ -1,5 +0,0 @@
-distributionBase=GRADLE_USER_HOME
-distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.1-all.zip
-zipStoreBase=GRADLE_USER_HOME
-zipStorePath=wrapper/dists
diff --git a/ui/kotlinx-coroutines-android/example-app/gradlew b/ui/kotlinx-coroutines-android/example-app/gradlew
deleted file mode 100755
index 2fe81a7d..00000000
--- a/ui/kotlinx-coroutines-android/example-app/gradlew
+++ /dev/null
@@ -1,183 +0,0 @@
-#!/usr/bin/env sh
-
-#
-# Copyright 2015 the original author or authors.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# https://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-##############################################################################
-##
-## Gradle start up script for UN*X
-##
-##############################################################################
-
-# Attempt to set APP_HOME
-# Resolve links: $0 may be a link
-PRG="$0"
-# Need this for relative symlinks.
-while [ -h "$PRG" ] ; do
- ls=`ls -ld "$PRG"`
- link=`expr "$ls" : '.*-> \(.*\)$'`
- if expr "$link" : '/.*' > /dev/null; then
- PRG="$link"
- else
- PRG=`dirname "$PRG"`"/$link"
- fi
-done
-SAVED="`pwd`"
-cd "`dirname \"$PRG\"`/" >/dev/null
-APP_HOME="`pwd -P`"
-cd "$SAVED" >/dev/null
-
-APP_NAME="Gradle"
-APP_BASE_NAME=`basename "$0"`
-
-# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
-DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
-
-# Use the maximum available, or set MAX_FD != -1 to use that value.
-MAX_FD="maximum"
-
-warn () {
- echo "$*"
-}
-
-die () {
- echo
- echo "$*"
- echo
- exit 1
-}
-
-# OS specific support (must be 'true' or 'false').
-cygwin=false
-msys=false
-darwin=false
-nonstop=false
-case "`uname`" in
- CYGWIN* )
- cygwin=true
- ;;
- Darwin* )
- darwin=true
- ;;
- MINGW* )
- msys=true
- ;;
- NONSTOP* )
- nonstop=true
- ;;
-esac
-
-CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
-
-# Determine the Java command to use to start the JVM.
-if [ -n "$JAVA_HOME" ] ; then
- if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
- # IBM's JDK on AIX uses strange locations for the executables
- JAVACMD="$JAVA_HOME/jre/sh/java"
- else
- JAVACMD="$JAVA_HOME/bin/java"
- fi
- if [ ! -x "$JAVACMD" ] ; then
- die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
-
-Please set the JAVA_HOME variable in your environment to match the
-location of your Java installation."
- fi
-else
- JAVACMD="java"
- which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
-
-Please set the JAVA_HOME variable in your environment to match the
-location of your Java installation."
-fi
-
-# Increase the maximum file descriptors if we can.
-if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
- MAX_FD_LIMIT=`ulimit -H -n`
- if [ $? -eq 0 ] ; then
- if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
- MAX_FD="$MAX_FD_LIMIT"
- fi
- ulimit -n $MAX_FD
- if [ $? -ne 0 ] ; then
- warn "Could not set maximum file descriptor limit: $MAX_FD"
- fi
- else
- warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
- fi
-fi
-
-# For Darwin, add options to specify how the application appears in the dock
-if $darwin; then
- GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
-fi
-
-# For Cygwin or MSYS, switch paths to Windows format before running java
-if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
- APP_HOME=`cygpath --path --mixed "$APP_HOME"`
- CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
- JAVACMD=`cygpath --unix "$JAVACMD"`
-
- # We build the pattern for arguments to be converted via cygpath
- ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
- SEP=""
- for dir in $ROOTDIRSRAW ; do
- ROOTDIRS="$ROOTDIRS$SEP$dir"
- SEP="|"
- done
- OURCYGPATTERN="(^($ROOTDIRS))"
- # Add a user-defined pattern to the cygpath arguments
- if [ "$GRADLE_CYGPATTERN" != "" ] ; then
- OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
- fi
- # Now convert the arguments - kludge to limit ourselves to /bin/sh
- i=0
- for arg in "$@" ; do
- CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
- CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
-
- if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
- eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
- else
- eval `echo args$i`="\"$arg\""
- fi
- i=`expr $i + 1`
- done
- case $i in
- 0) set -- ;;
- 1) set -- "$args0" ;;
- 2) set -- "$args0" "$args1" ;;
- 3) set -- "$args0" "$args1" "$args2" ;;
- 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
- 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
- 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
- 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
- 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
- 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
- esac
-fi
-
-# Escape application args
-save () {
- for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
- echo " "
-}
-APP_ARGS=`save "$@"`
-
-# Collect all arguments for the java command, following the shell quoting and substitution rules
-eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
-
-exec "$JAVACMD" "$@"
diff --git a/ui/kotlinx-coroutines-android/example-app/gradlew.bat b/ui/kotlinx-coroutines-android/example-app/gradlew.bat
deleted file mode 100644
index 62bd9b9c..00000000
--- a/ui/kotlinx-coroutines-android/example-app/gradlew.bat
+++ /dev/null
@@ -1,103 +0,0 @@
-@rem
-@rem Copyright 2015 the original author or authors.
-@rem
-@rem Licensed under the Apache License, Version 2.0 (the "License");
-@rem you may not use this file except in compliance with the License.
-@rem You may obtain a copy of the License at
-@rem
-@rem https://www.apache.org/licenses/LICENSE-2.0
-@rem
-@rem Unless required by applicable law or agreed to in writing, software
-@rem distributed under the License is distributed on an "AS IS" BASIS,
-@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-@rem See the License for the specific language governing permissions and
-@rem limitations under the License.
-@rem
-
-@if "%DEBUG%" == "" @echo off
-@rem ##########################################################################
-@rem
-@rem Gradle startup script for Windows
-@rem
-@rem ##########################################################################
-
-@rem Set local scope for the variables with windows NT shell
-if "%OS%"=="Windows_NT" setlocal
-
-set DIRNAME=%~dp0
-if "%DIRNAME%" == "" set DIRNAME=.
-set APP_BASE_NAME=%~n0
-set APP_HOME=%DIRNAME%
-
-@rem Resolve any "." and ".." in APP_HOME to make it shorter.
-for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
-
-@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
-set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
-
-@rem Find java.exe
-if defined JAVA_HOME goto findJavaFromJavaHome
-
-set JAVA_EXE=java.exe
-%JAVA_EXE% -version >NUL 2>&1
-if "%ERRORLEVEL%" == "0" goto init
-
-echo.
-echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
-echo.
-echo Please set the JAVA_HOME variable in your environment to match the
-echo location of your Java installation.
-
-goto fail
-
-:findJavaFromJavaHome
-set JAVA_HOME=%JAVA_HOME:"=%
-set JAVA_EXE=%JAVA_HOME%/bin/java.exe
-
-if exist "%JAVA_EXE%" goto init
-
-echo.
-echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
-echo.
-echo Please set the JAVA_HOME variable in your environment to match the
-echo location of your Java installation.
-
-goto fail
-
-:init
-@rem Get command-line arguments, handling Windows variants
-
-if not "%OS%" == "Windows_NT" goto win9xME_args
-
-:win9xME_args
-@rem Slurp the command line arguments.
-set CMD_LINE_ARGS=
-set _SKIP=2
-
-:win9xME_args_slurp
-if "x%~1" == "x" goto execute
-
-set CMD_LINE_ARGS=%*
-
-:execute
-@rem Setup the command line
-
-set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
-
-@rem Execute Gradle
-"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
-
-:end
-@rem End local scope for the variables with windows NT shell
-if "%ERRORLEVEL%"=="0" goto mainEnd
-
-:fail
-rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
-rem the _cmd.exe /c_ return code!
-if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
-exit /b 1
-
-:mainEnd
-if "%OS%"=="Windows_NT" endlocal
-
-:omega
diff --git a/ui/kotlinx-coroutines-android/example-app/settings.gradle.kts b/ui/kotlinx-coroutines-android/example-app/settings.gradle.kts
deleted file mode 100644
index 15a801b1..00000000
--- a/ui/kotlinx-coroutines-android/example-app/settings.gradle.kts
+++ /dev/null
@@ -1 +0,0 @@
-include(":app")
diff --git a/ui/kotlinx-coroutines-android/src/AndroidExceptionPreHandler.kt b/ui/kotlinx-coroutines-android/src/AndroidExceptionPreHandler.kt
index b105e5b8..7d06752c 100644
--- a/ui/kotlinx-coroutines-android/src/AndroidExceptionPreHandler.kt
+++ b/ui/kotlinx-coroutines-android/src/AndroidExceptionPreHandler.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.android
diff --git a/ui/kotlinx-coroutines-android/src/HandlerDispatcher.kt b/ui/kotlinx-coroutines-android/src/HandlerDispatcher.kt
index af79da7c..ca8dd0d0 100644
--- a/ui/kotlinx-coroutines-android/src/HandlerDispatcher.kt
+++ b/ui/kotlinx-coroutines-android/src/HandlerDispatcher.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
@file:Suppress("unused")
@@ -7,8 +7,8 @@
package kotlinx.coroutines.android
import android.os.*
-import androidx.annotation.*
import android.view.*
+import androidx.annotation.*
import kotlinx.coroutines.*
import kotlinx.coroutines.internal.*
import java.lang.reflect.*
@@ -54,15 +54,22 @@ internal class AndroidDispatcherFactory : MainDispatcherFactory {
override fun createDispatcher(allFactories: List<MainDispatcherFactory>) =
HandlerContext(Looper.getMainLooper().asHandler(async = true))
- override fun hintOnError(): String? = "For tests Dispatchers.setMain from kotlinx-coroutines-test module can be used"
+ override fun hintOnError(): String = "For tests Dispatchers.setMain from kotlinx-coroutines-test module can be used"
override val loadPriority: Int
get() = Int.MAX_VALUE / 2
}
/**
- * Represents an arbitrary [Handler] as a implementation of [CoroutineDispatcher]
+ * Represents an arbitrary [Handler] as an implementation of [CoroutineDispatcher]
* with an optional [name] for nicer debugging
+ *
+ * ## Rejected execution
+ *
+ * If the underlying handler is closed and its message-scheduling methods start to return `false` on
+ * an attempt to submit a continuation task to the resulting dispatcher,
+ * then the [Job] of the affected task is [cancelled][Job.cancel] and the task is submitted to the
+ * [Dispatchers.IO], so that the affected coroutine can cleanup its resources and promptly complete.
*/
@JvmName("from") // this is for a nice Java API, see issue #255
@JvmOverloads
@@ -113,7 +120,7 @@ internal class HandlerContext private constructor(
* @param handler a handler.
* @param name an optional name for debugging.
*/
- public constructor(
+ constructor(
handler: Handler,
name: String? = null
) : this(handler, name, false)
@@ -129,24 +136,33 @@ internal class HandlerContext private constructor(
}
override fun dispatch(context: CoroutineContext, block: Runnable) {
- handler.post(block)
+ if (!handler.post(block)) {
+ cancelOnRejection(context, block)
+ }
}
override fun scheduleResumeAfterDelay(timeMillis: Long, continuation: CancellableContinuation<Unit>) {
val block = Runnable {
with(continuation) { resumeUndispatched(Unit) }
}
- handler.postDelayed(block, timeMillis.coerceAtMost(MAX_DELAY))
- continuation.invokeOnCancellation { handler.removeCallbacks(block) }
+ if (handler.postDelayed(block, timeMillis.coerceAtMost(MAX_DELAY))) {
+ continuation.invokeOnCancellation { handler.removeCallbacks(block) }
+ } else {
+ cancelOnRejection(continuation.context, block)
+ }
}
override fun invokeOnTimeout(timeMillis: Long, block: Runnable, context: CoroutineContext): DisposableHandle {
- handler.postDelayed(block, timeMillis.coerceAtMost(MAX_DELAY))
- return object : DisposableHandle {
- override fun dispose() {
- handler.removeCallbacks(block)
- }
+ if (handler.postDelayed(block, timeMillis.coerceAtMost(MAX_DELAY))) {
+ return DisposableHandle { handler.removeCallbacks(block) }
}
+ cancelOnRejection(context, block)
+ return NonDisposableHandle
+ }
+
+ private fun cancelOnRejection(context: CoroutineContext, block: Runnable) {
+ context.cancel(CancellationException("The task was rejected, the handler underlying the dispatcher '${toString()}' was closed"))
+ Dispatchers.IO.dispatch(context, block)
}
override fun toString(): String = toStringInternalImpl() ?: run {
diff --git a/ui/kotlinx-coroutines-android/test/DisabledHandlerTest.kt b/ui/kotlinx-coroutines-android/test/DisabledHandlerTest.kt
new file mode 100644
index 00000000..a1f0a03d
--- /dev/null
+++ b/ui/kotlinx-coroutines-android/test/DisabledHandlerTest.kt
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.android
+
+import android.os.*
+import kotlinx.coroutines.*
+import org.junit.*
+import org.junit.runner.*
+import org.robolectric.*
+import org.robolectric.annotation.*
+
+@RunWith(RobolectricTestRunner::class)
+@Config(manifest = Config.NONE, sdk = [28])
+class DisabledHandlerTest : TestBase() {
+
+ private var delegateToSuper = false
+ private val disabledDispatcher = object : Handler() {
+ override fun sendMessageAtTime(msg: Message?, uptimeMillis: Long): Boolean {
+ if (delegateToSuper) return super.sendMessageAtTime(msg, uptimeMillis)
+ return false
+ }
+ }.asCoroutineDispatcher()
+
+ @Test
+ fun testRunBlocking() {
+ expect(1)
+ try {
+ runBlocking(disabledDispatcher) {
+ expectUnreached()
+ }
+ expectUnreached()
+ } catch (e: CancellationException) {
+ finish(2)
+ }
+ }
+
+ @Test
+ fun testInvokeOnCancellation() = runTest {
+ val job = launch(disabledDispatcher, start = CoroutineStart.LAZY) { expectUnreached() }
+ job.invokeOnCompletion { if (it != null) expect(2) }
+ yield()
+ expect(1)
+ job.join()
+ finish(3)
+ }
+
+ @Test
+ fun testWithTimeout() = runTest {
+ delegateToSuper = true
+ try {
+ withContext(disabledDispatcher) {
+ expect(1)
+ delegateToSuper = false
+ delay(Long.MAX_VALUE - 1)
+ expectUnreached()
+ }
+ expectUnreached()
+ } catch (e: CancellationException) {
+ finish(2)
+ }
+ }
+}
diff --git a/ui/kotlinx-coroutines-android/test/HandlerDispatcherTest.kt b/ui/kotlinx-coroutines-android/test/HandlerDispatcherTest.kt
index 55decde6..5128a74c 100644
--- a/ui/kotlinx-coroutines-android/test/HandlerDispatcherTest.kt
+++ b/ui/kotlinx-coroutines-android/test/HandlerDispatcherTest.kt
@@ -29,7 +29,7 @@ class HandlerDispatcherTest : TestBase() {
fun mainIsAsync() = runTest {
ReflectionHelpers.setStaticField(Build.VERSION::class.java, "SDK_INT", 28)
- val mainLooper = ShadowLooper.getShadowMainLooper()
+ val mainLooper = shadowOf(Looper.getMainLooper())
mainLooper.pause()
val mainMessageQueue = shadowOf(Looper.getMainLooper().queue)
@@ -48,7 +48,7 @@ class HandlerDispatcherTest : TestBase() {
val main = Looper.getMainLooper().asHandler(async = true).asCoroutineDispatcher()
- val mainLooper = ShadowLooper.getShadowMainLooper()
+ val mainLooper = shadowOf(Looper.getMainLooper())
mainLooper.pause()
val mainMessageQueue = shadowOf(Looper.getMainLooper().queue)
@@ -67,7 +67,7 @@ class HandlerDispatcherTest : TestBase() {
val main = Looper.getMainLooper().asHandler(async = true).asCoroutineDispatcher()
- val mainLooper = ShadowLooper.getShadowMainLooper()
+ val mainLooper = shadowOf(Looper.getMainLooper())
mainLooper.pause()
val mainMessageQueue = shadowOf(Looper.getMainLooper().queue)
@@ -86,7 +86,7 @@ class HandlerDispatcherTest : TestBase() {
val main = Looper.getMainLooper().asHandler(async = true).asCoroutineDispatcher()
- val mainLooper = ShadowLooper.getShadowMainLooper()
+ val mainLooper = shadowOf(Looper.getMainLooper())
mainLooper.pause()
val mainMessageQueue = shadowOf(Looper.getMainLooper().queue)
@@ -105,7 +105,7 @@ class HandlerDispatcherTest : TestBase() {
val main = Looper.getMainLooper().asHandler(async = false).asCoroutineDispatcher()
- val mainLooper = ShadowLooper.getShadowMainLooper()
+ val mainLooper = shadowOf(Looper.getMainLooper())
mainLooper.pause()
val mainMessageQueue = shadowOf(Looper.getMainLooper().queue)
diff --git a/ui/kotlinx-coroutines-android/test/R8ServiceLoaderOptimizationTest.kt b/ui/kotlinx-coroutines-android/test/R8ServiceLoaderOptimizationTest.kt
index 47beb85b..5d60d641 100644
--- a/ui/kotlinx-coroutines-android/test/R8ServiceLoaderOptimizationTest.kt
+++ b/ui/kotlinx-coroutines-android/test/R8ServiceLoaderOptimizationTest.kt
@@ -11,6 +11,7 @@ import java.io.*
import java.util.stream.*
import kotlin.test.*
+@Ignore
class R8ServiceLoaderOptimizationTest : TestBase() {
private val r8Dex = File(System.getProperty("dexPath")!!).asDexFile()
private val r8DexNoOptim = File(System.getProperty("noOptimDexPath")!!).asDexFile()
diff --git a/ui/kotlinx-coroutines-javafx/build.gradle.kts b/ui/kotlinx-coroutines-javafx/build.gradle.kts
index 112441e0..9e30590c 100644
--- a/ui/kotlinx-coroutines-javafx/build.gradle.kts
+++ b/ui/kotlinx-coroutines-javafx/build.gradle.kts
@@ -1,15 +1,22 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
plugins {
- id("org.openjfx.javafxplugin")
+ id("org.openjfx.javafxplugin") version "0.0.9"
}
javafx {
version = version("javafx")
modules = listOf("javafx.controls")
- configuration = "compile"
+ configuration = "compileOnly"
+}
+
+sourceSets {
+ test.configure {
+ compileClasspath += configurations.compileOnly
+ runtimeClasspath += configurations.compileOnly
+ }
}
val JDK_18: String? by lazy {
diff --git a/ui/kotlinx-coroutines-javafx/src/JavaFxConvert.kt b/ui/kotlinx-coroutines-javafx/src/JavaFxConvert.kt
index c7fcb1c2..ebeaa3b8 100644
--- a/ui/kotlinx-coroutines-javafx/src/JavaFxConvert.kt
+++ b/ui/kotlinx-coroutines-javafx/src/JavaFxConvert.kt
@@ -1,13 +1,12 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.javafx
-import javafx.beans.value.ChangeListener
-import javafx.beans.value.ObservableValue
+import javafx.beans.value.*
import kotlinx.coroutines.*
-import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.channels.*
import kotlinx.coroutines.flow.*
/**
@@ -27,11 +26,11 @@ import kotlinx.coroutines.flow.*
@ExperimentalCoroutinesApi // Since 1.3.x
public fun <T> ObservableValue<T>.asFlow(): Flow<T> = callbackFlow<T> {
val listener = ChangeListener<T> { _, _, newValue ->
- try {
- offer(newValue)
- } catch (e: CancellationException) {
- // In case the event fires after the channel is closed
- }
+ /*
+ * Do not propagate the exception to the ObservableValue, it
+ * already should've been handled by the downstream
+ */
+ trySend(newValue)
}
addListener(listener)
send(value)
diff --git a/ui/kotlinx-coroutines-javafx/src/JavaFxDispatcher.kt b/ui/kotlinx-coroutines-javafx/src/JavaFxDispatcher.kt
index c3069d63..0a35cbf2 100644
--- a/ui/kotlinx-coroutines-javafx/src/JavaFxDispatcher.kt
+++ b/ui/kotlinx-coroutines-javafx/src/JavaFxDispatcher.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.javafx
diff --git a/ui/kotlinx-coroutines-javafx/test/JavaFxObservableAsFlowTest.kt b/ui/kotlinx-coroutines-javafx/test/JavaFxObservableAsFlowTest.kt
index 69640501..bc40b0fd 100644
--- a/ui/kotlinx-coroutines-javafx/test/JavaFxObservableAsFlowTest.kt
+++ b/ui/kotlinx-coroutines-javafx/test/JavaFxObservableAsFlowTest.kt
@@ -83,4 +83,20 @@ class JavaFxObservableAsFlowTest : TestBase() {
}
}
+ @Test
+ fun testIntermediateCrash() = runTest {
+ if (!initPlatform()) {
+ println("Skipping JavaFxTest in headless environment")
+ return@runTest // ignore test in headless environments
+ }
+
+ val property = SimpleIntegerProperty(0)
+
+ assertFailsWith<TestException> {
+ property.asFlow().onEach {
+ yield()
+ throw TestException()
+ }.collect()
+ }
+ }
}
diff --git a/ui/kotlinx-coroutines-javafx/test/guide/example-ui-actor-02.kt b/ui/kotlinx-coroutines-javafx/test/guide/example-ui-actor-02.kt
index 51e94779..ec8a09f9 100644
--- a/ui/kotlinx-coroutines-javafx/test/guide/example-ui-actor-02.kt
+++ b/ui/kotlinx-coroutines-javafx/test/guide/example-ui-actor-02.kt
@@ -67,6 +67,6 @@ fun Node.onClick(action: suspend (MouseEvent) -> Unit) {
}
// install a listener to offer events to this actor
onMouseClicked = EventHandler { event ->
- eventActor.offer(event)
+ eventActor.trySend(event)
}
}
diff --git a/ui/kotlinx-coroutines-javafx/test/guide/example-ui-actor-03.kt b/ui/kotlinx-coroutines-javafx/test/guide/example-ui-actor-03.kt
index 81371678..aa152b79 100644
--- a/ui/kotlinx-coroutines-javafx/test/guide/example-ui-actor-03.kt
+++ b/ui/kotlinx-coroutines-javafx/test/guide/example-ui-actor-03.kt
@@ -65,8 +65,8 @@ fun Node.onClick(action: suspend (MouseEvent) -> Unit) {
val eventActor = GlobalScope.actor<MouseEvent>(Dispatchers.Main, capacity = Channel.CONFLATED) { // <--- Changed here
for (event in channel) action(event) // pass event to action
}
- // install a listener to offer events to this actor
+ // install a listener to send events to this actor
onMouseClicked = EventHandler { event ->
- eventActor.offer(event)
+ eventActor.trySend(event)
}
}
diff --git a/ui/kotlinx-coroutines-javafx/test/guide/example-ui-blocking-01.kt b/ui/kotlinx-coroutines-javafx/test/guide/example-ui-blocking-01.kt
index ea5ac90a..0c89ea70 100644
--- a/ui/kotlinx-coroutines-javafx/test/guide/example-ui-blocking-01.kt
+++ b/ui/kotlinx-coroutines-javafx/test/guide/example-ui-blocking-01.kt
@@ -55,7 +55,7 @@ fun Node.onClick(action: suspend (MouseEvent) -> Unit) {
for (event in channel) action(event) // pass event to action
}
onMouseClicked = EventHandler { event ->
- eventActor.offer(event)
+ eventActor.trySend(event)
}
}
diff --git a/ui/kotlinx-coroutines-javafx/test/guide/example-ui-blocking-02.kt b/ui/kotlinx-coroutines-javafx/test/guide/example-ui-blocking-02.kt
index 504f2ee6..6e8b984a 100644
--- a/ui/kotlinx-coroutines-javafx/test/guide/example-ui-blocking-02.kt
+++ b/ui/kotlinx-coroutines-javafx/test/guide/example-ui-blocking-02.kt
@@ -55,7 +55,7 @@ fun Node.onClick(action: suspend (MouseEvent) -> Unit) {
for (event in channel) action(event) // pass event to action
}
onMouseClicked = EventHandler { event ->
- eventActor.offer(event)
+ eventActor.trySend(event)
}
}
diff --git a/ui/kotlinx-coroutines-javafx/test/guide/example-ui-blocking-03.kt b/ui/kotlinx-coroutines-javafx/test/guide/example-ui-blocking-03.kt
index 0e115367..3ff5d7d5 100644
--- a/ui/kotlinx-coroutines-javafx/test/guide/example-ui-blocking-03.kt
+++ b/ui/kotlinx-coroutines-javafx/test/guide/example-ui-blocking-03.kt
@@ -55,7 +55,7 @@ fun Node.onClick(action: suspend (MouseEvent) -> Unit) {
for (event in channel) action(event) // pass event to action
}
onMouseClicked = EventHandler { event ->
- eventActor.offer(event)
+ eventActor.trySend(event)
}
}
diff --git a/ui/kotlinx-coroutines-swing/build.gradle.kts b/ui/kotlinx-coroutines-swing/build.gradle.kts
index b834f29f..b8ca906a 100644
--- a/ui/kotlinx-coroutines-swing/build.gradle.kts
+++ b/ui/kotlinx-coroutines-swing/build.gradle.kts
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
dependencies {
diff --git a/ui/kotlinx-coroutines-swing/src/SwingDispatcher.kt b/ui/kotlinx-coroutines-swing/src/SwingDispatcher.kt
index 054ed1f6..d2d9b786 100644
--- a/ui/kotlinx-coroutines-swing/src/SwingDispatcher.kt
+++ b/ui/kotlinx-coroutines-swing/src/SwingDispatcher.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.swing
diff --git a/ui/kotlinx-coroutines-swing/test/examples/swing-example.kt b/ui/kotlinx-coroutines-swing/test/examples/swing-example.kt
deleted file mode 100644
index cadb4681..00000000
--- a/ui/kotlinx-coroutines-swing/test/examples/swing-example.kt
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
- */
-
-package examples
-
-import kotlinx.coroutines.*
-import kotlinx.coroutines.swing.*
-import java.text.*
-import java.util.*
-import java.util.concurrent.*
-import kotlin.coroutines.*
-
-fun log(msg: String) = println("${SimpleDateFormat("yyyyMMdd-HHmmss.sss").format(Date())} [${Thread.currentThread().name}] $msg")
-
-suspend fun makeRequest(): String {
- log("Making request...")
- return suspendCoroutine { c ->
- ForkJoinPool.commonPool().execute {
- c.resume("Result of the request")
- }
- }
-}
-
-fun display(result: String) {
- log("Displaying result '$result'")
-}
-
-fun main(args: Array<String>) = runBlocking(Dispatchers.Swing) {
- try {
- // suspend while asynchronously making request
- val result = makeRequest()
- // example.display result in UI, here Swing dispatcher ensures that we always stay in event dispatch thread
- display(result)
- } catch (exception: Throwable) {
- // process exception
- }
-}
-